Go结构体声明结构体基本用法作用域总结

Hi,我是行舟,今天和大家一起学习Go语言的结构体。

结构体是一种复杂数据类型,是Go语言面向对象编程的重要组成部分。

声明结构体

type animal struct {
    name string
    age int
    class string
    weight float32
}
复制代码

如上示例,type关键字定义了一个animal类型,animal类型后面的struct表明这是一个结构体,它有name、age、class、weight四个字段,每个字段后面定义了其对应的类型。

type animal struct {
   name,class string
   age int
   weight float32
}
复制代码

如上示例,我们可以把相同类型的字段放在一行用逗号分割,但这种写法可读性不好,并不提倡。

匿名字段

type person struct {
   string
   int
}
复制代码

我们在定义结构体字段的时候,可以只有类型没有名称,这时代表名称和类型相同,所以上面的写法就等价于:

type person struct {
   string string
   int int
}
复制代码

基本用法

初始化

type animal struct {
   name string
   age int
   class string
   weight float32
}

func main(){
   var a1 animal // 初始化,各字段对应默认的零值
   var a2 = animal{"旺财",2,"狗狗",12.8// 初始化,并按字段顺序赋值
   var a3 = animal{name:"Tom",age:3,weight:11.5,class:"猫"// 初始化,并显示给所有字段赋值
   var a4 = animal{name:"小黑",age:5// 初始化,并显示给部分字段复制,未被赋值的字段为其类型对应的零值
a5 := struct// 匿名结构体,定义并初始化
   name string
   height float32
}{
   name: "三毛",
   height:1.85,
}

   fmt.Printf("a1=%+v \n",a1) // print a1={name: age:0 class: weight:0} 
   fmt.Printf("a2=%+v \n",a2) // print a2={name:旺财 age:2 class:狗狗 weight:12.8} 
   fmt.Printf("a3=%+v \n",a3) // print a3={name:Tom age:3 class:猫 weight:11.5} 
   fmt.Printf("a4=%+v \n",a4) // print a4={name:小黑 age:5 class: weight:0} 
   fmt.Printf("a5=%+v \n",a5) // print a5={name:三毛 height:1.85} 
}
复制代码

如上示例中,a1a2a3a4分别展示了四种初始化结构体的方法及各自的打印结构。

对于a1当结构体某个字段没有被赋值时,其默认值是该字段对应类型的零值;对于a2,在没有显示指定字段时,赋值的顺序需要和字段顺序保持一致;a5和前面四个都不太一样,它声明了一个没有名称的结构体,并完成了初始化,我们称这种没有名称的结构体为匿名结构体。

修改值

我们可以通过.号获取一个结构体对象的字段值,也可以修改字段值。如下示例:

type animal struct {
   name string
   age int
   class string
   weight float32
}

func main()  {
   a1 := animal{name:"Tom",age:3,weight:11.5,class:"猫"}
   fmt.Printf("a1.age=%d \n",a1.age) // print a1.age=3

   a1.age = 5
   fmt.Printf("a1.age=%d \n",a1.age) // print a1.age=5
}
复制代码

初始化为指针类型

type animal struct {
   name string
   age int
   class string
   weight float32
}

func main()  {
   a1 := &animal{name:"Tom",age:3,weight:11.5,class:"猫"}
   fmt.Printf("a1.name=%s \n",a1.name) // print a1.name=Tom
   fmt.Printf("a1.name=%s \n",(*a1).name) // print a1.name=Tom
}
复制代码

如上示例a1赋值为animal结构体的指针类型。a1.name(*a1).name的值相同,是因为Go语言帮我们做了默认类型转换,Go语言发现a1是指针类型,自动帮我们转换为指针值,所以我们可以通过a1.name获取正确的结果。

Go语言中,使用&符号获取地址,*符号获取指针指。

结构体嵌套

type animalName struct {
   firstName string
   lastName string
}

type animal struct {
   animalName
   age int
   class string
   weight float32
}

func main()  {
   a1 := animal{
      animalName: animalName{
         firstName:"tom",
         lastName:"steven",
      },
      age: 5,
      class:"猫",
      weight:12.5,
   }

   fmt.Printf("a1= %+v \n", a1) // print a1= {animalName:{firstName:tom lastName:steven} age:5 class:猫 weight:12.5}
   fmt.Printf("a1.firstName= %+v \n", a1.firstName) // print a1.firstName= tom
   fmt.Printf("a1.lastName= %+v \n", a1.lastName) // print a1.lastName= steven
}
复制代码

如上示例,我们声明的animal结构体中嵌套了animalName结构体。 a1.firstNamea1.lastName打印的结构是animalName结构体的字段值。

这是嵌套结构体的特性,当结构体本身字段不存在时,会往被嵌套结构体的“深层”寻找。Go语言由浅入深 逐层查找,找到了对应的字段就返回其值,并停止查找。

当同一层的两个嵌套结构体有相同字段名称时,会报错,因为此时Go语言不知道该访问哪个结构体的字段。如下示例:

type animalName struct {
   firstName string
   lastName string
}

// 动物
type animal struct {
   animalName
   age
   class string
   weight float32
}

type age struct {
   firstName string
   lastName string
}

func main()  {
   a1 := animal{
      animalName: animalName{
         firstName:"tom",
         lastName:"steven",
      },
      age: age{
         firstName:"age-tom",
         lastName:"age-steven",
      },
      class:"猫",
      weight:12.5,
   }

   fmt.Printf("a1= %+v \n", a1) // print a1= {animalName:{firstName:tom lastName:steven} age:5 class:猫 weight:12.5}
   fmt.Printf("a1.firstName= %+v \n", a1.firstName) // print a1.firstName= tom
}

报错:Ambiguous reference 'firstName' 
复制代码

我们定义了animal结构体,它嵌套的animalName和age结构体都有firstName和lastName字段。执行a1.firstName会报错,因为在第二层嵌套的结构体中找到了两个firstName字段,Go语言不知道该返回哪一个。这时如果需要获取某个被嵌套结构体的值需要明确调用路径,如下示例:

fmt.Printf("a1.animalName.firstName= %+v \n", a1.animalName.firstName) // print a1.animalName.firstName= tom
fmt.Printf("a1.age.firstName= %+v \n", a1.age.firstName) // print a1.age.firstName= age-tom 
复制代码

前面说到获取字段时会逐层查找,所以不在一个层级上的字段名称重复时,访问不会报错,但是大家也要清楚被访问的优先级,当内层结构体的字段需要被访问时,最好严格书写“调用路径”。

判等操作

结构体是值类型。如果两个结构体的每个字段都可以比较,则结构体可以比较,反之只要有一个字段不可以比较这个结构体就不可以比较。可以比较时只有当所有字段对应的值都相同时,两个结构体 才相等。看下面两个例子:

type animal struct {
   name string
   age int
   class string
   weight float32
}

func main()  {
   a1 := animal{
      name: "tom",
      age: 5,
      class:"猫",
      weight:12.5,
   }
   a2 := animal{
      name: "tom",
      age: 5,
      class:"猫",
      weight:12.5,
   }

   fmt.Printf("a1和a2相等%+v \n", a1 == a2) // print a1和a2相等true
}
复制代码

type person struct {
   character map[string]string
}

func main()  {
   
a3 := person{
   character: map[string]string{
      "xinqing":"gaoxing",
   },
}
a4 := person{
   character: map[string]string{
      "xinqing":"gaoxing",
   },
}
   fmt.Printf("a3和a4相等 %+v \n", a3 == a4) // 错误:Invalid operation: a3 == a4 (operator == not defined on person) 
}
复制代码

因为animal的每个字段都可以比较,所以a1和a2可以比较;person的字段character是map类型,不可以比较,所以产生编译错误Invalid operation: a3 == a4 (operator == not defined on person)

空结构体

一个结构体类型也可以不包含任何字段,没有任何字段的结构体也可以有意义,那就是该结构体类型可以拥有方法。如下示例:

type animal struct {
}

func (a animal) toString(){
   fmt.Printf("I am animal!"// print I am animal!
}
func main()  {
   a := animal{}
   a.toString()
}
复制代码

关于方法的更多内容,我们在下一篇文章深入了解。

作用域

当结构体第一个字母大写时,结构体可以被跨包访问。对于结构体的字段也同样如此,当字段第一个字母大写时,字段可以被跨包访问,小写时只能在包内可以访问。如下示例:

type Animal struct {
   Name string
   Age int
   class string
   weight float32
}
复制代码

上面的Animal本身是可以跨包引用的,它的Name和Age字段也可以在别的包中访问,但是calss和weight字段是不可以的。

总结

本文我们主要介绍了,结构体的声明方式、基本用法和作用域。在实际编程中,结构体是我们实现面向对象编程的重要部分。

诚邀关注公众号:一行舟
每周更新技术文章,和大家一起进步。