前导
- 方法是函数的特殊版本。
- 函数是独立的程序实体,可以有名字也可以没名字(匿名函数),还可以当做其他函数的参数或者返回值。可以把具有相同签名(
函数的参数和返回值类型都相同,叫做签名相同
)的函数抽象成独立的函数类型。如type operate func(string) int
。 - 方法不同,必须要有名字,不能当作值来看待(不能作为其他函数的参数或返回值),
必须属于某一个自定义类型
。 - 当一个结构体声明了名叫
String
的方法(返回类型是string)后,在fmt.Printf和fmt.Sprintf时,会调用这个方法。 实际上相当于实现了接口,在调用时。如果变量是&T{},则调用接口方法时可以找到*T和T的方法。如果变量是T{},那么只能找到T的方法。(因为编译器找不到T的地址。)
但是直接调用结构体方法时可以互相转换。如果是T的方法,则无论用&T{}和T{}调用,内部修改都不会生效。如果是*T的方法,则内部修改会生效。
func main() {
category := AnimalCategory{species: "cat"}
fmt.Printf("The animal category: %s\n", category)
}
type AnimalCategory struct {
kingdom string
phylum string
class string
order string
family string
genus string
species string
}
func (ac AnimalCategory) String() string {
return fmt.Sprintf("%s%s%s%s%s%s%s",
ac.kingdom, ac.phylum, ac.class, ac.order,
ac.family, ac.genus, ac.species)
}
方法所属类型不一定是结构体,但一定要是自定义类型,并且不能是接口
。- 一个类型它的方法不能有重名,并且如果是结构体,方法和字段也不能用相同名字。
组合
- 如果结构体类型的某个字段声明中只有一个类型名,该字段代表了结构体的一个嵌入字段(也称匿名字段)。
- 外层结构体可以调用嵌入字段的方法,如果结构体自身有一个方法A,嵌入字段也有一个方法A,调用时不指定的话,会调用最外层的,即结构体本身的A方法。
- 如果结构体有两个嵌入字段A,B,这两者都有方法F,如果外层结构体有方法F,调用时不指定,就会调用结构体本身的。如果结构体本身没有方法F,则编译时会产生一个ambiguous(模棱两可),编译器不知道用A还是B的F。
- 特别的,声明时,结构体零值已经初始化了,而结构体指针初始是nil。
并非继承
- Go语言并没有继承概念。而是嵌入字段的方式实现了类型组合。
- 面向对象的继承,是通过牺牲一定的代码简洁获取可扩展性。
- 类型之间的组合不需要显式声明某个类型实现了某个接口,或者一个类型继承了另一个类型。
值方法和指针方法
- 值方法接收者是类型值得副本,修改一般不会体现在原值上,除非类型本身是引用类型的别名类型,比如切片或字典。
- 指针方法接收者是类型值的指针的副本。修改一定会体现在原值上。
type A int
func main() {
var a A
b := 1
a.f(&b)
fmt.Println(a, b)
}
func (a *A) f(b *int) {
*a = 1
*b = 2
}
自定义数据类型的方法集合包含它的所有值方法,该类型的指针类型的方法集合包含所有值方法和指针方法
。- 在结构体类型调用时,这值也可以调用指针方法,指针也可以调用值方法,因为Go自动转义了。
- 但是在实现接口时,指针类型实现了A接口类型,但是它的基本类型却不能认为也实现了这个接口。