-
博文分类专栏
- Jquery基础教程
-
- 文章:(15)篇
- 阅读:47251
- shell命令
-
- 文章:(42)篇
- 阅读:156700
- Git教程
-
- 文章:(36)篇
- 阅读:237762
- leetCode刷题
-
- 文章:(76)篇
- 阅读:137189
-
go的方法集详解2019-02-25 22:28 阅读(7665) 评论(0)
一、什么是方法集
在go语言中,每个类型都有与之关联的方法,把这个类型的所有方法称为类型的方法集。如下:
type Student struct { age int8 name string } func (s Student) showName() { fmt.Println(s.name) } func (s * Student) setName(newName string) { s.name = newName }
类型Student方法集包含了showName()方法。
类型*Student方法集包含了showName()方法和setName()方法。
为什么呢?因为:
1、类型 T 方法集,包含全部 receiver T 方法。
2、类型 *T 方法集,包含全部 receiver T + *T 方法。
二、方法集和方法接受者的关系
在上面的案例中,类型Student的方法法集并不包含了setName()方法,那么是不是Student类型变量,就不能调用setName()方法呢?即下面调用,是否会报错呢?
s := Student{} s.setName("dq")
其实,上面的调用是ok的。为什么呢?我们来回顾一下go语言的方法定义。
1、参数 receiver 可任意命名,如方法中,不使用参数,可以省略参数名。
2、参数 receiver 类型可以是 T 或 *T,但类型T不能为接口或指针类型。
3、不支持方法重载。
4、可以实例value或pointer调用全部的方法,编译器会自动转换。
如下:
type Student struct { age int8 name string } type StudentPoint *Student func (Student) sayHello() { //省略receiver 的参数参数名字 fmt.Println("hello world") } func (s Student) showName() { fmt.Println(s.name) } func (s * Student) setName(newName string) { s.name = newName } func (s StudentPoint) showName2(){ // Error:接受者(receiver)为指针类型 fmt.Println(s.name) } s := Student{} s.setName("dq") //go会自动转为 (&s).setName("dq") var s2 = &s s2.showName() //o会自动转为 (*s2).showName()
所以,当类型调用自己申明的方法的时候,不需要考虑方法集。方法接受者是值类型(T),还是指针类型(*T),影响T类型的实体变量的方法集。
三、方法集和接口
1、接口的定义
接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口“对应的全部方法”签名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。
对应的全部方法:是指有相同名称、参数列表 (不包括参数名) 以及返回值。
接口只有方法的申明,没有实现。
接口可以匿名嵌入到其他接口,或是嵌入结构体中。
接口命名习惯以 er 结尾。
type Personer interface { //定义一个接口 showName() } type Student struct { Personer //嵌入接口 }
2、接口执行机制
接口对象,是有接口表(interface table)和数据指针组成。
接口表存储元数据信息,包括接口类型、动态类型,以及实现接口的方法指针。
数据指针持有的是目标对象的只读复制品,复制完整对象或指针。
package main import ( "fmt" "reflect" ) type User struct { id int name string } type Student struct { id int name string } func main() { u := User{1, "Tom"} var i interface{} = u // 由于interface{}不包含任何方法,所以任何类型,都实现了interface{}接口 fmt.Println(reflect.TypeOf(i)) //main.User i = Student{} fmt.Println(reflect.TypeOf(i)) //main.Student }
3、以实体类型实现接口和以指针类型实现接口的区别
1、若以实体类型(T)实现接口,不管是T类型的值,还是T类型的指针,都实现了该接口。
2、若以指针类型(*T)实现接口,只有T类型的指针,才实现了该接口。
type Animal interface { //接口 say() } type Dog struct { name string } type Cat struct { name string } func (_self Dog) say() { fmt.Println("I am", _self.name) } func (_self *Cat) say() { fmt.Println("I am", _self.name) } func main() { var d1 Animal= Dog{name:"wangCai1"} d1.say() //因为Dog 的value类型实现了Animal接口 var d2 Animal= &Dog{name:"wangCai2"} d2.say() //因为dDog 的指针类型实现了Animal接口 var c1 Animal= Cat{name:"miaoMiao1"} c1.say() //因为Cat 的value类型没有实现了Animal接口,所以报错 var c2 Animal= &Dog{name:"miaoMiao2"} c2.say() //因为Cat 的指针类型实现了Animal接口 }
类型必须实现接口的所有方法,才能表示它 "实现" 了该接口,如下:
type Animal interface { say() doSome() } type Dog struct { name string } func (_self Dog) say() { fmt.Println("I am", _self.name) } func (_self *Dog) doSome() { fmt.Println("I will do something") } func main() { // 报错,因为Dog的value类型实现了Animal接口的say方法没有实现doSome方法 var d1 Animal= Dog{name:"wangCai1"} d1.say() //因为dDog 的指针类型实现了Animal接口集的所有方法 var d2 Animal= &Dog{name:"wangCai2"} d2.say() }
四、方法集和嵌入
1、什么是嵌入
go语言中,所谓的嵌入,即把一个类型作为另外一个类型的匿名字段。如下:
type Person struct { age int8 name string } type Student struct { Person //嵌人 Persion类型 }
go语言通过嵌入组合,来实现继承的行为。于是,我们就可以通过Student类型的实例,访问Persion类型的变量和方法。如下:
var s = Student{} s.name = "dq"
2、值类型(T)嵌入和指针类型(*T)嵌入的区别
type Student1 struct { Person //值类型的嵌入 } type Student2 struct { *Person //指针类型的嵌入 }
要理解这个区别,就有知道go语言中类型的默认值。如下:
1、数值类型(如int8、int16、uint等),默认值为0;
2、布尔类型,默认值为false;
3、字符串类型,默认值为""
4、指针、通道、切片、字典等,默认值为nil
5、复合类型的默认值,为所包含类型的默认值。
所以:
type Person struct { age int8 name string } func (s Person) showName() { fmt.Println(s.name) } func (s *Person) setName(newName string) { s.name = newName } type Student1 struct { Person //Student1包含了Person,那么Student1对应的value和pointer包含Person } type Student2 struct { *Person } // 内嵌类型 Persion默认值为 Person{age:0, name:""} s1 := Student1{} s1.setName("student1_01") // ok s1.showName() // 内嵌类型 *Persion默认值为 nil s2 := &Student2{} s2.setName("student1_02") //Error,由于目前内嵌类型的值为nil,会触发报错 s2.showName() // 给嵌入类型一个复制,就ok了 s3 := &Student2{Person:&Person{age:3, name:"s3"}} //s3 := &Student2{&Person{age:3, name:"s3"}} 和上一行等价 s3.showName()
在上面的案例中变量s2中嵌入类型默认值为nil,故会报错:panic: runtime error: invalid memory address or nil pointer dereference
所以,针对指针嵌入类型,在使用前,需要赋值。
3、嵌入和方法集的关系
1、类型 S 包含匿名字段 T,则 S和*S 方法集包含 T 方法。
2、类型 S 包含匿名字段 *T,则 S和 *S 方法集包含 T + *T 方法。
3、不管嵌入的是T还是*T,*S方法集,包含 T + *T 方法。
下面,通过案例来解析,如下,思考Student1的指针和实例类型,以及Student2的指针和实例类型,是否实现了Human接口呢?
type Human interface { //定义接口 showName() setName(string) } type Person struct { age int8 name string } func (s Person) showName() { // Person类型的实例和指针,都实现了Human的showName方法 fmt.Println(s.name) } func (s *Person) setName(newName string) { // 只有Person类型的指针,才实现了Human的setNanme方法 s.name = newName } type Student1 struct { // 以值类型,嵌入 Person } type Student2 struct { // 以指针类型,嵌入 *Person }
解析:(1)应用上面规则1,由于Student1通过实体类型(T)方式,嵌入Person,所以 Stuednt1的实例类型,仅仅包含了接受者为Person的方法,即不包含setName()方法,所以Student1的实例类型,没有实现Human接口,不能赋值给Human接口;应用上面规则3, Stuednt1的指针类型,包含了接受者为Person和接受者为*Person的方法,即Stuednt1的指针类型,实现了Human接口。
(2)应用上面规则2,由于Student2通过指针类型(*T)方式,嵌入Person,所以Student2的指针类型和实例类型,都实现了Human接口
所以:
// Error 应用上面的关系判断第1条规则,因为Student1实例类型的方法集中,仅仅包含Person的实例方法集,即仅仅包含showName()方法,所以Student1的实例类型,没有实现Human接口 var s1 Human = Student1{} //报错:Student1 does not implement Human (setName method has pointer receiver) s1.setName("student1_01") s1.showName() var s2 Human = &Student1{} //ok 应用第1条和弟3条规则 s2.setName("student1_02") s2.showName() var s3 Human = Student2{&Person{}} //ok ,应用第2条规则 s3.setName("student2_01") s3.showName() var s4 Human = &Student2{&Person{}} //ok ,应用第2条规则 s4.setName("student2_02") s4.showName()