上一篇: [1438763483577]
ToC
- Go 边看边练 -《Go 学习笔记》系列(一)- 变量、常量
- Go 边看边练 -《Go 学习笔记》系列(二)- 类型、字符串
- Go 边看边练 -《Go 学习笔记》系列(三)- 指针
- Go 边看边练 -《Go 学习笔记》系列(四)- 控制流 1
- Go 边看边练 -《Go 学习笔记》系列(五)- 控制流 2
- Go 边看边练 -《Go 学习笔记》系列(六)- 函数
- Go 边看边练 -《Go 学习笔记》系列(七)- 错误处理
- Go 边看边练 -《Go 学习笔记》系列(八)- 数组、切片
- Go 边看边练 -《Go 学习笔记》系列(九)- Map、结构体
- Go 边看边练 -《Go 学习笔记》系列(十)- 方法
- Go 边看边练 -《Go 学习笔记》系列(十一)- 表达式
- Go 边看边练 -《Go 学习笔记》系列(十二)- 接口
- Go 边看边练 -《Go 学习笔记》系列(十三)- Goroutine
- Go 边看边练 -《Go 学习笔记》系列(十四)- Channel
6.1 接口定义
接口是一个或多个方法签名的集合,任何类型的方法集中只要拥有与之对应的全部方法,就表示它 "实现" 了该接口,无须在该类型上显式添加接口声明。
所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。当然,该类型还可以有其他方法。
- 接口命名习惯以 er 结尾,结构体。
- 接口只有方法签名,没有实现。
- 接口没有数据字段。
- 可在接口中嵌入其他接口。
- 类型可实现多个接口。
空接口 interface{}
没有任何方法签名,也就意味着任何类型都实现了空接口。其作用类似面向对象语言中的根对象 Object
。
1func Print(v interface{}) {
2 fmt.Printf("%T: %v\n", v, v)
3}
4
5func main() {
6 Print(1)
7 Print("Hello, World!")
8}
输出:
1int: 1
2string: Hello, World!
匿名接口可用作变量类型,或结构成员。
6.2 执行机制
接口对象由接口表 (interface table) 指针和数据指针组成。
runtime.h
1struct Iface
2{
3 Itab* tab;
4 void* data;
5};
6
7struct Itab
8{
9 InterfaceType* inter;
10 Type* type;
11 void (*fun[])(void);
12};
接口表存储元数据信息,包括接口类型、动态类型,以及实现接口的方法指针。无论是反射还是通过接口调用方法,都会用到这些信息。
数据指针持有的是目标对象的只读复制品,复制完整对象或指针。
接口转型返回临时对象,只有使用指针才能修改其状态。
只有 tab
和 data
都为 nil
时,接口才等于 nil
。
1var a interface{} = nil // tab = nil, data = nil
2var b interface{} = (*int)(nil) // tab 包含 *int 类型信息, data = nil
3
4type iface struct {
5 itab, data uintptr
6}
7
8ia := *(*iface)(unsafe.Pointer(&a))
9ib := *(*iface)(unsafe.Pointer(&b))
10
11fmt.Println(a == nil, ia)
12fmt.Println(b == nil, ib, reflect.ValueOf(b).IsNil())
输出:
1true {0 0}
2false {505728 0} true
6.3 接口转换
利用类型推断,可判断接口对象是否某个具体的接口或类型。
1type User struct {
2 id int
3 name string
4}
5
6func (self *User) String() string {
7 return fmt.Sprintf("%d, %s", self.id, self.name)
8}
9
10func main() {
11 var o interface{} = &User{1, "Tom"}
12
13 if i, ok := o.(fmt.Stringer); ok { // ok-idiom
14 fmt.Println(i)
15 }
16
17 u := o.(*User)
18 // u := o.(User) // panic: interface is *main.User, not main.User
19 fmt.Println(u)
20}
还可用 switch
做批量类型判断,不支持 fallthrough
。
1func main() {
2 var o interface{} = &User{1, "Tom"}
3
4 switch v := o.(type) {
5 case nil: // o == nil
6 fmt.Println("nil")
7 case fmt.Stringer: // interface
8 fmt.Println(v)
9 case func() string: // func
10 fmt.Println(v())
11 case *User: // *struct
12 fmt.Printf("%d, %s\n", v.id, v.name)
13 default:
14 fmt.Println("unknown")
15 }
16}
超集接口对象可转换为子集接口,反之出错。
1type Stringer interface {
2 String() string
3}
4
5type Printer interface {
6 String() string
7 Print()
8}
9
10type User struct {
11 id int
12 name string
13}
14
15func (self *User) String() string {
16 return fmt.Sprintf("%d, %v", self.id, self.name)
17}
18
19func (self *User) Print() {
20 fmt.Println(self.String())
21}
22
23func main() {
24 var o Printer = &User{1, "Tom"}
25 var s Stringer = o
26 fmt.Println(s.String())
27}
6.4 接口技巧
让编译器检查,以确保某个类型实现接口。
1var _ fmt.Stringer = (*Data)(nil)
某些时候,让函数直接 "实现" 接口能省不少事。
1type Tester interface {
2 Do()
3}
4
5type FuncDo func()
6func (self FuncDo) Do() { self() }
7
8func main() {
9 var t Tester = FuncDo(func() { println("Hello, World!") })
10 t.Do()
11}
下一篇: [1438938175118]
- 本系列是基于雨痕的《Go 学习笔记》(第四版)整理汇编而成,非常感谢雨痕的辛勤付出与分享!
- 转载请注明:文章转载自:黑客与画家的社区 [http://symphony.b3log.org]
- 如果你觉得本章节做得不错,请在下面打赏一下吧~
社区小贴士
- 关注标签 [golang] 可以方便查看 Go 相关帖子
- 关注标签 [Go 学习笔记] 可以方便查看本系列
- 关注作者后如有新帖将会收到通知
该文章同步自 黑客派