[译] part 16: golang 结构体 structures

https://juejin.im/post/5caa15d3e51d452b510e5e61

什么是结构体

结构是用户定义的类型,表示字段集合。它可以将一组数据放到结构中,而不是将它们作为单独的类型进行维护。

例如,员工具有firstNamelastNameage。将这三个属性组合成一个结构employee是有意义的。

声明一个结构

  1. type Employee struct {
  2. firstName string
  3. lastName string
  4. age int
  5. }

上面的代码片段声明了一个结构类型Employee,其中包含firstNamelastNameage字段。通过在单行中声明属于同一类型的字段,后跟类型名称,可以使该结构更紧凑。在上面的结构体中,firstNamelastName属于同一个类型的字符串,因此可以将struct重写为

  1. type Employee struct {
  2. firstName, lastName string
  3. age, salary int
  4. }

上面的Employee结构称为命名结构,因为它创建了一个名为Employee的新类型。

也可以在不声明新类型的情况下声明结构,并且这些类型的结构称为匿名结构。

  1. var employee struct {
  2. firstName, lastName string
  3. age int
  4. }

上面的代码片段创建了一个匿名结构employee

创建一个命名结构

让我们使用一个简单的程序定义一个命名结构Employee

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. //creating structure using field names
  11. emp1 := Employee{
  12. firstName: "Sam",
  13. age: 25,
  14. salary: 500,
  15. lastName: "Anderson",
  16. }
  17. //creating structure without using field names
  18. emp2 := Employee{"Thomas", "Paul", 29, 800}
  19. fmt.Println("Employee 1", emp1)
  20. fmt.Println("Employee 2", emp2)
  21. }

Run in playgroud

在上面程序的第 7 行中,我们创建了一个叫Employee的结构体。在第 15 行,初始化了一个emp1结构。字段名称的顺序不必与声明结构类型时的顺序相同。这里我们更改了lastName的位置并将其移到了最后。这将没有任何问题。

在第 23 行,通过省略字段名来初始化emp2。在这种情况下,必须保持字段的顺序与结构声明中指定的相同。 输出,

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

创建匿名结构体

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. emp3 := struct {
  7. firstName, lastName string
  8. age, salary int
  9. }{
  10. firstName: "Andreah",
  11. lastName: "Nikola",
  12. age: 31,
  13. salary: 5000,
  14. }
  15. fmt.Println("Employee 3", emp3)
  16. }

Run in playgroud

在上述程序的第 8 行,定义了匿名结构变量emp3。正如我们已经提到的,这被称为匿名结构,因为它只创建一个新的struct变量emp3,并没有定义任何新的结构类型。 该程序输出,

  1. Employee 3 {Andreah Nikola 31 5000}

结构体的零值

定义结构并且未使用任何值显式初始化它时,默认情况下会为结构的字段分配其零值。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. var emp4 Employee //zero valued structure
  11. fmt.Println("Employee 4", emp4)
  12. }

Run in playgroud

上面的程序定义了emp4,但它没有用任何值初始化。因此,firstNamelastName被赋予字符串的零值,即””,而agesalary被赋予int的零值,即 0。此程序输出\

  1. Employee 4 { 0 0}

也可以为某些字段指定值而忽略其余字段。在这种情况下,忽略的字段将被赋予零值。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. emp5 := Employee{
  11. firstName: "John",
  12. lastName: "Paul",
  13. }
  14. fmt.Println("Employee 5", emp5)
  15. }

Run in playgroud 在上面的第 14 和 15 行,firstNamelastName被初始化,而agesalary则没有。因此,agesalary被赋予 0。该程序输出,

  1. Employee 5 {John Paul 0 0}

访问结构的字段

.操作符用来访问结构的字段

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. emp6 := Employee{"Sam", "Anderson", 55, 6000}
  11. fmt.Println("First Name:", emp6.firstName)
  12. fmt.Println("Last Name:", emp6.lastName)
  13. fmt.Println("Age:", emp6.age)
  14. fmt.Printf("Salary: $%d", emp6.salary)
  15. }

Run in playgroud

上面程序中的emp6.firstName访问emp6结构的firstName字段。该程序输出,

  1. First Name: Sam
  2. Last Name: Anderson
  3. Age: 55
  4. Salary: $6000

