1. 程式人生 > >Golang教程16節--結構體

Golang教程16節--結構體

什麼是結構體?

結構體是使用者定義的型別,表示若干個欄位(Field)的集合。有時應該把資料整合在一起,而不是讓這些資料沒有聯絡。這種情況下可以使用結構體。

例如,一個職員有 firstNamelastName 和 age 三個屬性,而把這些屬性組合在一個結構體 employee 中就很合理。

結構體的宣告

type Employee struct {
    firstName string
    lastName  string
    age       int
}

在上面的程式碼片段裡,聲明瞭一個結構體型別 Employee,它有 firstNamelastName 和 age

 三個欄位。通過把相同型別的欄位宣告在同一行,結構體可以變得更加緊湊。在上面的結構體中,firstName 和 lastName 屬於相同的 string 型別,於是這個結構體可以重寫為:

type Employee struct {
    firstName, lastName string
    age, salary         int
}

上面的結構體 Employee 稱為 命名的結構體(Named Structure)。我們建立了名為 Employee 的新型別,而它可以用於建立 Employee 型別的結構體變數。

宣告結構體時也可以不用宣告一個新型別,這樣的結構體型別稱為 匿名結構體(Anonymous Structure)

var employee struct {
    firstName, lastName string
    age int
}

上述程式碼片段建立一個匿名結構體 employee

建立命名的結構體

通過下面程式碼,我們定義了一個命名的結構體 Employee

package main

import (  
    "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)
}

在上述程式的第 7 行,我們建立了一個命名的結構體 Employee。而在第 15 行,通過指定每個欄位名的值,我們定義了結構體變數 emp1。欄位名的順序不一定要與宣告結構體型別時的順序相同。在這裡,我們改變了 lastName 的位置,將其移到了末尾。這樣做也不會有任何的問題。

在上面程式的第 23 行,定義 emp2 時我們省略了欄位名。在這種情況下,就需要保證欄位名的順序與宣告結構體時的順序相同。

該程式將輸出:

Employee 1 {Sam Anderson 25 500}
Employee 2 {Thomas Paul 29 800}

建立匿名結構體

package main

import (
    "fmt"
)

func main() {
    emp3 := struct {
        firstName, lastName string
        age, salary         int
    }{
        firstName: "Andreah",
        lastName:  "Nikola",
        age:       31,
        salary:    5000,
    }

    fmt.Println("Employee 3", emp3)
}

在上述程式的第 3 行,我們定義了一個匿名結構體變數 emp3。上面我們已經提到,之所以稱這種結構體是匿名的,是因為它只是建立一個新的結構體變數 em3,而沒有定義任何結構體型別。

該程式會輸出:

Employee 3 {Andreah Nikola 31 5000}

結構體的零值(Zero Value)

當定義好的結構體並沒有被顯式地初始化時,該結構體的欄位將預設賦為零值。

package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    var emp4 Employee //zero valued structure
    fmt.Println("Employee 4", emp4)
}

該程式定義了 emp4,卻沒有初始化任何值。因此 firstName 和 lastName 賦值為 string 的零值("")。而 age 和 salary 賦值為 int 的零值(0)。該程式會輸出:

Employee 4 { 0 0}

當然還可以為某些欄位指定初始值,而忽略其他欄位。這樣,忽略的欄位名會賦值為零值。

package main

import (  
    "fmt"
)

type Employee struct {  
    firstName, lastName string
    age, salary         int
}

func main() {  
    emp5 := Employee{
        firstName: "John",
        lastName:  "Paul",
    }
    fmt.Println("Employee 5", emp5)
}

在上面程式中的第 14 行和第 15 行,我們初始化了 firstName 和 lastName,而 age 和 salary 沒有進行初始化。因此 age 和 salary 賦值為零值。該程式會輸出:

Employee 5 {John Paul 0 0}

訪問結構體的欄位

點號操作符 . 用於訪問結構體的欄位。

package main

import (  
    "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)
}

上面程式中的 emp6.firstName 訪問了結構體 emp6 的欄位 firstName。該程式輸出:

First Name: Sam  
Last Name: Anderson  
Age: 55  
Salary: $6000

還可以建立零值的 struct,以後再給各個欄位賦值。

package main

import (
    "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)
}

在上面程式中,我們定義了 emp7,接著給 firstName 和 lastName 賦值。該程式會輸出:

Employee 7: {Jack Adams 0 0}

結構體的指標

