结构体类型struct

  这一章我们来重点讲解一下Go的重要数据类型——结构体类型struct。一个结构体就是一个命名的元素序列,每个元素又叫做字段,每个字段都有一个类型和名字。从面向对象的角度看,结构体类型中的字段代表了该类型的属性,而与这些字段相关的方法,则可以看作针对这些属性的操作。

结构体定义

  Go语言的结构体定义和C是非常类似的。以type开始,后面跟着结构体的名字、struct{字段名称 类型}。下面是通常的例子:

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

  Go语言还支持只提供类型,而不写字段名的方式,也就是匿名字段,或称为嵌入字段。当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个结构体类型。

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. type Staff struct {
  6. Person //匿名字段
  7. Phone int
  8. Address string
  9. }

不仅仅是struct字段,所有内置类型和自定义类型都可以作为匿名字段,比如string。pro03_7_1.go

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. type Staff struct {
  6. Person //匿名字段
  7. Phone string
  8. Address string
  9. string //string作为匿名字段,当然也可以进一步使用切片slice []string
  10. }
  11. func main() {
  12. staff := Staff{Person{"Sunny", 41}, "13901234567", "Peking", "5 star staff"}
  13. fmt.Println(staff.string)
  14. fmt.Println(staff.Name)
  15. }

需要注意的

1)Go语言中没有public, protected, private的关键字,所以,如果你想让一个结构体可以被别的包访问的话,你需要把这个结构体的第一个字母大写。这是一种约定。

2)staff访问Person属性age和name的时候,就像访问自己所拥有的字段一样。staff.Person.Name和staff.Name都是正确的。

不过上面的第2条注意事项有产生了新的问题,就是如果在结构体里面有重名会怎样?重名也分两种情况:pro03_7_2.go

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. type Staff struct {
  6. Person //匿名字段
  7. Phone string
  8. Address string
  9. Name string //重名覆盖
  10. }
  11. func main() {
  12. staff := Staff{Person{"Sunny", 41}, "13901234567", "Peking", "Knight"}
  13. fmt.Println(staff.Name, staff.Person.Name)
  14. }

显示结果:Knight Sunny

我们上面说了,staff访问Person属性age和name的时候,就像访问自己所拥有的字段一样,不过在Staff里面也有了Name字段,如果访问的时候都是使用staff.Name,这个就像重写了字段Name,仿佛java里面的继承覆盖。不过,切记,切记,在Go中只存在嵌入而不存在继承的概念。在Go中是最外层优先的,staff.Name指向的是Staff里面的Name。

这样的重名是允许的,但是另外一种情况一定要避免,就是二义性的重名:

  1. type StructA struct {
  2. Name string
  3. }
  4. type StructB struct {
  5. Name string
  6. Age int
  7. }
  8. type StructC struct {
  9. StructA
  10. StructB
  11. }

当我们

  1. var s StructC

调用s.Name是调用的s.StructA.Name还是s.StructB.Name?

编译的时候会显示:ambiguous selector s.Name

结构体struct的赋值

在这里我着重讲一下struct的赋值,这个是GO比较有意思的一个语法点。pro03_7_3.go

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. func main() {
  6. var sunny Person
  7. sunny = Person{"sunny", 41} //最常用的赋值方式,没有写key,必须要按照定义顺序赋值,这种赋值方式不能少写参数
  8. fmt.Println(sunny)
  9. sunny = Person{Age: 42, Name: "Go"} //key value赋值,顺序都可以颠倒
  10. fmt.Println(sunny)
  11. sunny.Name = "Bill" //这个赋值方式让C程序员很习惯,不过这种赋值方式可以少写某个参数
  12. sunny.Age = 50
  13. fmt.Println(sunny)
  14. sunny.Age, sunny.Name = 10, "Peter" //这个就是具有Go语言特色的赋值方式
  15. fmt.Println(sunny)
  16. var peter Person //这里故意不给Name赋值,当然你也可以不给Age赋值。不赋值,系统会给默认值
  17. peter.Age = 50
  18. fmt.Println(peter)
  19. }

结构体struct我就先讲到这里,因为struct的重点就是方法,这个我在后面会讲到。结构体的定义加上方法,就很类似于java的Class了。我要在这里再次强调切记,切记,在Go中只存在嵌入而不存在继承的概念。

除此之外我还要强调一点, Go是不支持泛型的! 很多java程序员,看到struct以后就会想到如何泛型,这个是大忌。

还有我在这里提一件事情,如果你写了struct,其实默认你应该实现一个String方法,这个方法类似于java里面的toString,这个方法是为了可阅读性输出使用的。我会在方法里面仔细讲解。