也可以创建零结构,然后稍后为其字段分配值。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. var emp7 Employee
  11. emp7.firstName = "Jack"
  12. emp7.lastName = "Adams"
  13. fmt.Println("Employee 7:", emp7)
  14. }

Run in playgroud

在上面的程序中,定义了emp7,然后为firstNamelastName赋值。该程序输出,

  1. Employee 7: {Jack Adams 0 0}

指向结构的指针

可以创建一个指向结构的指针。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. emp8 := &Employee{"Sam", "Anderson", 55, 6000}
  11. fmt.Println("First Name:", (*emp8).firstName)
  12. fmt.Println("Age:", (*emp8).age)
  13. }

Run in playgroud

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

  1. First Name: Sam
  2. Age: 55

Go 语言可以使用emp8.firstName来访问firstName字段,从而可以不用通过显式解除引用(* emp8).firstName来访问。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. firstName, lastName string
  7. age, salary int
  8. }
  9. func main() {
  10. emp8 := &Employee{"Sam", "Anderson", 55, 6000}
  11. fmt.Println("First Name:", emp8.firstName)
  12. fmt.Println("Age:", emp8.age)
  13. }

Run in playgroud

我们使用emp8.firstName来访问上面程序中的firstName字段,这个程序同样输出,

  1. First Name: Sam
  2. Age: 55

匿名字段

可以使用包含不带字段名称去创建结构。这些字段称为匿名字段。

下面的代码片段创建了一个结构Person,它有两个匿名字段stringint

  1. type Person struct {
  2. string
  3. int
  4. }

让我们使用匿名字段写一段代码,

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Person struct {
  6. string
  7. int
  8. }
  9. func main() {
  10. p := Person{"Naveen", 50}
  11. fmt.Println(p)
  12. }

Run in playgroud

在上面的程序中,Person是一个带有两个匿名字段的结构。 p := Person {“Naveen”,50}定义了Person的变量。该程序输出{Naveen 50}

匿名字段没有名称,默认情况下,匿名字段的名称也是其类型的名称。例如,在上面的Person结构的情况下,虽然字段是匿名的,但默认情况下它们采用字段类型的名称。所以Person结构有 2 个字段,名称为stringint

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Person struct {
  6. string
  7. int
  8. }
  9. func main() {
  10. var p1 Person
  11. p1.string = "naveen"
  12. p1.int = 50
  13. fmt.Println(p1)
  14. }

Run in playgroud

在上面程序的第 14 和 15 行中,我们使用它们的类型stringint作为字段名称来访问Person结构的匿名字段。上述程序的输出是,

  1. {naveen 50}

嵌套结构

如果结构包含一个struct类型的字段那称该结构为嵌套结构。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Address struct {
  6. city, state string
  7. }
  8. type Person struct {
  9. name string
  10. age int
  11. address Address
  12. }
  13. func main() {
  14. var p Person
  15. p.name = "Naveen"
  16. p.age = 50
  17. p.address = Address {
  18. city: "Chicago",
  19. state: "Illinois",
  20. }
  21. fmt.Println("Name:", p.name)
  22. fmt.Println("Age:",p.age)
  23. fmt.Println("City:",p.address.city)
  24. fmt.Println("State:",p.address.state)
  25. }

Run in playgroud

上述程序中的Person结构有一个字段address,而该字段又是一个结构。将输出,

  1. Name: Naveen
  2. Age: 50
  3. City: Chicago
  4. State: Illinois

提升字段

一个结构中的匿名结构的字段称为提升字段,因为它们可以被访问就像它们属于原结构一样。这个定义有点复杂,所以让我们直接用

  1. type Address struct {
  2. city, state string
  3. }
  4. type Person struct {
  5. name string
  6. age int
  7. Address
  8. }

在上面的代码片段中,Person结构有一个匿名字段Address,它是一个结构。现在,Address结构的字段即citystate被称为提升字段,因为它们可以和Person结构声明的字段一样被访问。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Address struct {
  6. city, state string
  7. }
  8. type Person struct {
  9. name string
  10. age int
  11. Address
  12. }
  13. func main() {
  14. var p Person
  15. p.name = "Naveen"
  16. p.age = 50
  17. p.Address = Address{
  18. city: "Chicago",
  19. state: "Illinois",
  20. }
  21. fmt.Println("Name:", p.name)
  22. fmt.Println("Age:", p.age)
  23. fmt.Println("City:", p.city) //city is promoted field
  24. fmt.Println("State:", p.state) //state is promoted field
  25. }

