iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
1

戶外教學的第二天晚上老師們準備了試膽遊戲,讓小孩子們拿著蠟燭進入一個洞窟在返回到洞口。可惜這個試膽遊戲沒有接下來的課程精彩

↑ 小櫻到是在怕什麼啦,都見過大風大浪了~,以後還有資料結構、演算法、計算機組織等著你吔

$("#奈緒子").fadeOut(1000, function(){
    $("#利佳").fadeOut(1000, function(){
         $("#千春").fadeOut(1000);
    });
});

↑ 有要教 jQuery 的,這個梗就送給各位了

為什麼大家會消(淡)失(出)呢?一定又是 Golang 牌搞得鬼

各位魔法使們,我們一起跟著小櫻和小狼,收服「struct」並把被消失的同學們淡入回來吧!!

type 定義型態別名

收服 struct 之前,我們必需先搞懂什麼是 type ,如果是精通 C 的魔法使們,Go中的type 其實可以理解成 C 中的 typedef

package main
import "fmt"

type myint int

func main(){
    var num myint
    num = 10
    fmt.Println(num)
}

執行結果:
10

利用 type 可以實現「型態別名」什麼是型態別名呢?就是同個型態有不同的名稱。常見的型態別名比如 runerune 其實就是 int32 的別名,在第三課就有提到: #3 字串 ─ 知世:反正我們是屬於正義的一方 | Golang魔法使

struct 定義複合型態

struct 是一種複合型態,可以在一個 struct 內存放不同型態的變數,這一節也是相當複雜的單元,除非你學過 C 或 C++ 那就另當別論

透過 struct 可以存放各種不同型態的變數,比如我想在一個變數中同時存有 var name stringvar age uint

直覺的做法是分別宣告兩個變數,但實際上可以利用 struct 可以把這兩個型態包在一起:

struct{
    name string
    age uint
}

這個 struct 並不是變數 而是一個自創的型態,我們可以把這個自創型態命名成:person

type person struct{
    name string
    age uint
}

宣告 struct 型態的變數

宣告一個 person 型態的變數

package main
import "fmt"

type person struct{
    name string
    age uint
}

func main(){
    var sakura person    // 型態:person
    sakura.name = "小櫻"
    sakura.age  = 10
    fmt.Println(sakura)
    fmt.Printf("%T", sakura)    // 利用 %T 印出型態
}

執行結果:
{小櫻 10}
main.person

型態為 person 可以理解但為什麼會印出 main.person 呢?這個會牽涉到後面的課程(會劇逶),留到以後再做說明

更簡短的宣告

package main
import "fmt"

type person struct{
    name string
    age uint
}

