Go的接口总结

一、什么是接口

  • 接口类型是一种抽象的类型,它描述了一系列方法的集合。
    • 接口约定:接口类型中定义的方法即为约定,若一个具体类型实现了所有这些方法,则该类型就满足该接口的约定,或者说它是这个接口类型的实例(实现了该接口)。
    • 可替换性(LSP里氏替换):满足相同接口约定的类型之间可进行相互替换。例如:若一个方法的形参定义为接口类型,那么它可以接收任何满足该接口约定的类型的实参。
    • 接口内嵌:接口类型可通过组合已有的接口来定义
    • io.Writer接口提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等;io.Reader可以代表任意可以读取bytes的类型,io.Closer可以是任意可以关闭的值,例如一个文件或是网络链接。还有fmt.Stringer接口等
    • 接口类型名一般以“er”结尾

二、什么是接口值

  • 接口值:即接口变量的值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值
    • 接口值的零值:动态类型type和对应的动态值value均为nil,如var w io.Writer
    • 空接口值:当且仅当接口的动态类型type和对应的动态值value均为nil时,才为空接口值,此时它等于nil
    • 接口变量的赋值与调用过程:
      • 如w = os.Stdout,这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这个接口值w的动态类型被设为*os.Stdout指针的类型描述符,它的动态值持有os.Stdout的拷贝
      • 调用一个包含*os.File类型指针的接口值的Write方法,w.Write([]byte("hello")) ,使得(*os.File).Write方法被调用
    • 一个接口值可以持有任意大的动态值,不论动态值多大,接口值总是可以容下它
    • 接口值的可比较性:
      • 时刻记住:只能比较动态类型是可比较类型的接口值。
      • 如果接口值的动态类型是可比较的,那么它们之间就可以使用==和!=来进行比较:两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。
      • 如果接口值是可比较的,那么它们可以用在map的键或者作为switch语句的操作数
      • 非接口类型要么是安全的可比较类型(如基本类型和指针)要么是完全不可比较的类型(如切片,映射类型,和函数),但是在比较接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic。同样的风险也存在于使用接口作为map的键或者switch的操作数。
    • 注意:一个包含nil指针的接口不是nil接口(空接口),此时调用接口方法会发生panic错误。即一个接口值的动态类型type != nil,但动态值value == nil,此时的接口值 w != nil。(当把一个值为nil的非接口类型的变量转换为接口类型时,即出现这种情况)
    • 技巧:使用接口时,直接声明一个接口类型的变量,然后再对它赋值,之后使用该变量时,就可以直接把它和nil比较来判断是否为空接口

三、看下面示例

func test(w io.Writer) {
	if w != nil {
		fmt.Println("not nil")
	} else {
		fmt.Println("nil")
	}
}

func test1(w *bytes.Buffer) {
	if w != nil {
		fmt.Println("not nil")
	} else {
		fmt.Println("nil")
	}
}

func proc() {
	var b *bytes.Buffer // b 的接口类型为 bytes.Buffer指针,值为nil(指针指向的是nil)
	test(b)  => not nil // test 中 w 是一个接口类型(io.Writer是一个接口),但是当b作为参数传入时,w的动态类型被赋予了 *bytes.Buffer指针类型,并指向nil,不是一个空接口了,所以 != nil
	test1(b) => nil     // test1 中 w 是一个 *bytes.Buffer指针,b作为参数传入也是一个*bytes.Buffer指针类型,类型相同,这个时候 w != nil 比较的是 byte.Buffer这个指针的指向,由于b指针指向nil,所以 nil == nil

	fmt.Println("===================")

	var b2 = new(bytes.Buffer) // b2 的接口类型为 bytes.Buffer指针,但是值不是nil,因为new的时候会分配一段内存空间并将b2指向这段内存空间
	test(b2)  => not nil       // 同 test(b)
	test1(b2) => not nil       // 由于b2并不指向nil,所以 != nil

	fmt.Println("===================")
	b2 = nil                   // 将 b2 指向nil,就变成了和b一样了
	test(b2)  => not nil
	test1(b2) => nil

	fmt.Println("===================")
	var b3 io.Writer       // 申明b3为一个接口类型,同时由于没有赋予动态类型,所以是一个空接口,== nil
	b3 = new(bytes.Buffer) // 将b3赋予动态接口类型为bytes.Buffer,同时指向了一段新开辟的内存空间,不再是一个空接口
	test(b3)  => not nil   // test 中 w 被赋予为b3,b3是有动态类型的,不是空接口,所以 != nil
	//test1(b3)            // 无法调用 test1,无法将io.Writer作为*bytes.Buffer使用
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值