[譯] part 16: golang 結構體structures

咔嘰咔嘰發表於2019-04-07

什麼是結構體

結構是使用者定義的型別,表示欄位集合。它可以將一組資料放到結構中,而不是將它們作為單獨的型別進行維護。

例如,員工具有firstNamelastNameage。將這三個屬性組合成一個結構employee是有意義的。

宣告一個結構

type Employee struct {  
    firstName string
    lastName  string
    age       int
}
複製程式碼

上面的程式碼片段宣告瞭一個結構型別Employee,其中包含firstNamelastNameage欄位。通過在單行中宣告屬於同一型別的欄位,後跟型別名稱,可以使該結構更緊湊。在上面的結構體中,firstNamelastName屬於同一個型別的字串,因此可以將struct重寫為

type Employee struct {  
    firstName, lastName string
    age, salary         int
}
複製程式碼

上面的Employee結構稱為命名結構,因為它建立了一個名為Employee的新型別。

也可以在不宣告新型別的情況下宣告結構,並且這些型別的結構稱為匿名結構。

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)
}
複製程式碼

Run in playgroud

在上面程式的第 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)
}
複製程式碼

Run in playgroud

在上述程式的第 8 行,定義了匿名結構變數emp3。正如我們已經提到的,這被稱為匿名結構,因為它只建立一個新的struct變數emp3,並沒有定義任何新的結構型別。 該程式輸出,

Employee 3 {Andreah Nikola 31 5000}  
複製程式碼

結構體的零值

定義結構並且未使用任何值顯式初始化它時,預設情況下會為結構的欄位分配其零值。

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)
}
複製程式碼

Run in playgroud

上面的程式定義了emp4,但它沒有用任何值初始化。因此,firstNamelastName被賦予字串的零值,即"",而agesalary被賦予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)
}
複製程式碼

Run in playgroud 在上面的第 14 和 15 行,firstNamelastName被初始化,而agesalary則沒有。因此,agesalary被賦予 0。該程式輸出,

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)
}
複製程式碼

Run in playgroud

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

First Name: Sam  
Last Name: Anderson  
Age: 55  
Salary: $6000  
複製程式碼

也可以建立零結構,然後稍後為其欄位分配值。

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)
}
複製程式碼

Run in playgroud

在上面的程式中,定義了emp7,然後為firstNamelastName賦值。該程式輸出,

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)
}
複製程式碼

Run in playgroud

上面程式中的emp8是指向Employee結構的指標。(* emp8).firstName是訪問firstName欄位的語法。該程式輸出,

First Name: Sam  
Age: 55  
複製程式碼

Go 語言可以使用emp8.firstName來訪問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)
}
複製程式碼

Run in playgroud

我們使用emp8.firstName來訪問上面程式中的firstName欄位,這個程式同樣輸出,

First Name: Sam  
Age: 55  
複製程式碼

匿名欄位

可以使用包含不帶欄位名稱去建立結構。這些欄位稱為匿名欄位。

下面的程式碼片段建立了一個結構Person,它有兩個匿名欄位stringint

type Person struct {  
    string
    int
}
複製程式碼

讓我們使用匿名欄位寫一段程式碼,

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

func main() {  
    p := Person{"Naveen", 50}
    fmt.Println(p)
}
複製程式碼

Run in playgroud

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

匿名欄位沒有名稱,預設情況下,匿名欄位的名稱也是其型別的名稱。例如,在上面的Person結構的情況下,雖然欄位是匿名的,但預設情況下它們採用欄位型別的名稱。所以Person結構有 2 個欄位,名稱為stringint

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

func main() {  
    var p1 Person
    p1.string = "naveen"
    p1.int = 50
    fmt.Println(p1)
}
複製程式碼

Run in playgroud

在上面程式的第 14 和 15 行中,我們使用它們的型別stringint作為欄位名稱來訪問Person結構的匿名欄位。上述程式的輸出是,

{naveen 50}
複製程式碼

巢狀結構

如果結構包含一個struct型別的欄位那稱該結構為巢狀結構。

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)
}
複製程式碼

Run in playgroud

上述程式中的Person結構有一個欄位address,而該欄位又是一個結構。將輸出,

Name: Naveen  
Age: 50  
City: Chicago  
State: Illinois  
複製程式碼

提升欄位

一個結構中的匿名結構的欄位稱為提升欄位,因為它們可以被訪問就像它們屬於原結構一樣。這個定義有點複雜,所以讓我們直接用

type Address struct {  
    city, state string
}
type Person struct {  
    name string
    age  int
    Address
}
複製程式碼

在上面的程式碼片段中,Person結構有一個匿名欄位Address,它是一個結構。現在,Address結構的欄位即citystate被稱為提升欄位,因為它們可以和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
}
複製程式碼

Run in playgroud

在上述程式的第 26 和 27 行中,p訪問提升欄位citystate,就好像它們是在結構p本身中被宣告瞭,然後使用語法p.cityp.state訪問。該程式輸出,

Name: Naveen  
Age: 50  
City: Chicago  
State: Illinois 
複製程式碼

匯出結構和欄位

如果結構型別以大寫字母開頭,則它是匯出型別,可以從其他包訪問它。類似地,如果結構的欄位以大寫字母開頭,則也可以從其他包中訪問它們。

讓我們編寫一個包含自定義包的程式,以便更好地理解這一點。

在 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,其中包含一個匯出的結構型別Spec,它包含兩個匯出欄位MakerPrice以及一個非匯出的欄位model。讓我們從main package匯入這個包並使用Spec結構。

在 structs 目錄中建立一個名為 main.go 的檔案,並在 main.go 中編寫以下程式

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結構的兩個匯出欄位MakerPrice Spec的價格。可以通過執行命令go install structs,然後再執行workspacepath/bin/structs來執行該程式

該程式將輸出,Spec: {apple 50000}

如果我們嘗試訪問非匯出的欄位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)

結構的相等性

結構是值型別,如果它們的每個欄位都是可比較的,則可比較。兩個結構變數的相應欄位相等,則認為它們是相等的。

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")
    }
}
複製程式碼

Run in playgroud 在上面的程式中,name結構包含兩個字串欄位。由於字串具有可比性,因此可以通過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")
    }
}
複製程式碼

Run in playgroud

在上面的程式中,image結構型別包含一個map型別的欄位data。由於map不具有可比性,因此無法比較image1image2。如果執行此程式,編譯將失敗,錯誤為main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared).

相關文章