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 = &s
s2.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") // 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

所以,针对指针嵌入类型,在使用前,需要赋值。

嵌入和方法集的关系

  • 类型 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()

本文转载自云计算


往期精彩回顾

2019年度精选文章

Linux中断一网打尽(1) — 中断及其初始化

全国新型肺炎实时动态


360技术公众号

技术干货|一手资讯|精彩活动

扫码关注我们

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值