func main(){
    sakura := person{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    fmt.Printf("%T", sakura)    // 利用 %T 印出型態
}

執行結果:
{小櫻 10}
main.person

不使用 type 直接使用 struct 宣告

package main
import "fmt"

func main(){
    sakura := struct{
        name string
        age uint
    }{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    fmt.Printf("%T", sakura)    // 利用 %T 印出型態
}

執行結果:
{小櫻 10}
struct { name string; age uint }

可能一時之間沒辦法理解,請大家對照前一支程式把 person 的地方用 struct{name string age uint} 取代掉也許就能理解了

在函式中使用 struct

以下為錯誤範例 試著將 person 中的 age 加一

package main
import "fmt"

type person struct{
    name string
    age uint
}

func plusOne(p person){
    p.age = p.age + 1
}

func main(){
    sakura := person{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    plusOne(sakura)
    fmt.Println(sakura)
}

執行結果:
{小櫻 10}
{小櫻 10}

如同上一課所說,sakura 是一個 person 型態 的變數,並不是「指標變數」,若想要能在函式中更改 sakura 那麼我們需要取得 sakura 的指標才行

在函式中使用指向某個 struct{} 的指標

如果你在上一課的指標學的不錯,那麼你一定很快就能推出以下的Golang牌咒語:

package main
import "fmt"

type person struct{
    name string
    age uint
}

func plusOne(p *person){    // 改用 *person
    (*p).age = (*p).age + 1 // 第 10 行
}

func main(){
    sakura := person{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    plusOne(&sakura)        // 利用 & 取得 sakura 的位址
    fmt.Println(sakura)
}

執行結果:
{小櫻 10}
{小櫻 11}

第 10 行中這個寫法看似有些麻煩有沒有更簡單的寫法呢?其實是有的!在 Golang 中,如果是一個 struct{} 出來的變數是用 . 去取得值;而如果是一個指向 struct{} 的變數仍然可以直接透過 . 去取值,也就是說

(*p).age = (*p).age + 1
// 完全可以改成
p.age = p.age + 1
// 即使 p 的型態是 *person 不是 person
package main
import "fmt"

type person struct{
    name string
    age uint
}

func plusOne(p *person){
    p.age = p.age + 1  // 等效於 (*p).age = (*p).age + 1
}

func main(){
    sakura := person{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    plusOne(&sakura)
    fmt.Println(sakura)
}

執行結果:
{小櫻 10}
{小櫻 11}

如果原先是 C 語言的魔法使,可能會無法那麼快熟悉,因此文末會做一個比較,讓你快速熟悉 C 和 Go

利用 new() 宣告一個指向 struct{} 的指標變數

除了先前提到的宣告方式,在 Golang 中可以直接用 new() 宣告指向 struct{} 的指標變數,因為比起 struct{} 我們更喜歡使用 *struct{}

要注意的一點是,如果 new 裡所使用的是型態 T 那麼其產生的會是 *T

請觀察以下程式

package main
import "fmt"

type person struct{
    name string
    age uint
}

func main(){
    // new() 出來的變數型態會是 new() 裡頭所放的指標
    // 即 sukura 的型態是 *person
    sakura := new(person)
    sakura.name = "小櫻"
    sakura.age  = 10

    fmt.Println(sakura)    // sakura 的型態是 *person
    fmt.Println(*sakura)   // *sakura 的型態是 person
}

執行結果:
&{小櫻 10}
{小櫻 10}

這個執行結果還滿有趣的,透過 new(person) 產生的 sakura 型態是 *person,所以在我們呼叫 fmt.Println(sakura) 時理論上是可以印出一串指向 sakura 位址的數字,但 Golang 反而是更人性化的印出記憶體的內容並在前面標示 & 來代表是取該值的位址

這個 new 的用法,跟C語言的 malloc 極為相似

全域變數

前幾篇文有提到 scope 的概念,然而如果將變數在某個地方宣告,那麼他的 scope 會相當之大,可以讓該Golang牌的所有函式都能直接使用,該變數稱為全域變數

package main
import "fmt"

var n int

func double(){
    n = n*2
}

func main(){
    n = 5
    fmt.Println(n)
    double()
    fmt.Println(n)
}

執行結果:
5
10

我們可以在 func main() 之外直接宣告「全域變數」這個變數不但可以在 main() 裡面使用,也能在其他函式使用,不需要傳遞

或者你也可以這樣寫

package main
import "fmt"

var n = 5

func double(){
    n = n*2
}

func main(){
    fmt.Println(n)
    double()
    fmt.Println(n)
}

執行結果:
5
10

先前並沒有提及 var n = 5 這種寫法,這種寫法其實就跟 n := 5 有異曲同功之妙。另一方面除了這種寫法,寫成 var n int = 5 也是可以的

但是不可以這樣寫

package main
import "fmt"

n := 5

func double(){
    n = n*2
}

func main(){
    fmt.Println(n)
    double()
    fmt.Println(n)
}

# command-line-arguments

.\lesson11.go:4:1: syntax error: non-declaration statement outside function body

先前沒有特別提到,這種 := 稱為短變數宣告,golang中規定其不可以在函式外使用

補充 ── 從 C 到 Go

在 C 魔法中 struct 搭配 -> 取值,而指標則搭配 . 取值,會令人相當不習慣

我們嘗試用 C 魔法改寫以下 Golang 魔法

package main
import "fmt"

type person struct{
    name string
    age uint
}

func plusOne(p *person){
    p.age = p.age + 1  // 等效於 (*p).age = (*p).age + 1
}

func main(){
    sakura := person{
        name : "小櫻",
        age  : 10,
    }
    fmt.Println(sakura)
    plusOne(&sakura)
    fmt.Println(sakura)
}

執行結果:
{小櫻 10}
{小櫻 11}

以C改寫:

#include<stdio.h>

// golang 的 type 和 c 的 typedef 順序相反
typedef struct p{
    char* name;
    unsigned int age;
}person;

// golang 的 *person 在 c 中是 person*
void plusOne(person* p){
    // 指標取值用 ->
    p->age = p->age + 1;
}

int main(){
    person sakura;
    // 一般的變數則用 .
    sakura.name = "小櫻";
    sakura.age  = 10;
    printf("{%s %d}\n", sakura.name, sakura.age);
    plusOne(&sakura);
    printf("{%s %d}\n", sakura.name, sakura.age);

    return 0;
}

執行結果:
{小櫻 10}
{小櫻 11}

本文多數圖片來自:
庫洛魔法使第一季第十七集


上一篇
#10 指標 Pointer | Golang魔法使
下一篇
#12 方法 Method & 空指標 nil (不是爆哥neal) | Golang魔法使
系列文
Golang魔法使 ─ 30天從零開始學習Go語言 | 比Python還簡單 | 理科生一定學得會 | 文科生不好說30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言