golang 接口

1、自定义类型存入接口的内存布局
接口值是一个两个字节长度数据结构,第一个字节包含一个指向内部表的指针(iTable),包含了存储的值的类型信息及和这个值判刑的一组方法。第二个字节指向所存储值的指针。

如果将指针赋值给接口,则类信息会存储一个指向保存的类型的指针,接口的第二个字节依旧保存指向实体值的指针。(图B)


                                                                                           图A


                                                                           图B


2、方法接收者(方法集)
自定义类型 T 的值的指针(t *T)的方法集,由接收者为 T 和 *T  的组成,如果在指针上调用一个接受值类型的方法,go会自动将该指针解引用,并将指针所指的底层值作为方法的接收者


自定义类型 T 的值(t T)的方法集,由接收者为 T的组成。如果我们只有一个值 T,仍然可以调用一个接收者为指针类型的方法,这可以借助于go语言传值的地址的能力实现,前提是该值是可寻址的(即它是一个变量,一个解引用指针,一个数组或切片项,或者结构体中的一个可寻址字段)。因此,假设我们这样调用t.Method(),其中Method() 需要一个指针接收者,而t是一个可寻址的值,go语言会把这个调用等同于(&t).Method()。

为什么会有这种限制?

因为编译器并不是总能自动获得一个值的地址。例如:自定义类型,type Integer int   , 就获取不到Integer 类型值的地址



所以不管是T 或者 *T 的方法集,在调用上是没有区别的。
接收者:
如果是T  则参数为传入参数的一个copy,在方法内的操作不会影响函数外面的值。(如果属性有引用类型,也会有影响。copy 的只是引用类型下的类型类型的指针)
如果是*T  则在函数内修改参数,影响到函数外的值(指针的用途)。

示例代码:

package main
import "fmt"

//user 结构
type user struct {
	name  string
	email string
}
//sendMail 方法绑定在user 上
func (u user) sendMail() {
	fmt.Printf("send mail to %s,%s \n", u.name, u.email)
}
//printName 方法绑定在user 指针上
func (u *user) printName() {
	fmt.Printf("user name %s \n", u.name)
}

func main() {
	u := user{"zhangsan", "[email protected]"}
	u.sendMail()
	u.printName()

	u2 := &user{"zhangsan2", "[email protected]"}
	u2.sendMail()
	u2.printName()

}
3、接口实现
方法集:方法集定义了一组关联到给定类型的值或者指针的方法。定义方法时使用的接收者的类型决定了这个方法的关联到的值,还是关联到指针,还是两个都关联。
GO 语言规范定义的方法集规则:T 类型的方法集只包含值接收者声明的方法, *T 类型的方法集包含值接收声明的方法,也包含指针接收者声明的方法。也就是说:如果 *T 接收者来实现的一个接口,只有指向 T 类型的指针才能够实现接口。T 接收者来实现接口,那么T 和 *T 类型都能够实现了这个接口。

示例代码:

package main

import "fmt"

/**
user 类型
*/
type user struct {
	name  string
	email string
}

/**
user 实现接口
*/
type notify interface {
	notify()
}

type notify2 interface {
	notify2()
}

/**
user 指针实现了 notify 接口
*/
func (u *user) notify() {
	fmt.Printf("notify to user %s \n", u.name)
}

/**
user 实现了 notify 接口
*/
func (u user) notify2() {
	fmt.Printf("notify2 to user %s \n", u.name)
}

/**
notify 方法,接收对数是notify 接口
*/
func notifyMethod(n notify) {
	n.notify()
}

func notifyMethod2(n notify2) {
	n.notify2()
}

func main() {
	u := user{"zhangsan", "[email protected]"}

	//不能将user 类型作为 notify 类型传递给 notifyMethod 方法,user 并没有实现 notify 接口
	//notifyMethod(u)

	//这样传递才可以
	notifyMethod(&u)

	//user 实现了 notify2 接口,那么在user 或者 *user 都实现了此接口
	notifyMethod2(u)
	notifyMethod2(&u)

}
注:示例中notifyMethod 函数是多态函数,只要实现了notify 接口,那么这个函数可以针对任意实体类型来执行notify 方法,这就是多态。

4、类型嵌套接口实现
内部类型接口的实现会被自动提升到外部类型。如果外部类型也实现了相同的接口,那么就内部类型的实现就不会被提升。
示例代码:

package main

import "fmt"

/**
user 类型
*/
type user struct {
	name  string
	email string
}

/**
类型嵌套
*/
type admin struct {
	user
	level int
}

/**
接口
*/
type notify interface {
	notifyToEmail()
}

/*
*user 实现接口
 */
func (u *user) notifyToEmail() {
	fmt.Printf("send mail %s to user %s \n", u.email, u.name)
}

/**
多态方法
*/
func notifyMethod(n notify) {
	n.notifyToEmail()
}

func main() {
	//创建一个admin
	a := admin{
		user:  user{"name", "[email protected]"},
		level: 1,
	}
	//由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。所有 *admin 类型也实现了notify 接口。
	//如果 *admin 实现notify 接口,则内部类型的实现就不会被提升
	notifyMethod(&a)
}




猜你喜欢

转载自blog.csdn.net/convict_eva/article/details/75169248