1、接口的定义
保留字 interface 被赋予了多种不同的含义。
每个类型都有接口,意 味着对那个类型定义了方法集合
定义结构和结构的方法:
type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }
也可以定义接口类型,仅仅是方法的集合。
这里定义了一个有两个方法的接口 I:
type I interface {
Get() int
Put(int)
}
对于接口 I,S 是合法的实现,因为它定义了 I 所需的两个方法。
注意,即便是没有明确定义 S 实现了 I,这也是正确的。
func f(p I){ // 定义一个函数接受一个接口类型作为参数
fmt.Println(p.Get()) // p实现了接口I,必须有Get()方法
p.Put(1) // Put() 方法是类似的
}
这里的变量 p 保存了接口类型的值。因为 S 实现了 I,可以调用 f 向其传递 S 类型的值的指针:
var s S; f(&s)
获取 s 的地址,而不是 S 的值的原因,是因为在 s 的指针上定义了方法,
这并不是必须的——可以定义让方法接受值——但是这样的话 Put 方法就不会像期望的那样工作了。
Go 联合了接口值、静态类型检查、运行时动态转换,以及无须明确定义类型适 配一个接口
定义另外一个类型同样实现了接口 I:
type R struct { i int }
func (p *R) Get() int { return p.i }
func (p *R) Put(v int) { p.i = v }
函数 f 现在可以接受 R 个 S 类型的变量。假设需要在函数 f 中知道实际的类型。
在 Go 中可以使用 type switch 得到。
func f(p I) {
switcht:=p.(type){ // 类型判断。在 switch 语句中使用 (type)。保存类型到变量 t;
case *S: // p的实际类型是S的指针;
case *R: // p的实际类型是R的指针;
case S: // p的实际类型是S;
case R: // p的实际类型是R;
default: // 实现了 I 的其他类型。
}
}
在 switch 之外使用 (type) 是非法的。类型判断不是唯一的运行时得到类型的方法。
为了在运行时得到类型,同样可以使用 “comma, ok” 来判断一个接口类型
是否实现了某个特定接口:
if t, ok := something.(I); ok {
// 对于某些实现了接口 I 的
// t 是其所拥有的类型
}
确定一个变量实现了某个接口,可以使用:
t := something.(I)
2、空接口
由于每个类型都能匹配到空接口: interface{}。
我们可以创建一个接受空接口 作为参数的普通函数:
func g(something interface{}) int {
return something.(I).Get()
}
在这个函数中的 return something.(I).Get() 是有一点窍门的。
值 something 具有类型 interface{},这意味着方法没有任何约束:它能包含任何类型。
.(I) 是 类型断言,用于转换 something 到 I 类型的接口。
如果有这个类型,则可以调用 Get() 函数。因
此,如果创建一个 *S 类型的新变量,也可以调用 g(),因为 *S 同 样实现了空接口。
s = new(S)
fmt.Println(g(s));
调用 g 的运行不会出问题,并且将打印 0。
如果调用 g() 的参数没有实现 I 会带来一个麻烦:
i:=5 ←声明i 是一个``该死的‘‘int
fmt.Println(g(i))
这能编译,但是当运行的时候会得到:
panic: interface conversion: int is not main.I: missing method Get
这是绝对没问题,内建类型 int 没有 Get() 方法。
3、方法
方法就是有接收者的函数。
可以在任意类型上定义方法(除了非本地类型,包括内建类型:int 类型不能有方法)。
然而可以新建一个拥有方法的整数类型。
type Foo int
func (self Foo) Emit() {
fmt.Printf("%v", self)
}
type Emitter interface {
Emit()
}
接口类型的方法
接口定义为一个方法的集合。方法包含实际的代码。
换句话说,一个接口就是 定义,而方法就是实现。
因此,接收者不能定义为接口类型,
这样做的话会引 起 invalid receiver type ... 的编译器错误。
注:接收者类型必须是 T 或 *T,这里的 T 是类型名。
T 叫做接收者基础类型或简称基础类型。
基础类型一定不能使指针或接口类型,并且定义在与方法相同的包中。
接口指针
在 Go 中创建指向接口的指针是无意义的。
实际上创建接口值的指针 也是非法的。
4、接口名字
根据规则,单方法接口命名为方法名加上 -er 后缀:Reader,Writer,Formatter 等。
有一堆这样的命名,高效的反映了它们职责和包含的函数名。
Read,Write, Close,Flush,String 等等有着规范的声明和含义。
为了避免混淆,除非有类似 的声明和含义,否则不要让方法与这些重名。
相反的,如果类型实现了与众所 周知的类型相同的方法,
那么就用相同的名字和声明;将字符串转换方法命名 为 String 而不是 ToString。
5、自省和反射
了解一下定义在 Person 的定义中的 “标签”(这里命名为 “namestr”)。
为了做到这个,需要 reflect 包(在 Go 中没有其他方法)。
要记得, 查看标签意味着返回类型的定义。
因此使用 reflect 包来指出变量的类型, 然后 访问标签。