结构体字段定义的两种方式:普通字段声明与嵌入字段
先创建一个结构体Student,Student具有两个方法:Hello()以及Grow(),Hello()是一个值接收者方法,Grow()是一个指针接收者方法。
type Student struct{
Name string
Age int
}
func(student Student) Hello(){
fmt.Printf("你好, 我是%s,今年%d岁\n",student.Name,student.Age)
}
func(student *Student) Grow(){
student.Age++
}
再创建一个接口StudentInterface以及GrowOne()函数,显然Student是实现了StudentInterface。
type StudentInterface interface{
Hello()
Grow()
}
func GrowOne(studentInterface StudentInterface){
studentInterface.Hello()
studentInterface.Grow()
studentInterface.Hello()
}
普通字段声明
再创建一个结构体Person,这里使用普通字段声明Student
type Person struct{
Student Student
Address string
}
对于普通字段声明,Student的方法和字段完全与Person无关:
1.Person不能直接调用Hello()和Grow()方法,必须通过person.Student来调用这两个方法
2.Person没有实现StudentInterface接口
3.Person不具备Student的字段
嵌入字段
嵌入字段是Go实现组合(而非继承)的核心特性。在使用嵌入字段时,有两种情况:使用T嵌入和使用*T嵌入
使用T嵌入
修改结构体Person,T嵌入的本质是嵌入一个值
type Person struct{
Student
Address string
}
初始化
在Person被创建的时候,即使不给Student赋值,也会分配一个Student的零值类型
func main() {
person:=Person{
Address: "上海",
}
GrowOne(&person.Student)
}
输出结果为:
你好, 我是,今年0岁
你好, 我是,今年1岁
很明显,Student中的Name字段是string类型,被赋予零值"",Age字段是int,被赋予零值0
方法提升和字段提升
Student的值接收者方法和其字段会被提升到Person,如图所示:
但是为什么Grow()方法是一个指针接收者方法,而person.Grow()不会报错呢?
原因在于Go语言在调用方法时有一个重要的特性:自动解引用/取地址机制。
通过一个变量调用方法时,Go编译器会自动进行必要的地址操作,以匹配方法的接收者类型。
person.Grow() // Go会自动转换为:(&person.Student).Grow()
下面来证明person实际是不拥有Grow()这个方法的:
方法1
先假设person拥有Grow()这个方法,那么此时person同时拥有Hello()和Grow(),这意味着person实现了StudentInterface接口。
但实际上perso并不是StudentInterface,因此person实际上是不拥有Grow()的。
方法2
还有可以通过另一种方式证明,并引出go中字面量的特性
通过person的字面量调用Grow()和Hello(),会发现调用Grow()时报错,其原因还是
Person{...}.Grow() // Go会自动转换为:(&Person{...}.Student).Grow()
但是Person{...}是一个字面量,但在 Go 语言中,复合字面量(如结构体、数组、切片、map的字面量)在直接使用时是不可寻址的,字面量代表的是一个值,而不是一个变量。因此Go直接禁止了字面量的寻址。
使用*T嵌入
修改结构体Person,*T嵌入本质是嵌入一个指针
type Person struct{
*Student
Address string
}
初始化
需要注意的一点是,因为*T嵌入是嵌入一个指针,因此在初始化Person时未设置Student字段的值,那么其将是一个空指针nil。
方法提升和字段提升
Student中的所有字段以及所有方法将会提升到Person中,被Person所拥有
person拥有Hello()以及Grow()因此person此时实现了StudentInterface接口
总结
- 在结构体中使用普通字段声明,字段的字段和方法与结构体无关,必须通过字段来间接调用。
- 在结构体中使用T嵌入字段时,T的字段和值接收方法被结构体所拥有
- 在结构体中使用*T嵌入字段时,T的所有和所有方法都被结构体所拥有