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}
}
如上示例中,a1
,a2
,a3
,a4
分别展示了四种初始化结构体的方法及各自的打印结构。
对于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.firstName
和a1.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字段是不可以的。
总结
本文我们主要介绍了,结构体的声明方式、基本用法和作用域。在实际编程中,结构体是我们实现面向对象编程的重要部分。
原文始发于微信公众号(一行舟):Go 结构体
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/20299.html