结构体内嵌类似OOP中的继承。
1. 类型内嵌
结构体允许其成员字段在声明时没有字段名,而只有类型。这种形式的字段称为匿名字段或类型内嵌。
type Data struct {
int // 定义结构体的匿名字段
float32
bool
}
ins := &Data{
int: 10, // 为Data实例中的字段赋初值
float32: 3.14,
bool: true,
}
结构体要求成员变量字段名称必须唯一,因此一个结构体中同类型的匿名字段只能有一个。
2. 声明结构体内嵌
如果匿名字段的类型为结构体,那么结构体实例化后,我们可以直接访问匿名结构体里的所有成员变量,此方式称为结构体内嵌。
- 传统写法
package main
import "fmt"
// 定义基础颜色
type BasicColor struct {
R, G, B float32
}
// 定义完整颜色
type Color struct {
Basic BasicColor // 基础颜色结构体
Alpha float32 // 透明度
}
func main() {
var c Color // 结构体实例化
c.Basic.R = 1 // 访问结构体实例的结构体类型成员变量并赋值
c.Basic.G = 1
c.Basic.B = 0
c.Alpha = 1
fmt.Printf("%+v", c)
}
// {Basic:{R:1 G:1 B:0} Alpha:1}
- 结构体内嵌写法
package main
import "fmt"
// 定义基础颜色
type BasicColor struct {
R, G, B float32
}
// 定义完整颜色
type Color struct {
BasicColor // 没有成员变量名只有类型,叫做结构体内嵌
Alpha float32 // 透明度
}
func main() {
var c Color // 结构体实例化
c.R = 1 // 直接访问实例c的匿名结构体里所有成员变量
c.G = 1
c.B = 0
c.Alpha = 1
fmt.Printf("%+v", c)
}
// {BasicColor:{R:1 G:1 B:0} Alpha:1}
3. 结构体内嵌特性
- 结构体内嵌可以直接访问其成员变量
可以通过实例名直接访问嵌入结构体的成员变量。如果结构体有多层嵌入结构,通过实例访问任何一级嵌入结构成员变量时,只需给出字段名即可。如ins.a.b.c可以简写成ins.c。
- 结构体内嵌的类型名是它的成员变量字段名
结构体内嵌仍然可以使用传统方式进行一层层访问,结构体内嵌的类型名是它的成员变量字段名。
var c Color
c.BasicColor.R = 1
c.BasicColor.G = 1
c.BasicColor.B = 0
一个结构体只能嵌入一个同类型的成员,无须担心结构体重名和错误赋值。
4. 使用“组合”描述对象
在OOP中,实现对象关系需要使用“继承”特性,如人类不能飞行,鸟类可以飞行。人类和鸟类都可以从行走类继承,但只有鸟类从飞行类继承。OOP原则建议对象最好不要使用多重继承,鸟类同时继承行走类和飞行类,这显然是有问题的。在OOP中要正确实现对象多重继承,需要技巧。
go语言的结构体内嵌是一种组合,类似于OOP中的继承,可以快速构建对象的不同特性。
package main
import "fmt"
// 天上飞的
type Flying struct {}
func (f *Flying) Fly() {
fmt.Println("can fly")
}
// 地上走的
type Walking struct {}
// 给Walking结构体添加Walk方法
func (w *Walking) Walk() {
fmt.Println("can walk")
}
// 结构体内嵌
// 人类
type Human struct {
Walking
}
// 鸟类
type Bird struct {
Walking
Flying
}
func main() {
b := new(Bird) // 结构体实例化
fmt.Println("Bird: ")
b.Fly() // 实例调用方法
b.Walk()
h := new(Human)
fmt.Println("Human: ")
h.Walk()
}
/*
Bird:
can fly
can walk
Human:
can walk
*/
使用go语言的结构体内嵌组合,可以自由地对实例进行实现增、删、改各种操作。
5. 初始化结构体内嵌
结构体内嵌初始化时,把结构体内嵌的类型作为成员变量字段名,像普通结构体一样初始化。
package main
import "fmt"
// 车轮,基础构件
type Wheel struct {
Size int
}
// 引擎,基础构件
type Engine struct {
Power int
Type string
}
// 车,整体结构,结构体内嵌
type Car struct {
Wheel
Engine
}
func main() {
c := Car {
Wheel: Wheel{
Size: 18,
},
Engine: Engine{
Power: 143,
Type: "1.4T",
},
}
fmt.Printf("%+v\n", c)
}
//{Wheel:{Size:18} Engine:{Power:143 Type:1.4T}}
6. 初始化匿名结构体内嵌
在初始化匿名内嵌结构体时,我们需要再次声明匿名结构体的结构,才能初始化赋值。使用“键值对”格式,初始化填充数据。
package main
import "fmt"
// 车轮,基础构件
type Wheel struct {
Size int
}
// 车,整体结构
type Car struct {
Wheel
Engine struct { // 匿名内嵌结构体,匿名内嵌结构体不能被外部引用
Power int
Type string
}
}
func main() {
c := Car {
Wheel: Wheel{
Size: 18,
},
Engine: struct { // 内嵌结构体类型名作为成员变量字段名
Power int
Type string
}{
Power: 143,
Type: "1.4T",
},
}
fmt.Printf("%+v\n", c)
}
注:声明内嵌结构体时,多个成员之间换行不用",";在初始化赋值时,结构体成员变量之间要有",",这点需要注意。
7. 成员名字冲突
内嵌结构体内部可能有相同的成员名,成员重名会导致编译器无法选择是哪个内嵌结构体的成员变量字段,引起歧义和错误。
package main
import "fmt"
type A struct {
a int
}
type B struct {
a int
}
type C struct {
A
B
}
func main() {
c := &C{} // 内嵌结构体实例化
c.A.a = 1 // 成员变量类型已经定义,所以不用:=类型推断赋值,而用=
fmt.Println(c)
// c.a = 1
// fmt.Println(c)
}
// &{{1} {0}}