Go 结构体
发布于 2021-11-04 14:31 ,所属分类:软件编程学习资料
Hi,我是行舟,今天和大家一起学习Go语言的结构体。
结构体是一种复杂数据类型,是Go语言面向对象编程的重要组成部分。
声明结构体
typeanimalstruct{
namestring
ageint
classstring
weightfloat32
}
如上示例,type
关键字定义了一个animal类型,animal类型后面的struct
表明这是一个结构体,它有name、age、class、weight四个字段,每个字段后面定义了其对应的类型。
typeanimalstruct{
name,classstring
ageint
weightfloat32
}
如上示例,我们可以把相同类型的字段放在一行用逗号分割,但这种写法可读性不好,并不提倡。
匿名字段
typepersonstruct{
string
int
}
我们在定义结构体字段的时候,可以只有类型没有名称,这时代表名称和类型相同,所以上面的写法就等价于:
typepersonstruct{
stringstring
intint
}
基本用法
初始化
typeanimalstruct{
namestring
ageint
classstring
weightfloat32
}
funcmain(){
vara1animal//初始化,各字段对应默认的零值
vara2=animal{"旺财",2,"狗狗",12.8}//初始化,并按字段顺序赋值
vara3=animal{name:"Tom",age:3,weight:11.5,class:"猫"}//初始化,并显示给所有字段赋值
vara4=animal{name:"小黑",age:5}//初始化,并显示给部分字段复制,未被赋值的字段为其类型对应的零值
a5:=struct{//匿名结构体,定义并初始化
namestring
heightfloat32
}{
name:"三毛",
height:1.85,
}
fmt.Printf("a1=%+v\n",a1)//printa1={name:age:0class:weight:0}
fmt.Printf("a2=%+v\n",a2)//printa2={name:旺财age:2class:狗狗weight:12.8}
fmt.Printf("a3=%+v\n",a3)//printa3={name:Tomage:3class:猫weight:11.5}
fmt.Printf("a4=%+v\n",a4)//printa4={name:小黑age:5class:weight:0}
fmt.Printf("a5=%+v\n",a5)//printa5={name:三毛height:1.85}
}
如上示例中,a1
,a2
,a3
,a4
分别展示了四种初始化结构体的方法及各自的打印结构。
对于a1
当结构体某个字段没有被赋值时,其默认值是该字段对应类型的零值;对于a2
,在没有显示指定字段时,赋值的顺序需要和字段顺序保持一致;a5
和前面四个都不太一样,它声明了一个没有名称的结构体,并完成了初始化,我们称这种没有名称的结构体为匿名结构体。
修改值
我们可以通过.
号获取一个结构体对象的字段值,也可以修改字段值。如下示例:
typeanimalstruct{
namestring
ageint
classstring
weightfloat32
}
funcmain(){
a1:=animal{name:"Tom",age:3,weight:11.5,class:"猫"}
fmt.Printf("a1.age=%d\n",a1.age)//printa1.age=3
a1.age=5
fmt.Printf("a1.age=%d\n",a1.age)//printa1.age=5
}
初始化为指针类型
typeanimalstruct{
namestring
ageint
classstring
weightfloat32
}
funcmain(){
a1:=&animal{name:"Tom",age:3,weight:11.5,class:"猫"}
fmt.Printf("a1.name=%s\n",a1.name)//printa1.name=Tom
fmt.Printf("a1.name=%s\n",(*a1).name)//printa1.name=Tom
}
如上示例a1赋值为animal结构体的指针类型。a1.name
和(*a1).name
的值相同,是因为Go语言帮我们做了默认类型转换,Go语言发现a1是指针类型,自动帮我们转换为指针值,所以我们可以通过a1.name
获取正确的结果。
Go语言中,使用
&
符号获取地址,*
符号获取指针指。
结构体嵌套
typeanimalNamestruct{
firstNamestring
lastNamestring
}
typeanimalstruct{
animalName
ageint
classstring
weightfloat32
}
funcmain(){
a1:=animal{
animalName:animalName{
firstName:"tom",
lastName:"steven",
},
age:5,
class:"猫",
weight:12.5,
}
fmt.Printf("a1=%+v\n",a1)//printa1={animalName:{firstName:tomlastName:steven}age:5class:猫weight:12.5}
fmt.Printf("a1.firstName=%+v\n",a1.firstName)//printa1.firstName=tom
fmt.Printf("a1.lastName=%+v\n",a1.lastName)//printa1.lastName=steven
}
如上示例,我们声明的animal结构体中嵌套了animalName结构体。a1.firstName
和a1.lastName
打印的结构是animalName结构体的字段值。
这是嵌套结构体的特性,当结构体本身字段不存在时,会往被嵌套结构体的“深层”寻找。Go语言由浅入深 逐层查找,找到了对应的字段就返回其值,并停止查找。
当同一层的两个嵌套结构体有相同字段名称时,会报错,因为此时Go语言不知道该访问哪个结构体的字段。如下示例:
typeanimalNamestruct{
firstNamestring
lastNamestring
}
//动物
typeanimalstruct{
animalName
age
classstring
weightfloat32
}
typeagestruct{
firstNamestring
lastNamestring
}
funcmain(){
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)//printa1={animalName:{firstName:tomlastName:steven}age:5class:猫weight:12.5}
fmt.Printf("a1.firstName=%+v\n",a1.firstName)//printa1.firstName=tom
}
报错:Ambiguous reference 'firstName'
我们定义了animal结构体,它嵌套的animalName和age结构体都有firstName和lastName字段。执行a1.firstName会报错,因为在第二层嵌套的结构体中找到了两个firstName字段,Go语言不知道该返回哪一个。这时如果需要获取某个被嵌套结构体的值需要明确调用路径,如下示例:
fmt.Printf("a1.animalName.firstName=%+v\n",a1.animalName.firstName)//printa1.animalName.firstName=tom
fmt.Printf("a1.age.firstName=%+v\n",a1.age.firstName)//printa1.age.firstName=age-tom
前面说到获取字段时会逐层查找,所以不在一个层级上的字段名称重复时,访问不会报错,但是大家也要清楚被访问的优先级,当内层结构体的字段需要被访问时,最好严格书写“调用路径”。
判等操作
结构体是值类型。如果两个结构体的每个字段都可以比较,则结构体可以比较,反之只要有一个字段不可以比较这个结构体就不可以比较。可以比较时只有当所有字段对应的值都相同时,两个结构体 才相等。看下面两个例子:
typeanimalstruct{
namestring
ageint
classstring
weightfloat32
}
funcmain(){
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)//printa1和a2相等true
}
typepersonstruct{
charactermap[string]string
}
funcmain(){
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)
。
空结构体
一个结构体类型也可以不包含任何字段,没有任何字段的结构体也可以有意义,那就是该结构体类型可以拥有方法。如下示例:
typeanimalstruct{
}
func(aanimal)toString(){
fmt.Printf("Iamanimal!")//printIamanimal!
}
funcmain(){
a:=animal{}
a.toString()
}
关于方法的更多内容,我们在下一篇文章深入了解。
作用域
当结构体第一个字母大写时,结构体可以被跨包访问。对于结构体的字段也同样如此,当字段第一个字母大写时,字段可以被跨包访问,小写时只能在包内可以访问。如下示例:
typeAnimalstruct{
Namestring
Ageint
classstring
weightfloat32
}
上面的Animal本身是可以跨包引用的,它的Name和Age字段也可以在别的包中访问,但是calss和weight字段是不可以的。
总结
本文我们主要介绍了,结构体的声明方式、基本用法和作用域。在实际编程中,结构体是我们实现面向对象编程的重要部分。
相关资源