接口

接口类型 是由一组方法签名定义的集合
接口类型的变量可以保存任何实现了这些方法的值。
注意: 示例代码的 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

fmt 包中定义的 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 }

 

 
 
posted on 2022-08-02 19:14  Jolyne123  阅读(46)  评论(0)    收藏  举报