Part 16:结构体

欢迎来到Go系列教程的第 16 部分

什么是结构体?

结构体是用户定义的类型,它代表一个字段的集合。它可以在有意义的地方被使用,将一组数据放到单一个单元中而不是维护每一个独立的类型。

例如雇员有一个名字(firstName),姓(lastName),年龄(age)。将这三个属性组合到一个结构体 employee 是有意义的。

声明结构体

type Employee struct {  
    firstName string
    lastName  string
    age       int
}

上面声明结构体类型 Employee片断,有一个 firstName 字段,lastName 字段, age 字段。这个结体体也可以更紧凑,通过在一行声明同一类型的字段,后面是类型名字。在上面结构 firstNamelastName 属于相同类型 string,因此这个结构可以被重写为

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

上面 Employee 结构被称为 命名结构体,因为它创建了一个名为 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)
}

上面程序的第 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)
}

在程序的第 8 行,一个匿名结构体变量emp3被定义。正如我们已经提到的,这个结构体被称为匿名结构体,因为它仅创建一个新的结构变量 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)
}

上面程序定义了 emp4 但是并未使用任何值初始化。因此 firstNamelastName 被赋值为字段串的零值,即 “”,age, salary 被赋值整型的零值 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 行,firstNamelastName 被初始化而 agesalary 没有初始化。这样,agesalary 被赋予它们的零值。这个程序的输出

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访问结构体 emp6firstName 字段。程序的输出

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)
}

上面程序定义 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)
}

上面程序中emp8是一个结构体 Employee 指针。(*emp8).firstName 是访问结构体 emp8 字段 firstName 的语法。程序的输出

First Name: Sam  
Age: 55 

语言给我们使用 emp8.firstName 选项替代明确解引用 (*emp8).firstName来访问 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

匿名字段

可以创建字段仅包含一个类型而没有字段名的结构体。这类字段被称为匿名字段。

下面是创建结构体 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)
}

上面程序中,Person 是一个含有两个匿名字段的结构体。p := Person{"Naveen",50} 字义一个类型为 Person 的变量,这个程序输出 {Naveen 50)

尽管一个匿名字段没有名字,匿名字段的默认名字是它类型的名字。例如上面例子 Person,尽管它的字段是匿名的,默认它使用字段类型的名字。所以结构体 Person 有名字为 stringint 两个字段。

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}

结构体嵌套

可以创建一个结构体包含字段为结构体的结构体,这类结构体被称为嵌套结构体

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 ,它是一个结构体。程序的输出

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
}

在 第26行和27行,提阶字段 citystate 可以使用语法 p.cityp.state被访问,正如他们自己在结构体 p 中声明一样,输出如下:

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

导出结构体和字段

如果结构体类型以大写字段开始,那它是导出的类型,它可以被其他包访问。同样的如果结构体字段以大写字段开始,他们可以被其他包访问 。

让我们写个包含自定义包的程序以更好的理解

在你go工作目录的 src 目录创建一个名为 struct 的文件夹,在目录 struct 中创建另一个目录 computer

package computer

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

上面片段创建一个 computer 包,它有一个导出的结构体类型 Spec,该结构体有两个可导出字段 MarkerPrice 和一个不可导出字段 model。让我们从 main 包中导入这个包并使用 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。这个程序可以通过命令 go install structs 后在 workspacepath/bin/structs 中执行。

程序输出 Spec: {appe 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")
    }
}

在上面程序中,结构体类型 name包含两个字段串字段,由于字符串是可比的,可以比较两个类型为 name 的结构体变量。

在上面程序的 name1name2是相等的,而 name3name4 是不相等的。程序将输出

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 的字段 data。maps 是不可比的。因此 image1image2 不可以比较。如果你运行这个程序,汇编失败,错误为main.go:18:invalid operation:image1 == image2(struct containing map[int]int cannot be compared)

这个教程的源代码可以在github获得

下一教程 - 方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值