Run in playgroud

在上述程序的第 26 和 27 行中,p访问提升字段citystate,就好像它们是在结构p本身中被声明了,然后使用语法p.cityp.state访问。该程序输出,

  1. Name: Naveen
  2. Age: 50
  3. City: Chicago
  4. State: Illinois

导出结构和字段

如果结构类型以大写字母开头,则它是导出类型,可以从其他包访问它。类似地,如果结构的字段以大写字母开头,则也可以从其他包中访问它们。

让我们编写一个包含自定义包的程序,以便更好地理解这一点。

在 go 工作区的 src 目录中创建一个名为 structs 的文件夹。在 structs 内创建另一个目录 computer。

进入 computer 目录,用 spec.go 为名称保存该代码。

  1. package computer
  2. type Spec struct { //exported struct
  3. Maker string //exported field
  4. model string //unexported field
  5. Price int //exported field
  6. }

上面的代码片段创建了一个包computer,其中包含一个导出的结构类型Spec,它包含两个导出字段MakerPrice以及一个非导出的字段model。让我们从main package导入这个包并使用Spec结构。

在 structs 目录中创建一个名为 main.go 的文件,并在 main.go 中编写以下程序

  1. package main
  2. import "structs/computer"
  3. import "fmt"
  4. func main() {
  5. var spec computer.Spec
  6. spec.Maker = "apple"
  7. spec.Price = 50000
  8. fmt.Println("Spec:", spec)
  9. }

包结构应该如下所示,

  1. src
  2. structs
  3. computer
  4. spec.go
  5. main.go

在上面程序的第 3 行中,我们导入了 computer 包。在第 8 和 9 行中,我们访问了Spec结构的两个导出字段MakerPrice Spec的价格。可以通过执行命令go install structs,然后再执行workspacepath/bin/structs来运行该程序

该程序将输出,Spec: {apple 50000}

如果我们尝试访问非导出的字段model,编译器会报错。我们用以下代码替换上述 main.go 的内容。

  1. package main
  2. import "structs/computer"
  3. import "fmt"
  4. func main() {
  5. var spec computer.Spec
  6. spec.Maker = "apple"
  7. spec.Price = 50000
  8. spec.model = "Mac Mini"
  9. fmt.Println("Spec:", spec)
  10. }

在上述程序的第 10 行中,我们尝试访问非导出的字段model。运行此程序将导致编译错误spec.model undefined (cannot refer to unexported field or method model)

结构的相等性

结构是值类型,如果它们的每个字段都是可比较的,则可比较。两个结构变量的相应字段相等,则认为它们是相等的。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type name struct {
  6. firstName string
  7. lastName string
  8. }
  9. func main() {
  10. name1 := name{"Steve", "Jobs"}
  11. name2 := name{"Steve", "Jobs"}
  12. if name1 == name2 {
  13. fmt.Println("name1 and name2 are equal")
  14. } else {
  15. fmt.Println("name1 and name2 are not equal")
  16. }
  17. name3 := name{firstName:"Steve", lastName:"Jobs"}
  18. name4 := name{}
  19. name4.firstName = "Steve"
  20. if name3 == name4 {
  21. fmt.Println("name3 and name4 are equal")
  22. } else {
  23. fmt.Println("name3 and name4 are not equal")
  24. }
  25. }

Run in playgroud 在上面的程序中,name结构包含两个字符串字段。由于字符串具有可比性,因此可以通过name结构的两个变量去比较。

在上面的程序中,name1 和 name2 相等,而 name3 和 name4 则不相等。该程序将输出,

  1. name1 and name2 are equal
  2. name3 and name4 are not equal

如果结构变量包含无法比较的字段,则该结构不具有可比性。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type image struct {
  6. data map[int]int
  7. }
  8. func main() {
  9. image1 := image{data: map[int]int{
  10. 0: 155,
  11. }}
  12. image2 := image{data: map[int]int{
  13. 0: 155,
  14. }}
  15. if image1 == image2 {
  16. fmt.Println("image1 and image2 are equal")
  17. }
  18. }

Run in playgroud

在上面的程序中,image结构类型包含一个map类型的字段data。由于map不具有可比性,因此无法比较image1image2。如果运行此程序,编译将失败,错误为main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared).

ft_authoradmin  ft_create_time2019-08-03 16:35
 ft_update_time2019-08-03 16:36