19

Go 语言接口详解(二)

 4 years ago
source link: https://www.tuicool.com/articles/jUJvuez
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

fumE3ya.gif

这是『就要学习 Go 语言』系列的第 20 篇分享文章

提醒 :文末给大家留了小练习,可以先看文章,再做练习,检验自己的学习成果!

我们接着上一篇,继续讲接口的其他用法。

实现多个接口

一种类型可以实现多个接口,来看下例子:

 1type Shape interface {
 2    Area() float32
 3}
 4
 5type Object interface {
 6    Perimeter() float32
 7}
 8
 9type Circle struct {
10    radius float32
11}
12
13func (c Circle) Area() float32 {
14    return math.Pi * (c.radius * c.radius)
15}
16
17func (c Circle) Perimeter() float32 {
18    return 2 * math.Pi * c.radius
19}
20
21func main() {
22    c := Circle{3}
23    var s Shape = c
24    var p Object = c
25    fmt.Println("area: ", s.Area())
26    fmt.Println("perimeter: ", p.Perimeter())
27}

输出:

1area:  28.274334
2perimeter:  18.849556

上面的代码,结构体 Circle 分别实现了 Shape 接口和 Object 接口,所以可以将结构体变量 c 赋给变量 s 和 p,此时 s 和 p 具有相同的动态类型和动态值,分别调用各自实现的方法 Area() 和 Perimeter()。

我们修改下程序:

1fmt.Println("area: ", p.Area())
2fmt.Println("perimeter: ", s.Perimeter())

编译会出错:

1p.Area undefined (type Object has no field or method Area)
2s.Perimeter undefined (type Shape has no field or method Perimeter)

为什么?因为 s 的静态类型是 Shape,而 p 的静态类型是 Object。那有什么解决办法吗?有的,我们接着看下一节

类型断言

类型断言可以用来获取接口的底层值,通常的语法: i.(Type) ,其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。

 1type Shape interface {
 2    Area() float32
 3}
 4
 5type Object interface {
 6    Perimeter() float32
 7}
 8
 9type Circle struct {
10    radius float32
11}
12
13func (c Circle) Area() float32 {
14    return math.Pi * (c.radius * c.radius)
15}
16
17func (c Circle) Perimeter() float32 {
18    return 2 * math.Pi * c.radius
19}
20
21func main() {
22    var s Shape = Circle{3}
23    c := s.(Circle)
24    fmt.Printf("%T\n",c)
25    fmt.Printf("%v\n",c)
26    fmt.Println("area: ", c.Area())
27    fmt.Println("perimeter: ", c.Perimeter())
28}

输出:

1main.Circle
2{3}
3area:  28.274334
4perimeter:  18.849556

上面的代码,我们可以通过 c 访问接口 s 的底层值,也可以通过 c 分别调用方法 Area() 和 Perimeter(),这就解决了上面遇到的问题。

在语法 i.(Type) 中,如果 Type 没有实现 i 所属的接口,编译的时候会报错;或者 i 的动态值不是 Type,则会报 panic 错误。怎么解决呢?可以使用下面的语法:

1value, ok := i.(Type)

使用上面的语法,Go 会自动检测上面提到的两种情况,我们只需要通过变量 ok 判断结果是否正确即可。如果正确,ok 为 true,否则为 false,value 为 Type 对应的零值。

类型选择

类型选择用于将接口的具体类型与各种 case 语句中指定的多种类型进行匹配比较,有点类似于 switch case 语句,不同的是 case 中指定是类型。

类型选择的语法有点类似于类型断言的语法:i.(type),其中 i 是接口,type 是固定关键字,使用这个可以获得接口的具体类型而不是值,每一个 case 中的类型必须实现了 i 接口。

 1func switchType(i interface{}) {
 2    switch i.(type) {
 3    case string:
 4        fmt.Printf("string and value is %s\n", i.(string))
 5    case int:
 6        fmt.Printf("int and value is %d\n", i.(int))
 7    default:
 8        fmt.Printf("Unknown type\n")
 9    }
10}
11func main() {
12    switchType("Seekload")
13    switchType(27)
14    switchType(true)
15}

输出:

1string and value is Seekload
2int and value is 27
3Unknown type

上面的代码应该很好理解,i 的类型匹配到哪个 case ,就会执行相应的输出语句。

注意:只有接口类型才可以进行类型选择。其他类型,例如 int、string等是不能的:

1i := 1
2switch i.(type) {
3case int:
4    println("int type")
5default:
6    println("unknown type")
7}