還可以建立指向結構體的指標。

package main

import (  
    "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)
}

在上面程式中,emp8 是一個指向結構體 Employee 的指標。(*emp8).firstName 表示訪問結構體 emp8 的 firstName 欄位。該程式會輸出:

First Name: Sam
Age: 55

Go 語言允許我們在訪問 firstName 欄位時,可以使用 emp8.firstName 來代替顯式的解引用 (*emp8).firstName

package main

import (  
    "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)
}

在上面的程式中,我們使用 emp8.firstName 來訪問 firstName 欄位,該程式會輸出:

First Name: Sam
Age: 55

匿名欄位

當我們建立結構體時,欄位可以只有型別,而沒有欄位名。這樣的欄位稱為匿名欄位(Anonymous Field)。

以下程式碼建立一個 Person 結構體,它含有兩個匿名欄位 string 和 int

type Person struct {  
    string
    int
}

我們接下來使用匿名欄位來編寫一個程式。

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

func main() {  
    p := Person{"Naveen", 50}
    fmt.Println(p)
}

在上面的程式中,結構體 Person 有兩個匿名欄位。p := Person{"Naveen", 50} 定義了一個 Person 型別的變數。該程式輸出 {Naveen 50}

雖然匿名欄位沒有名稱,但其實匿名欄位的名稱就預設為它的型別比如在上面的 Person 結構體裡,雖說欄位是匿名的,但 Go 預設這些欄位名是它們各自的型別。所以 Person 結構體有兩個名為 string 和 int 的欄位。

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

func main() {  
    var p1 Person
    p1.string = "naveen"
    p1.int = 50
    fmt.Println(p1)
}

在上面程式的第 14 行和第 15 行,我們訪問了 Person 結構體的匿名欄位,我們把欄位型別作為欄位名,分別為 "string" 和 "int"。上面程式的輸出如下:

{naveen 50}

巢狀結構體(Nested Structs)

結構體的欄位有可能也是一個結構體。這樣的結構體稱為巢狀結構體。

package main

import (  
    "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)
}

上面的結構體 Person 有一個欄位 address,而 address 也是結構體。該程式輸出:

Name: Naveen  
Age: 50  
City: Chicago  
State: Illinois

提升欄位(Promoted Fields)

如果是結構體中有匿名的結構體型別欄位,則該匿名結構體裡的欄位就稱為提升欄位。這是因為提升欄位就像是屬於外部結構體一樣,可以用外部結構體直接訪問。我知道這種定義很複雜,所以我們直接研究下程式碼來理解吧。

type Address struct {  
    city, state string
}
type Person struct {  
    name string
    age  int
    Address
}

在上面的程式碼片段中,Person 結構體有一個匿名欄位 Address,而 Address 是一個結構體。現在結構體 Address 有 city 和 state 兩個欄位,訪問這兩個欄位就像在 Person 裡直接宣告的一樣,因此我們稱之為提升欄位

package main

import (
    "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
}

在上面程式碼中的第 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 的檔案中儲存下面的程式。

package computer

type Spec struct { //exported struct  
    Maker string //exported field
    model string //unexported field
    Price int //exported field
}

上面的程式碼片段中,建立了一個 computer 包,裡面有一個匯出結構體型別 SpecSpec 有兩個匯出欄位 Maker 和 Price,和一個未匯出的欄位 model。接下來我們會在 main 包中匯入這個包,並使用 Spec 結構體。

package main

import "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 的內容替換為下面的程式碼。

package main

import "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)

結構體是值型別。如果它的每一個欄位都是可比較的,則該結構體也是可比較的。如果兩個結構體變數的對應欄位相等,則這兩個變數也是相等的

package main

import (  
    "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")
    }
}

在上面的程式碼中,結構體型別 name 包含兩個 string 型別。由於字串是可比較的,因此可以比較兩個 name 型別的結構體變數。

上面程式碼中 name1 和 name2 相等,而 name3 和 name4 不相等。該程式會輸出:

name1 and name2 are equal  
name3 and name4 are not equal

如果結構體包含不可比較的欄位,則結構體變數也不可比較。

package main

import (  
    "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")
    }
}

在上面程式碼中,結構體型別 image 包含一個 map 型別的欄位。由於 map 型別是不可比較的,因此 image1 和 image2 也不可比較。如果執行該程式,編譯器會報錯:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)

github 上有本教程的原始碼

本節結束。祝您愉快!