首页 > golang > Go的方法集详解
2019
05-31

Go的方法集详解

 

Go语言以其本身具有的高并发特性,在云计算开发中,得到了广泛的应用,也深受广大开发者的欢迎。但是大家对go语言真的了解了么?本文作者经过对go语言的多年实践应用,现对go语言中的方法集进行了一次详细的总结,并通过实验进行了验证,相信对于go语言爱好者有很大的帮助。下来就跟随作者一起学习下吧。

 

Go的方法集详解_方法集

1

什么是方法集

在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()方法。为什么呢?因为:
  • 类型 T 方法集,包含全部 receiver T 方法。

  • 类型 *T 方法集,包含全部 receiver T + *T 方法。

 

2

方法集和方法接受者的关系

在上面的案例中,类型Student的方法集并不包含了setName()方法,那么是不是Student类型变量,就不能调用setName()方法呢?即下面调用,是否会报错呢?

  •  
s := Student{}s.setName("dq")
其实,上面的调用是ok的。为什么呢?我们来回顾一下go语言的方法定义。
  • 参数 receiver 可任意命名,如方法中,不使用参数,可以省略参数名。
  • 参数 receiver 类型可以是 T 或 *T,但类型T不能为接口或指针类型。
  • 不支持方法重载。
  • 实例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 = &ss2.showName() //o会自动转为 (*s2).showName()

所以,当类型调用自己申明的方法的时候,不需要考虑方法集。方法接受者是值类型(T),还是指针类型(*T),影响T类型的实体变量的方法集。

 

3

方法集和接口

接口的定义

接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口“对应的全部方法”声名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。对应的全部方法:是指有相同名称、参数列表 (不包括参数名) 以及返回值。接口只有方法的声明,没有实现。接口可以匿名嵌入到其他接口,或是嵌入结构体中。接口命名习惯以 er 结尾。
  •  
type Personer interface { //定义一个接口   showName()} type Student struct {   Personer //嵌入接口}

接口执行机制

接口对象,是由接口表(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}

以实体类型和以指针类型实现接口的区别

  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= &Cat{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() }

 

4

方法集和嵌入

什么是嵌入

go语言中,所谓的嵌入,即把一个类型作为另外一个类型的匿名字段。如下:

  •  
type Person struct {   age  int8   name string} type Student struct {   Person //嵌人 Persion类型}

go语言通过嵌入组合,来实现继承的行为。于是,我们就可以通过Student类型的实例,访问Persion类型的变量和方法。如下:

  •  
var s = Student{}s.name = "dq"

值类型(T)嵌入和指针类型(*T)嵌入的区别

  •  
type Student1 struct {   Person //值类型的嵌入}type Student2 struct {   *Person //指针类型的嵌入}
要理解这个区别,就有知道go语言中类型的默认值。如下:
  • 数值类型(如int8、int16、uint等),默认值为0;
  • 布尔类型,默认值为false;
  • 字符串类型,默认值为""
  • 指针、通道、切片、字典等,默认值为nil
  • 复合类型的默认值,为所包含类型的默认值。
所以:
  •  
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") // oks1.showName() // 内嵌类型 *Persion默认值为 nils2 := &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所以,针对指针嵌入类型,在使用前,需要赋值。

嵌入和方法集的关系

  • 类型 S 包含匿名字段 T,则 S和*S 方法集包含 T 方法。
  • 类型 S 包含匿名字段 *T,则 S和 *S 方法集包含 T + *T 方法。
  • 不管嵌入的是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()

 

Go的方法集详解_方法集_02

 

作者:golang中国
golang中国

本文》有 9054 条评论

留下一个回复