报错:

1cannot type switch on non-interface value i (type int)

接口嵌套

Go 语言中,接口不能去实现别的接口也不能继承,但是可以通过嵌套接口创建新接口。

 1type Math interface {
 2    Shape
 3    Object
 4}
 5type Shape interface {
 6    Area() float32
 7}
 8
 9type Object interface {
10    Perimeter() float32
11}
12
13type Circle struct {
14    radius float32
15}
16
17func (c Circle) Area() float32 {
18    return math.Pi * (c.radius * c.radius)
19}
20
21func (c Circle) Perimeter() float32 {
22    return 2 * math.Pi * c.radius
23}
24
25func main() {
26
27    c := Circle{3}
28    var m Math = c
29    fmt.Printf("%T\n", m )
30    fmt.Println("area: ", m.Area())
31    fmt.Println("perimeter: ", m.Perimeter())
32}

输出:

1main.Circle
2area:  28.274334
3perimeter:  18.849556

上面的代码,通过嵌套接口 Shape 和 Object,创建了新的接口 Math。任何类型如果实现了接口 Shape 和 Object 定义的方法,则说类型也实现了接口 Math,例如我们创建的结构体 Circle。

主函数里面,定义了接口类型的变量 m,动态类型是结构体 Circle,注意下方法 Area 和  Perimeter 的调用方式,类似与访问嵌套结构体的成员。

使用指针接收者和值接收者实现接口

在前面我们都是通过值接收者去实现接口的,其实还可以通过指针接收者实现接口。实现过程中还是有需要注意的地方,我们来看下:

 1type Shape interface {
 2    Area() float32
 3}
 4
 5type Circle struct {
 6    radius float32
 7}
 8
 9type Square struct {
10    side float32
11}
12
13func (c Circle) Area() float32 {
14    return math.Pi * (c.radius * c.radius)
15}
16
17func (s *Square) Area() float32 {
18    return s.side * s.side
19}
20
21func main() {
22    var s Shape
23    c1 := Circle{3}
24    s = c1
25    fmt.Printf("%v\n",s.Area())
26
27    c2 := Circle{4}
28    s = &c2
29    fmt.Printf("%v\n",s.Area())
30
31    c3 := Square{3}
32    //s = c3
33    s = &c3
34    fmt.Printf("%v\n",s.Area())
35
36}

输出:

128.274334
250.265484
39

上面的代码,结构体 Circle 通过值接收者实现了接口 Shape。我们在方法那篇文章中已经讨论过了,值接收者的方法可以使用值或者指针调用,所以上面的 c1 和 c2 的调用方式是合法的。

结构体 Square 通过指针接收者实现了接口 Shape。如果将上方注释部分打开的话,编译就会出错:

1cannot use c3 (type Square) as type Shape in assignment:
2Square does not implement Shape (Area method has pointer receiver)

从报错提示信息可以清楚看出,此时我们尝试将值类型 c3 分配给 s,但 c3 并没有实现接口 Shape。这可能会令我们有点惊讶,因为在方法中,我们可以直接通过值类型或者指针类型调用指针接收者方法。

记住一点: 对于指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的 。但接口存储的具体值是不可寻址的,对于编译器无法自动获取 c3 的地址,于是程序报错。

关于接口的使用方法总结全都在这,希望这两篇文章能够给你带来帮助!

作业:

文章提到的类型断言: i.(Type) ,其中 i 是接口,Type 可以是类型或接口,如果 Type 是接口的话,表达式是什么意思呢?下面的程序输出什么?欢迎大家留言讨论!

 1type Shape interface {
 2    Area() float32
 3}
 4
 5type Object interface {
 6    Perimeter() float32
 7}
 8
 9type Circle struct {
10    radius float32
11}
12
13func (c Circle) Area() float32 {
14    return math.Pi * (c.radius * c.radius)
15}
16
17func main() {
18    var s Shape = Circle{3}
19    value1,ok1 := s.(Shape)
20    value2,ok2 := s.(Object)
21
22    fmt.Println(value1,ok1)
23    fmt.Println(value2,ok2)
24}

推荐阅读:

Go 语言接口详解(一)

教女朋友写方法

包罗万象的结构体 -- 就要学习 Go 语言

四月你好,继续努力

RneQVnZ.png!webRneQVnZ.png!webRneQVnZ.png!web

如果我的文章对你有所帮助,点赞、转发都是一种支持!

i2AFZvM.gif

2qu6JrM.jpg!web

你也「在看」吗?:point_down:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK