3.1.1. struct结构¶
基本¶
字段¶
填充字段:
struct {
x, y int
_ float32 // 填充
A *[]int
}
匿名字段:
struct {
T1 // 字段名 T1
*T2 // 字段名 T2
P.T3 // 字段名 T3
*P.T4 // 字段名T4
x, y int // 字段名 x 和 y
}
实例化:
var t T // 给 t 分配内存,并零值化内存,这时 t 是类型T
t := new(T) // 变量 t 是一个指向 T的指针, 即*T
var p *T // p是指向一个结构体类型变量的指针
表达式 new(Type) 和 &Type{} 是等价的。
&struct1{a, b, c} 是一种简写,底层仍然会调用 new ()
origin := Point3D{} // Point3D 是零值
line := Line{origin, Point3D{y: -4, z: 12.3}} // line.q.x 是零值
标签(tag)¶
标签的内容不可以在一般的编程中使用,只有 reflect 包能获取它:
type Student struct {
name string "学生名字" // 结构体标签
Room int `json:"Roomid"` // 结构体标签
}
func main() {
st := Student{"Titan", 14}
fmt.Println(reflect.TypeOf(st).Field(0).Tag) // 学生名字
fmt.Println(reflect.TypeOf(st).Field(1).Tag) // json:"Roomid"
}
匿名成员¶
备注
Go语言结构体中可以包含一个或多个匿名(内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字(这一特征决定了在一个结构体中,每种数据类型只能有一个匿名字段)
嵌入与聚合¶
在接口中嵌入接口:
相当于合并了两个接口类型定义的全部函数
在接口中嵌入结构体(不合法)
在结构体中内嵌接口:
初始化的时候,内嵌接口要用一个实现此接口的结构体赋值 这个新结构体可作为初始化时实现了内嵌接口的结构体来赋值
在结构体中嵌入结构体:
不能嵌入自身值类型,可以嵌入自身的指针类型即递归嵌套 在初始化时,内嵌结构体也进行赋值;外层结构自动获得内嵌结构体所有定义的字段和实现的方法
语法糖:
stu := new(Student)
fmt.Printf("%d\n", stu.name) // 相当于: (*stu).name, 这是一个语法糖
内嵌结构体的字段:
stu.Human.name
如果外层结构体中没有同名的name字段,也可用: stu.name
命名冲突¶
当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?外层名字会覆盖内层名字(但是两者的内存空间都保留)。 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误,但不使用没关系
下面代码中如果写成 c.a 是错误的:
type A struct {a int}
type B struct {a, b int}
type C struct {A; B}
var c C
fmt.Printf(c.A.a, c.B.a) // ✅
fmt.Printf(c.a) // 🚫
值接收者和指针接收者¶
纯结构体实现¶
在调用方法的时候:
值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;
指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法
备注
不管方法的接收者是什么类型,该类型的值和指针都可以调用
结构体类型:
type Person struct {
age int
}
func (p Person) howOld() int {
return p.age
}
func (p *Person) growUp() {
p.age += 1
}
值类型:
func main() { qcrao := Person{age: 18} // 值类型 调用接收者也是值类型的方法 fmt.Println(qcrao.howOld()) // 18 // 值类型 调用接收者是指针类型的方法 qcrao.growUp() // 语法糖1️⃣实际上调用的是: (&qcrao).growUp() fmt.Println(qcrao.howOld()) // 19 }
指针类型:
func main() { stefno := &Person{age: 100} // 指针类型 调用接收者是值类型的方法 fmt.Println(stefno.howOld()) // 100: (语法糖2️⃣实际上调用的是: (*stefno).howOld()) // 指针类型 调用接收者也是指针类型的方法 stefno.growUp() fmt.Println(stefno.howOld()) // 101 }
接口实现¶
定义接口:
type Human interface {
howOld() int
growUp()
}
指针类型请求值类型✅:
func main() { var c Human = &Person{18} fmt.Println(c.howOld()) c.growUp() fmt.Println(c.howOld()) }
值类型请求指针类型🚫:
func main() { var c Human = Person{18} fmt.Println(c.howOld()) c.growUp() // 🚫 fmt.Println(c.howOld()) } ./prog.go:23:11: cannot use Person literal (type Person) as type Human in assignment: Person does not implement Human (growUp method has pointer receiver)
备注
如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。
值类型&对象指针分别在何时使用¶
如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者
如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身
备注
使用指针作为方法的接收者的理由:1.方法能够修改接收者指向的值。2.避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。
匿名结构体&匿名接口¶
sort包中有这么一个interface,实现了数组的大小比较:
type Interface interface {
Less(i, j int) bool
}
// Array 实现Interface接口
type Array []int
func (arr Array) Less(i, j int) bool {
return arr[i] < arr[j]
}
匿名结构体¶
上面实现了比较大小,如果第i个元素比第j个元素小返回true,现在想实现反过来的功能,即当第i元素小于第j个元素时返回false,则:
// 使用匿名struct
type reverse struct {
Array
}
// 重写Less方法
func (r reverse) Less(i, j int) bool {
return r.Array.Less(j, i)
}
// 构造reverse Interface
func Reverse(data Array) Interface {
return &reverse{data}
}
使用:
func main() {
arr := Array{1, 2, 3}
rarr := Reverse(arr)
fmt.Println(arr.Less(0, 1))
fmt.Println(rarr.Less(0, 1))
}
匿名嵌入类型方法集提升的规则:
1. 如果 S 包含一个匿名字段 T,S 和 *S 的方法集都包含接收器为 T 的方法提升
2. 如果 S 包含一个匿名字段 T, *S 类型的方法集包含接收器为 *T 的方法提升
3. 如果 S 包含一个匿名字段 *T,S 和 *S 的方法集都包含接收器为 T 或者 *T 的方法提升
匿名接口¶
上面是 匿名接口体
的实现,现在来看看使用 匿名接口
的实现:
// 匿名接口(anonymous interface)
type reverse struct {
Interface
}
// 重写(override)
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
// 构造reverse Interface
func Reverse(data Interface) Interface {
return &reverse{data}
}
使用:
func main() {
arr := Array{1, 2, 3}
rarr := Reverse(arr)
fmt.Println(arr.Less(0,1))
fmt.Println(rarr.Less(0,1))
}
总结¶
上面的2个实例看, 匿名接口
与 匿名结构体
的实现非常相似。但是仔细对比一下你就会发现匿名接口的优点,匿名接口的方式不依赖具体实现,可以对任意实现了该接口的类型进行重写。这在写一些公共库时会非常有用,如果你经常看一些库的源码,匿名接口的写法应该会很眼熟。
对结构体添加一些约束¶
再增加一个Array2类型:
type Array2 []int
func (arr Array2) Less(i, j int) bool {
return arr[i] < arr[j]
}
增加额外字段type:
type Sortable struct {
Interface
// other field
Type string
}
func NewSortable(i Interface) Sortable {
t := reflect.TypeOf(i).String()
return Sortable{
Interface: i,
Type: t,
}
}
使用:
func DoSomething(s Sortable) {
fmt.Println(s.Type) // 打印为Array或Array2
fmt.Println(s.Less(0, 1)) // 等同于s.Interface.Less(0, 1)
}
func main() {
arr1 := Array1{1, 2, 3}
arr2 := Array2{3, 2, 1, 0}
DoSomething(NewSortable(arr1))
DoSomething(NewSortable(arr2))
}