《Go程序设计语言》 7 接口

接口类型是对其他类型行为的概括与抽象

接口即约定

具体类型指定了其所含数据的精确布局,暴露了基于这个精确布局的内部操作;接口是一种抽象类型,它仅仅提供一些方法。接口是:你不知道它是什么,只知道它能做什么。

为了展示,给出下面这个例子

package fmt
func Fprintf(w io.Writer,format string ,args ...interface{}) (int,error)

该函数的第一个参数类型,就是一个接口类型

package io
type Writer interface {
	Write(p []byte)(n int,err error)
}

io.Writer接口定义了一种约定,该约定要求提供的具体类型,包含一个与其签名和行为一致的Write方法。这个约定保证了fmt.Fprintf只需要能够调用其参数的Write方法,就能适合一大堆的类型。比如我们自己造一个

type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) {
	*c += ByteCounter(len(p))
	return len(p),nil
}

这个类型的Write方法就是将字节切片的长度做累加,我们可以对他做fmt.Fprintf

var c ByteCounter
var str = "world"
fmt.Fprintf(&c,"hello,%s",str)
fmt.Println(c) 

接口类型

接口类型所要声明的,仅仅是方法的集合,也就是说下面两种定义是等价的

type Writer interface {
	Write(p []byte)(n int,err error)
}
type Reader interface {
	Read(p []byte) (n int,err error)
} 
type ReadWriter interface {
	Writer
	Reader
}

上面是使用接口内嵌

type ReadWriter interface {
	Write(p []byte)(n int,err error)
	Read(p []byte) (n int,err error)
}

上面是声明一个包含多个方法签名的接口,两者效果是一致的

实现接口

如果一个类型实现了一个接口所要求的所有方法,那么这个类型就实现了这个接口。我们称这是一个具体类型“是(is-a)”接口类型

  • 接口的赋值规则:仅当一个表达式实现了一个接口时,这个表达式才可以赋值给接口
  • 接口的封装功能:接口变量只能调用接口约定的那些方法。也就是将一个具体变量赋值给接口后,只能调用该接口所拥有的方法,原变量的内部结构和其它方法都不被暴露出来
  • 空接口类型必不可少interface{},这是因为我们可以将任意类型的变量赋值给空接口类型

其它高级语言也有接口,不过Go语言的特殊之处是接口实现是“隐性的”,只要你实现了这些方法,就自动判定你实现了这个接口。另一方面,类比C++的话,接口有点像是虚基类

接口值

接口值在 Go 中由两部分构成:动态类型和动态值

  • 动态类型: 这是接口值的一部分,表示接口值当前的具体类型,或者说实现了接口的具体类型。如果我们将一个具体的类型(如*os.File*bytes.Buffer)的值赋给一个接口类型的变量,那么这个接口值的动态类型就会变成这个具体的类型。
  • 动态值: 这是接口值的另一部分,表示接口值的当前值。这个值是动态类型的一个实例。动态值可以存储实现了接口的类型的值。例如,如果我们有一个接口类型Writer,并且类型*os.File*bytes.Buffer都实现了这个接口,那么一个Writer接口类型的值可以存储一个*os.File*bytes.Buffer的值。

在编译时我们并无法知道一个接口值是怎么样的,所以在运行时会启用动态分发。去查找动态类型对应实现的接口方法,并使用动态值作为接收者调用

类型断言

x.(T)

  • 其中x是一个接口类型表达式
  • T是一个类型,称为断言类型
    • 若T是具体类型,检查x的动态类型是否是T,检查成功,返回x的动态值
    • 若T是接口类型,检查x的动态类型是否实现了T,检查成功,返回类型为T的接口变量,其接口值与x保持一致。该方式通常用于给变量“升变“,增加方法数量
  • 断言失败时,假设我们有var w io.Writer = os.Stdout
    • 若是单一返回值,直接崩溃 b := w.(*bytes.Buffer)
    • 两个返回值,则后一个返回值是布尔值,指示是否成功 b,ok := w.(*bytes.Buffer)

类型分支

类型分支是类型断言的一种应用方式,该风格利用率接口值能容纳各种具体类型的能力,将一个接口变量作为这些类型的union来使用。

类型分支通过特殊的switch语句实现

switch x.(type) {
case nil:
case int,uint:
case bool:
default:
}

或者我们可以通过类型断言将原具体类型变量提取出来

func sqlQuote(x interface{}) string {
	switch x := x.(type)
	case nil:
		return "NULL"
	case int,uint:
		return fmt.Sprintf("%d",x) //这里x还是interface{}
	case bool:
		if(x) { //这里x是bool
			return "TRUE"
		}
		return "FALSE"
	case string:
		return sqlQuteString(x)
	default:
		panic(fmt.Sprintf("unexpected type %T:%v",x,x))
}

对于单一类型的分支,x的类型就是对应类型;对应多类型的分支,x的类型保持interface{}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值