接口
接口类型 是由一组方法签名定义的集合。
接口类型的变量可以保存任何实现了这些方法的值。
注意: 示例代码的 22 行存在一个错误。由于 Abs 方法只为 *Vertex (指针类型)定义,因此 Vertex(值类型)并未实现 Abser。
1 package main 2 3 4 import ( 5 "fmt" 6 "math" 7 ) 8 9 10 type Abser interface { 11 Abs() float64 12 } 13 14 15 func main() { 16 var a Abser 17 f := MyFloat(-math.Sqrt2) 18 v := Vertex{3, 4} 19 20 21 a = f // a MyFloat 实现了 Abser【看后面的abs()定义】 22 a = &v // a *Vertex 实现了 Abser【看后面的abs()定义】 23 24 25 // 下面一行,v 是一个 Vertex(而不是 *Vertex) 26 // 所以没有实现 Abser。 27 a = v 28 29 30 fmt.Println(a.Abs()) 31 } 32 33 34 type MyFloat float64 35 36 37 func (f MyFloat) Abs() float64 { 38 if f < 0 { 39 return float64(-f) 40 } 41 return float64(f) 42 } 43 44 45 type Vertex struct { 46 X, Y float64 47 } 48 49 50 func (v *Vertex) Abs() float64 { 51 return math.Sqrt(v.X*v.X + v.Y*v.Y) 52 }
接口与隐式实现
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
1 type I interface { 2 M() 3 } 4 5 type T struct { 6 S string 7 }
// 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。 10 func (t T) M() { 11 fmt.Println(t.S) 12 } 13 14 func main() { 15 var i I = T{"hello"}//i是实现了接口I的类型T,S值为"hello" 16 i.M()//i调用接口I中的M()方法,打印i的S值"hello" 17 }
接口值
接口也是值。它们可以像其它值一样传递。
接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和具体类型的元组:
- (value, type)
接口值保存了一个具体底层类型的具体值。
接口值调用方法时会执行其底层类型的同名方法。
1 package main 2 3 4 import ( 5 "fmt" 6 "math" 7 ) 8 9 10 type I interface { 11 M() 12 } 13 14 15 type T struct { 16 S string 17 } 18 19 20 func (t *T) M() {//T的指针类型实现了I接口的方法M() 21 fmt.Println(t.S)//打印t指针指向的值的s 22 } 23 24 25 type F float64 26 27 28 func (f F) M() {//F类型 float64 实现了I接口的方法M() 29 fmt.Println(f)//打印f 30 } 31 32 33 func main() { 34 var i I//变量 35 36 37 i = &T{"Hello"}//T指针指向T类型S为hello的值 38 describe(i) 39 i.M()//调用接受者为T指针类型的M()方法,打印该指针指向的T类型值的S,即"hello" 40 41 42 i = F(math.Pi)//i=Π的浮点表示,F--float64 43 describe(i) 44 i.M()//调用接受者为F类型的M()方法,打印F,即"i=Π的浮点表示" 45 } 46 47 48 func describe(i I) { 49 fmt.Printf("(%v, %T)\n", i, i)//打印值和类型 50 }
底层值为 nil 的接口值
即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。
在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。
注意: 保存了 nil 具体值的接口其自身并不为 nil。
1 package main 2 3 4 import "fmt" 5 6 7 type I interface { 8 M() 9 } 10 11 12 type T struct { 13 S string 14 } 15 16 17 func (t *T) M() { 18 if t == nil { 19 fmt.Println("<nil>") 20 return 21 } 22 fmt.Println(t.S) 23 } 24 25 26 func main() { 27 var i I 28 29 30 var t *T 31 i = t//空指针 32 describe(i) 33 i.M()//空指针异常,看上面的处理 34 35 36 i = &T{"hello"} 37 describe(i) 38 i.M()//正常时打印 39 } 40 41 42 func describe(i I) { 43 fmt.Printf("(%v, %T)\n", i, i) 44 }

nil 接口值
nil 接口值既不保存值也不保存具体类型。
为 nil 接口调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。
1 type I interface { 2 M() 3 } 4 5 6 func main() { 7 var i I//nil接口值 8 describe(i) 9 i.M()//会产生运行时错误,因为不能知道该调用哪个具体方法。 10 //上例与此例区别为,上例知道是空指针,而且有空指针处理的方法,因此不报错 11 } 12 13 14 func describe(i I) { 15 fmt.Printf("(%v, %T)\n", i, i) 16 }

空接口
指定了零个方法的接口值被称为 空接口:
- interface{}
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。
1 func main() { 2 var i interface{} 3 describe(i)//空接口 4 5 6 i = 42 7 describe(i) 8 9 10 i = "hello" 11 describe(i) 12 } 13 14 15 func describe(i interface{}) { 16 fmt.Printf("(%v, %T)\n", i, i) 17 }

类型断言
类型断言 提供了访问接口值底层具体值的方式。
- t := i.(T)
该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。
若 i 并未保存 T 类型的值,该语句就会触发一个panic。
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
- t, ok := i.(T)
若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true。
否则,ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生panic。
请注意这种语法和读取一个映射时的相同之处。
1 package main 2 3 import "fmt"func main() { 4 var i interface{} = "hello" 5 6 7 s := i.(string)//断言接口值i保存了具体类型string,并将其底层类型为string的值(即"hello")赋予变量s 8 fmt.Println(s) 9 10 11 s, ok := i.(string)//判断接口值是否保存了一个特定的类型,类型断言(i.(T))返回两个值:其底层值以及一个报告断言是否成功的布尔值 12 fmt.Println(s, ok) 13 14 15 f, ok := i.(float64)//同上,因为接口值没有保存float64类型的值所以返回float类型的零值与布尔值flase 16 fmt.Println(f, ok) 17 18 19 f = i.(float64) // 报错(panic) 20 fmt.Println(f) 21 }
类型选择
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
- switch v := i.(type) {
- case T:
- // v 的类型为 T
- case S:
- // v 的类型为 S
- default:
- // 没有匹配,v 与 i 的类型相同
- }
类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type。
此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 T 或 S 的情况下,变量 v 会分别按 T 或 S 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 v 与 i 的接口类型和值相同。
1 package main 2 3 4 import "fmt" 5 6 7 func do(i interface{}) { 8 switch v := i.(type) {//类型选择:从几种类型断言中选择分支 9 case int: 10 fmt.Printf("Twice %v is %v\n", v, v*2) 11 case string: 12 fmt.Printf("%q is %v bytes long\n", v, len(v)) 13 default: 14 fmt.Printf("I don't know about type %T!\n", v) 15 } 16 } 17 18 19 func main() { 20 do(21)//Int型 21 do("hello")//string型 22 do(true)//布尔型,do()中没有匹配 23 }
Stringer
- type Stringer interface {
- String() string
- }
Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。
1 package main 2 3 import "fmt" 4 5 type Person struct { 6 Name string 7 Age int 8 } 9 10 11 func (p Person) String() string { 12 return fmt.Sprintf("%v (%v years)", p.Name, p.Age) 13 } 14 15 func main() { 16 a := Person{"Arthur Dent", 42} 17 z := Person{"Zaphod Beeblebrox", 9001} 18 //a,z分别是两个person类,接收String()方法 19 fmt.Println(a, z) 20 }
练习:Stringer
通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。
例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"。
1 package main 2 3 import "fmt" 4 5 type IPAddr [4]byte 6 7 // TODO: 给 IPAddr 添加一个 "String() string" 方法 8 9 func (ip IPAddr) String() string{ 10 return fmt.Sprintf("%v.%v.%v.%v",ip[0],ip[1],ip[2],ip[3]) 11 } 12 13 14 func main() { 15 hosts := map[string]IPAddr{ 16 "loopback": {127, 0, 0, 1}, 17 "googleDNS": {8, 8, 8, 8}, 18 } 19 for name, ip := range hosts { 20 fmt.Printf("%v: %v\n", name, ip) 21 } 22 }
浙公网安备 33010602011771号