一、方法简介
方法: Go语言里有两种类型的接收者:值接收者和指针接收者。使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。如果我们使用一个指针作为接收者,那么就会其作用了,因为指针接收者传递的是一个指向原值指针的副本,指针的副本,指向的还是原来类型的值,所以修改时,同时也会影响原来类型变量的值。
我们在调用指针接收者方法的时候,使用的也可以是一个值的变量,这样也是可以的。如果我们没有这么强制使用指针进行调用,Go的编译器自动会帮我们取指针,以满足接收者的要求。同样的,如果是一个值接收者的方法,使用指针也是可以调用的,Go编译器自动会解引用,以满足接收者的要求。 总之,方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。
二、接口简介
接口是duck-type的一种实现。它不关注对象的类型,而是关注对象具有的行为(方法)。大多数的静态语言需要显示的声明类型的继承关系。而 golang 通过 interface 实现了 duck typing, 使得我们无需显示的类型继承。不像其它实现了 duck typing 的动态语言那样,只能在运行时才能检查到类型的转换错误。而 golang 的 interface 特性可以让我们在编译时就能发现错误。
接口: 实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者实现了接口,那么只有类型的指针才能够转换为对应的接口。类型的值只能实现值接收者的接口;所以指向类型的指针,既可以转换为值接收者的接口,也可以转换为指针接收者的接口。
分析:因为不是总能获取一个值的地址,所以值的方法集只包括了使用值接收者实现的方法。
三、接口的内部实现原理
接口对象由接⼝表 (interface table) 指针和数据指针组成。数据指针data 用来保存实际变量的地址。data 中的内容会根据实际情况变化,因为 golang 在函数传参和赋值时是 值传递
的,所以:
- 如果实际类型是一个值,那么 interface 会保存这个值的一份拷贝。interface 会在堆上为这个值分配一块内存,然后 data 指向它。
- 如果实际类型是一个指针,那么 interface 会保存这个指针的一份拷贝。由于 data 的长度恰好能保存这个指针的内容,所以 data 中存储的就是指针的值。它和实际数据指向的是同一个变量。
-
eface 表示空的 interface{},它用两个机器字长表示,第一个字 _type 是指向实际类型描述的指针,第二个字 data 代表数据指针。
// 没有方法的interface type eface struct { _type *_type //指向的是实际类型的描述信息 data unsafe.Pointer//data 用来保存实际变量的地址。 } // 记录着Go语言中某个数据类型的基本特征 type _type struct { size uintptr //size 为该类型所占用的字节数量。 ptrdata uintptr hash uint32 tflag tflag align uint8 fieldalign uint8 kind uint8//kind 表示类型的种类,如 bool、int、float、string、struct、interface 等。 alg *typeAlg gcdata *byte str nameOff ptrToThis typeOff }
-
iface 表示至少带有一个函数的 interface, 它也用两个机器字长表示,第一个字 tab 指向一个 itab 结构,第二个字 data 代表数据指针。其中itab 表示 interface 和 实际类型的转换信息。对于每个 interface 和实际类型,只要在代码中存在引用关系, go 就会在运行时为这一对具体的 <Interface, Type> 生成 itab 信息。
// 有方法的interface type iface struct { tab *itab data unsafe.Pointer //data 用来保存实际变量的地址。 } type itab struct { inter *interfacetype //inter 指向对应的 interface 的类型信息。接口自身的元信息。 _type *_type //type 和 eface 中的一样,指向的是实际类型的描述信息 _type link *itab hash uint32 bad bool inhash bool unused [2]byte fun [1]uintptr //fun 为函数列表,表示对于该特定的实际类型而言,interface 中所有函数的地址 } // interface数据类型对应的type type interfacetype struct { typ _type // _type 为 interface 类型提供的信息 pkgpath name mhdr []imethod //interface 所申明的所有函数信息 }
关于fun字段: fun字段其实是一个动态大小的数组,虽然声明时是固定大小为1,但在使用时会直接通过fun指针获取其中的数据,并且不会检查数组的边界,所以该数组中保存的元素数量是不确定的。fun个人理解是类似于C++中的虚函数指针的存在,当通过接口调用函数时,实际操作就是s.itab->func()。但不同与C++的虚表之处是,GO是在运行时生成虚表,保障了唯一性,避免了C++中可能存在的同一个接口在不同层次被多次继承实现的等一系列问题,但这里会产生额外的时间消耗。
四、示例
- 值接收者实现接口: 使用值接收者实现接口之后,不管是结构体还是结构体指针类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,结构体指针在内部会自动求值解引用指针。
type Mover interface {
move()
}
type dog struct {
}
//值接收者实现接口
func (d dog) move() {
fmt.Println("狗会动")
}
//此时实现接口的是dog类型:
func main() {
var x Mover
var wangcai = dog{
} // 旺财是dog类型
x = wangcai // x可以接收dog类型
var fugui = &dog{
} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
x.move()
}
- 指针接收者实现接口: 实现接口的若是结构体的指针类型,则不能把值类型传给接口,此时接口只能存储结构体指针类型的值。
func (d *dog) move() {
fmt.Println("狗会动")
}
func main() {
var x Mover
var wangcai = dog{
} // 旺财是dog类型
x = wangcai // x不可以接收dog类型
var fugui = &dog{
} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
}