這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。歡迎來到 [Golang 系列教程](/subject/2)的第 16 個教程。 ### 什麼是結構體?結構體是使用者定義的類型,表示若干個欄位(Field)的集合。有時應該把資料整合在一起,而不是讓這些資料沒有聯絡。這種情況下可以使用結構體。例如,一個職員有 `firstName`、`lastName` 和 `age` 三個屬性,而把這些屬性群組合在一個結構體 `employee` 中就很合理。### 結構體的聲明```gotype Employee struct { firstName string lastName string age int}```在上面的程式碼片段裡,聲明了一個結構體類型 `Employee`,它有 `firstName`、`lastName` 和 `age` 三個欄位。通過把相同類型的欄位聲明在同一行,結構體可以變得更加緊湊。在上面的結構體中,`firstName` 和 `lastName` 屬於相同的 `string` 類型,於是這個結構體可以重寫為:```gotype Employee struct { firstName, lastName string age, salary int}```上面的結構體 `Employee` 稱為 **命名的結構體(Named Structure)**。我們建立了名為 `Employee` 的新類型,而它可以用於建立 `Employee` 類型的結構體變數。 聲明結構體時也可以不用聲明一個新類型,這樣的結構體類型稱為 **匿名結構體(Anonymous Structure)**。```govar employee struct { firstName, lastName string age int}```上述程式碼片段建立一個**匿名結構體** `employee`。### 建立命名的結構體通過下面代碼,我們定義了一個**命名的結構體 `Employee`**。```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { //creating structure using field names emp1 := Employee{ firstName: "Sam", age: 25, salary: 500, lastName: "Anderson", } //creating structure without using field names emp2 := Employee{"Thomas", "Paul", 29, 800} fmt.Println("Employee 1", emp1) fmt.Println("Employee 2", emp2)}``` [線上運行程式](https://play.golang.org/p/uhPAHeUwvK) 在上述程式的第 7 行,我們建立了一個命名的結構體 `Employee`。而在第 15 行,通過指定每個欄位名的值,我們定義了結構體變數 `emp1`。欄位名的順序不一定要與聲明結構體類型時的順序相同。在這裡,我們改變了 `lastName` 的位置,將其移到了末尾。這樣做也不會有任何的問題。在上面程式的第 23 行,定義 `emp2` 時我們省略了欄位名。在這種情況下,就需要保證欄位名的順序與聲明結構體時的順序相同。該程式將輸出:```Employee 1 {Sam Anderson 25 500}Employee 2 {Thomas Paul 29 800}```### 建立匿名結構體```gopackage mainimport ( "fmt")func main() { emp3 := struct { firstName, lastName string age, salary int }{ firstName: "Andreah", lastName: "Nikola", age: 31, salary: 5000, } fmt.Println("Employee 3", emp3)}```[線上運行程式](https://play.golang.org/p/TEMFM3oZiq) 在上述程式的第 3 行,我們定義了一個**匿名結構體變數** `emp3`。上面我們已經提到,之所以稱這種結構體是匿名的,是因為它只是建立一個新的結構體變數 `em3`,而沒有定義任何結構體類型。該程式會輸出:```Employee 3 {Andreah Nikola 31 5000}```### 結構體的零值(Zero Value)當定義好的結構體並沒有被顯式地初始化時,該結構體的欄位將預設賦為零值。```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { var emp4 Employee //zero valued structure fmt.Println("Employee 4", emp4)}```[線上運行程式](https://play.golang.org/p/p7_OpVdFXJ) 該程式定義了 `emp4`,卻沒有初始化任何值。因此 `firstName` 和 `lastName` 賦值為 string 的零值(`""`)。而 `age` 和 `salary` 賦值為 int 的零值(0)。該程式會輸出:```Employee 4 { 0 0}```當然還可以為某些欄位指定初始值,而忽略其他欄位。這樣,忽略的欄位名會賦值為零值。 ```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { emp5 := Employee{ firstName: "John", lastName: "Paul", } fmt.Println("Employee 5", emp5)}```[線上運行程式](https://play.golang.org/p/w2gPoCnlZ1) 在上面程式中的第 14 行和第 15 行,我們初始化了 `firstName` 和 `lastName`,而 `age` 和 `salary` 沒有進行初始化。因此 `age` 和 `salary` 賦值為零值。該程式會輸出:```Employee 5 {John Paul 0 0}```### 訪問結構體的欄位點號操作符 `.` 用於訪問結構體的欄位。```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { emp6 := Employee{"Sam", "Anderson", 55, 6000} fmt.Println("First Name:", emp6.firstName) fmt.Println("Last Name:", emp6.lastName) fmt.Println("Age:", emp6.age) fmt.Printf("Salary: $%d", emp6.salary)}```[線上運行程式](https://play.golang.org/p/GPd_sT85IS) 上面程式中的 **emp6.firstName** 訪問了結構體 `emp6` 的欄位 `firstName`。該程式輸出:```First Name: Sam Last Name: Anderson Age: 55 Salary: $6000 ```還可以建立零值的 `struct`,以後再給各個欄位賦值。```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { var emp7 Employee emp7.firstName = "Jack" emp7.lastName = "Adams" fmt.Println("Employee 7:", emp7)}```[線上運行程式](https://play.golang.org/p/ZEOx10g7nN) 在上面程式中,我們定義了 `emp7`,接著給 `firstName` 和 `lastName` 賦值。該程式會輸出: ```Employee 7: {Jack Adams 0 0}```### 結構體的指標還可以建立指向結構體的指標。```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { emp8 := &Employee{"Sam", "Anderson", 55, 6000} fmt.Println("First Name:", (*emp8).firstName) fmt.Println("Age:", (*emp8).age)}```[線上運行程式](https://play.golang.org/p/xj87UCnBtH) 在上面程式中,**emp8** 是一個指向結構體 `Employee` 的指標。`(*emp8).firstName` 表示訪問結構體 `emp8` 的 `firstName` 欄位。該程式會輸出:```First Name: SamAge: 55```**Go 語言允許我們在訪問 `firstName` 欄位時,可以使用 `emp8.firstName` 來代替顯式的解引用 `(*emp8).firstName`**。 ```gopackage mainimport ( "fmt")type Employee struct { firstName, lastName string age, salary int}func main() { emp8 := &Employee{"Sam", "Anderson", 55, 6000} fmt.Println("First Name:", emp8.firstName) fmt.Println("Age:", emp8.age)}```[線上運行程式](https://play.golang.org/p/0ZE265qQ1h) 在上面的程式中,我們使用 `emp8.firstName` 來訪問 `firstName` 欄位,該程式會輸出: ```First Name: SamAge: 55```### 匿名欄位當我們建立結構體時,欄位可以只有類型,而沒有欄位名。這樣的欄位稱為匿名欄位(Anonymous Field)。 以下代碼建立一個 `Person` 結構體,它含有兩個匿名欄位 `string` 和 `int`。 ```gotype Person struct { string int}```我們接下來使用匿名欄位來編寫一個程式。 ```gopackage mainimport ( "fmt")type Person struct { string int}func main() { p := Person{"Naveen", 50} fmt.Println(p)}```[線上運行程式](https://play.golang.org/p/YF-DgdVSrC) 在上面的程式中,結構體 `Person` 有兩個匿名欄位。`p := Person{"Naveen", 50}` 定義了一個 `Person` 類型的變數。該程式輸出 `{Naveen 50}`。 **雖然匿名欄位沒有名稱,但其實匿名欄位的名稱就預設為它的類型**。比如在上面的 `Person` 結構體裡,雖說欄位是匿名的,但 Go 預設這些欄位名是它們各自的類型。所以 `Person` 結構體有兩個名為 `string` 和 `int` 的欄位。 ```gopackage mainimport ( "fmt")type Person struct { string int}func main() { var p1 Person p1.string = "naveen" p1.int = 50 fmt.Println(p1)}```[線上運行程式](https://play.golang.org/p/K-fGNxVyiA) 在上面程式的第 14 行和第 15 行,我們訪問了 `Person` 結構體的匿名欄位,我們把欄位類型作為欄位名,分別為 "string" 和 "int"。上面程式的輸出如下: ```{naveen 50}```### 嵌套結構體(Nested Structs)結構體的欄位有可能也是一個結構體。這樣的結構體稱為嵌套結構體。 ```gopackage mainimport ( "fmt")type Address struct { city, state string}type Person struct { name string age int address Address}func main() { var p Person p.name = "Naveen" p.age = 50 p.address = Address { city: "Chicago", state: "Illinois", } fmt.Println("Name:", p.name) fmt.Println("Age:",p.age) fmt.Println("City:",p.address.city) fmt.Println("State:",p.address.state)}```[線上運行程式](https://play.golang.org/p/46jkQFdTPO) 上面的結構體 `Person` 有一個欄位 `address`,而 `address` 也是結構體。該程式輸出: ```Name: Naveen Age: 50 City: Chicago State: Illinois ```### 提升欄位(Promoted Fields)如果是結構體中有匿名的結構體類型欄位,則該匿名結構體裡的欄位就稱為提升欄位。這是因為提升欄位就像是屬於外部結構體一樣,可以用外部結構體直接存取。我知道這種定義很複雜,所以我們直接研究下代碼來理解吧。 ```gotype Address struct { city, state string}type Person struct { name string age int Address}```在上面的程式碼片段中,`Person` 結構體有一個匿名欄位 `Address`,而 `Address` 是一個結構體。現在結構體 `Address` 有 `city` 和 `state` 兩個欄位,訪問這兩個欄位就像在 `Person` 裡直接聲明的一樣,因此我們稱之為提升欄位。```gopackage mainimport ( "fmt")type Address struct { city, state string}type Person struct { name string age int Address}func main() { var p Person p.name = "Naveen" p.age = 50 p.Address = Address{ city: "Chicago", state: "Illinois", } fmt.Println("Name:", p.name) fmt.Println("Age:", p.age) fmt.Println("City:", p.city) //city is promoted field fmt.Println("State:", p.state) //state is promoted field}```[線上運行程式](https://play.golang.org/p/OgeHCJYoEy) 在上面代碼中的第 26 行和第 27 行,我們使用了文法 `p.city` 和 `p.state`,訪問提升欄位 `city` 和 `state` 就像它們是在結構體 `p` 中聲明的一樣。該程式會輸出:```Name: Naveen Age: 50 City: Chicago State: Illinois ```### 匯出結構體和欄位如果結構體名稱以大寫字母開頭,則它是其他包可以訪問的匯出類型(Exported Type)。同樣,如果結構體裡的欄位首字母大寫,它也能被其他包訪問到。 讓我們使用自訂包,編寫一個程式來更好地去理解它。 在你的 Go 工作區的 `src` 目錄中,建立一個名為 `structs` 的檔案夾。另外在 `structs` 中再建立一個目錄 `computer`。 在 `computer` 目錄中,在名為 `spec.go` 的檔案中儲存下面的程式。 ```gopackage computertype Spec struct { //exported struct Maker string //exported field model string //unexported field Price int //exported field}```上面的程式碼片段中,建立了一個 `computer` 包,裡面有一個匯出結構體類型 `Spec`。`Spec` 有兩個匯出欄位 `Maker` 和 `Price`,和一個未匯出的欄位 `model`。接下來我們會在 main 包中匯入這個包,並使用 `Spec` 結構體。```gopackage mainimport "structs/computer" import "fmt"func main() { var spec computer.Spec spec.Maker = "apple" spec.Price = 50000 fmt.Println("Spec:", spec)}```包結構如下所示: ```src structs computer spec.go main.go```在上述程式的第 3 行,我們匯入了 `computer` 包。在第 8 行和第 9 行,我們訪問了結構體 `Spec` 的兩個匯出欄位 `Maker` 和 `Price`。執行命令 `go install structs` 和 `workspacepath/bin/structs`,運行該程式。 如果我們試圖訪問未匯出的欄位 `model`,編譯器會報錯。將 `main.go` 的內容替換為下面的代碼。 ```gopackage mainimport "structs/computer" import "fmt"func main() { var spec computer.Spec spec.Maker = "apple" spec.Price = 50000 spec.model = "Mac Mini" fmt.Println("Spec:", spec)}```在上面程式的第 10 行,我們試圖訪問未匯出的欄位 `model`。如果運行這個程式,編譯器會產生錯誤:**spec.model undefined (cannot refer to unexported field or method model)**。 ### 結構體相等性(Structs Equality)**結構體是實值型別。如果它的每一個欄位都是可比較的,則該結構體也是可比較的。如果兩個結構體變數的對應欄位相等,則這兩個變數也是相等的**。```gopackage mainimport ( "fmt")type name struct { firstName string lastName string}func main() { name1 := name{"Steve", "Jobs"} name2 := name{"Steve", "Jobs"} if name1 == name2 { fmt.Println("name1 and name2 are equal") } else { fmt.Println("name1 and name2 are not equal") } name3 := name{firstName:"Steve", lastName:"Jobs"} name4 := name{} name4.firstName = "Steve" if name3 == name4 { fmt.Println("name3 and name4 are equal") } else { fmt.Println("name3 and name4 are not equal") }}```[線上運行程式](https://play.golang.org/p/AU1FkdsPk7) 在上面的代碼中,結構體類型 `name` 包含兩個 `string` 類型。由於字串是可比較的,因此可以比較兩個 `name` 類型的結構體變數。 上面代碼中 `name1` 和 `name2` 相等,而 `name3` 和 `name4` 不相等。該程式會輸出: ```name1 and name2 are equal name3 and name4 are not equal ```**如果結構體包含不可比較的欄位,則結構體變數也不可比較。** ```gopackage mainimport ( "fmt")type image struct { data map[int]int}func main() { image1 := image{data: map[int]int{ 0: 155, }} image2 := image{data: map[int]int{ 0: 155, }} if image1 == image2 { fmt.Println("image1 and image2 are equal") }}```[線上運行程式](https://play.golang.org/p/T4svXOTYSg) 在上面代碼中,結構體類型 `image` 包含一個 `map` 類型的欄位。由於 `map` 類型是不可比較的,因此 `image1` 和 `image2` 也不可比較。如果運行該程式,編譯器會報錯:**`main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)`**。 [github](https://github.com/golangbot/structs) 上有本教程的原始碼。 **上一教程 - [指標](https://studygolang.com/articles/12262)****下一教程 - [方法](https://studygolang.com/articles/12264)**
via: https://golangbot.com/structs/
作者:Nick Coghlan 譯者:Noluye 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
3038 次點擊