[关闭]
@adamhand 2019-01-31T18:57:09.000000Z 字数 5213 阅读 703

golang--结构体


结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体。

结构体的声明

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

在上面的代码片段里,声明了一个结构体类型 Employee,它有 firstName、lastName 和 age 三个字段。通过把相同类型的字段声明在同一行,结构体可以变得更加紧凑。在上面的结构体中,firstName 和 lastName 属于相同的 string 类型,于是这个结构体可以重写为:

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

上面的结构体 Employee 称为 命名的结构体(Named Structure)。我们创建了名为 Employee 的新类型,而它可以用于创建 Employee 类型的结构体变量。

声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure)。

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

而在第 15 行,通过指定每个字段名的值,我们定义了结构体变量 emp1。字段名的顺序不一定要与声明结构体类型时的顺序相同。

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

之所以称这种结构体是匿名的,是因为它只是创建一个新的结构体变量 em3,而没有定义任何结构体类型。

该程序会输出:

  1. Employee 3 {Andreah Nikola 31 5000}

结构体的零值(Zero Value)

当定义好的结构体并没有被显式地初始化时,该结构体的字段将默认赋为零值。

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

该程序定义了 emp4,却没有初始化任何值。因此 firstName 和 lastName 赋值为 string 的零值("")。而 age 和 salary 赋值为 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. 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. }

上面程序中的 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. emp8 := &Employee{"Sam", "Anderson", 55, 6000}
  11. fmt.Println("First Name:", (*emp8).firstName)
  12. fmt.Println("Age:", (*emp8).age)
  13. }

在上面程序中,emp8 是一个指向结构体 Employee 的指针。(*emp8).firstName 表示访问结构体 emp8 的 firstName 字段。该程序会输出:

  1. First Name: Sam
  2. Age: 55

Go 语言允许我们在访问 firstName 字段时,可以使用 emp8.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. }

匿名字段

当我们创建结构体时,字段可以只有类型,而没有字段名。这样的字段称为匿名字段(Anonymous Field)。

  1. 以下代码创建一个 Person 结构体,它含有两个匿名字段 string int
  2. type Person struct {
  3. string
  4. int
  5. }

虽然匿名字段没有名称,但其实匿名字段的名称就默认为它的类型。如下所示:

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

程序的输出如下:

  1. {naveen 50}

嵌套结构体(Nested Structs)

结构体的字段有可能也是一个结构体。这样的结构体称为嵌套结构体。

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

该程序输出:

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

提升字段(Promoted Fields)

如果是结构体中有匿名的结构体类型字段,则该匿名结构体里的字段就称为提升字段。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。

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

在上面的代码片段中,Person 结构体有一个匿名字段 Address,而 Address 是一个结构体。现在结构体 Address 有 city 和 state 两个字段,访问这两个字段就像在 Person 里直接声明的一样,因此我们称之为提升字段。

导出结构体和字段

如果结构体名称以大写字母开头,则它是其他包可以访问的导出类型(Exported Type)。同样,如果结构体里的字段首字母大写,它也能被其他包访问到。

  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中定义一个结构体,它是导出的,该结构体中的Marker字段和Price字段也是导出的,而model字段不是导出的。

下面从main包中访问:

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

上述程序是正确的,但是如果试图访问未导出的字段 model,则会报错。

结构体相等性(Structs Equality)

结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。如果两个结构体变量的对应字段相等,则这两个变量也是相等的。

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

该程序会输出:

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

上面的例子中,map是不可比较的,所以image也不可比较。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注