D 的个人博客

全职做开源,自由职业者

  menu

Go 边看边练 -《Go 学习笔记》系列(十二)

上一篇: [1438763483577]


ToC


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};

接口表存储元数据信息,包括接口类型、动态类型,以及实现接口的方法指针。无论是反射还是通过接口调用方法,都会用到这些信息。

数据指针持有的是目标对象的只读复制品,复制完整对象或指针。

接口转型返回临时对象,只有使用指针才能修改其状态。

只有 tabdata 都为 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]



社区小贴士

  • 关注标签 [golang] 可以方便查看 Go 相关帖子
  • 关注标签 [Go 学习笔记] 可以方便查看本系列
  • 关注作者后如有新帖将会收到通知

该文章同步自 黑客派