golang 开荒 - 0 - 接口

接口是一组方法的签名(这里説签名,因为真的只有名字,没有实现)的集合,
一个结构体可以选择实现接口的方法,通过接口,一个数据可以被多个不同的结构体使用。这就是多态
因此,接口就是定义了对象的行为。

For example, a Dog can walk and bark. If an interface defines method signatures for walk and bark while Dog implements walk and bark methods, then Dog is said to implement that interface.

接口定义方法的名字,参数,和返回值。

在GO中不需要使用 implement 接口

type Shape interface {
    Area() float64
    Perimeter() float64
}

这个接口定义了两个方法,任何实现了这两个方法的 type (如结构体)都实现了这个叫Shape 的接口

静态接口和动态接口

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

func main() {
	var s Shape
	fmt.Println("value of s is", s)
	fmt.Printf("type of s is %T\n", s)
}

上面代码会打印出0 和 nil.
这是因为我们定义了type Shape, 但并没有赋值
注意这个type!!!, 它既可以是结构体,也可以是接口
A variable of an interface type can hold a value of a type that implements the interface. The value of that type becomes the dynamic value of the interface and that type becomes the dynamic type of the interface.

当我们用println. 打印时,调用的是接口的动态值

  • 实际上,接口s的类型是shape, 是他的静态type

实现接口

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

// 这里的结构体  实现了 Shape 接口
func (r Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func main() {
	var s Shape
	s = Rect{5.0, 4.0}
	r := Rect{5.0, 4.0}
	fmt.Printf("type of s is %T\n", s)
	fmt.Printf("value of s is %v\n", s)
	fmt.Println("area of rectange s", s.Area())
	fmt.Println("s == r is", s == r)
}
// 结果如下 
type of s is main.Rect
value of s is {5 4}
area of rectange s 20
s == r is true

这里的先创建接口Shape 和 结构体 Rect, 然后Rect实现了接口的方法 Area() Perimeter(),
这种实现都是** 自动发生的
下面就是有意思的事情了 :
一个type 实现了一个接口, 他本身的类型是不变的, 但是同时
习得了这个接口的功能**。

比如张三学会了跳舞,他还是张三,但是他可以进行跳舞的活动了! 所以上面有 s == r .

这里的 type 是很灵活的,可以转换为其他的type , 包括struct 或 interface

空接口 interface{}

没有方法的接口, 正是因为如此, 所有的type 都实现了这个接口
下面用fmt.Println() 来描述空接口的作用, 我们来看下这个函数的签名:

func Println(a …interface{}) (n int, err error)
可见他接受空接口作为参数

package main

import "fmt"

type MyString string

type Rect struct {
	width  float64
	height float64
}

func explain(i interface{}) {
	fmt.Printf("value given to explain function is of type '%T' with value %v\n", i, i)
}

func main() {
	ms := MyString("Hello World!")
	r := Rect{5.5, 4.5}
	explain(ms)
	explain(r)
}

知道为什么把 **i interface{}**做为参数吗,因为任务变量都 实现了空接口, 所以任何type都等于空接口!

实现多个接口

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	c := Cube{3}
	var s Shape = c
	var o Object = c
	fmt.Println("volume of s of interface type Shape is", s.Area())
	fmt.Println("area of o of interface type Object is", o.Volume())
}

上面建立两个接口Shape, Object. 而结构体Cube同时实现了这两个接口, 则 var s Shape = c
var o Object = c 两个声明都是对的。

type断言 (type assertion)

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	var s Shape = Cube{3}
	c := s.(Cube)
	fmt.Println("area of c of type Cube is", c.Area())
	fmt.Println("volume of c of type Cube is", c.Volume())
}

这里 shape 类型, 的动态值是Cube 类型, 通过这个语法, 我们把Cube这个类型“提取”出来, c:=s.(Cube)
这样c 就可以使用Area() 和 Volume()方法了,

要注意的是, 使用语法 c := s.(Cube) 时, s中要有Cube的类型,如果Cube没有实现s接口的话,就会报错,
简单的説, s要“即是s,也是Cube”!

而如果没有给 s 添加 Cube 的值, 比如只有下面代码

	var s Shape
	c := s.(Cube)

则 ** c := s.(Cube)**会报错:

panic: interface conversion: main.Shape is nil, not main.Cube

为了避免这种错误,可以使用下面语法, 防止 panic

c, ok := s.(Cube)

我们还可以用类型断言的方式来检测一个type是否实现了某些接口:
如果断言一个type是不是接口,Golang就会检测他是否实现了该接口的方法

	var c Cube= Cube{3}
	check1 := c.(Object)
	check12 := c.(Shape)
package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Skin interface {
	Color() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	var s Shape = Cube{3}
	value1, ok1 := s.(Object)
	fmt.Printf("dynamic value of Shape 's' with value %v implements interface Object? %v\n", value1, ok1)
	value2, ok2 := s.(Skin)
	fmt.Printf("dynamic value of Shape 's' with value %v implements interface Skin? %v\n", value2, ok2)
}
  • 我们需要类型断言来判断一个type是否有另一个type的属性,如果有,我们就可以放心用那个type的属性了。(前提是先把这个type提取出来, : value1, ok1 := s.(Object) )

总而言之,类型断言的作用有两个:

  1. 检测一个接口是否有其他的type 的值
  2. 一个接口转换为另一个接口

类型转换

这里的type是保留字
这里的 i interface{} 表示任何类型传入的值


func explain(i interface{}) {
	switch i.(type) {
	case string:
		fmt.Println("i stored string ", strings.ToUpper(i.(string)))
	case int:
		fmt.Println("i stored int", i)
	default:
		fmt.Println("i stored something else", i)
	}
}

func main() {
	explain("Hello World")
	explain(52)
	explain(true)
}

// 执行结果 
i stored string  HELLO WORLD
i stored int 52
i stored something else true

接口的嵌入

go 中接口不能实现其他接品, 也不能继承其他接口

但我们可以通过 合并 接口来实现类似的功能

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Material interface {
	Shape
	Object
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	c := Cube{3}
	var m Material = c
	var s Shape = c
	var o Object = c
	fmt.Printf("dynamic type and value of interface m of static type Material is'%T' and '%v'\n", m, m)
	fmt.Printf("dynamic type and value of interface s of static type Shape is'%T' and '%v'\n", s, s)
	fmt.Printf("dynamic type and value of interface o of static type Object is'%T' and '%v'\n", o, o)
}

上面代码,Cube结构体实现了 Area() , Volue() 方法,所以实现了 Shape 和 Object 接口。

同时因为 Material 接口是 Shape 和 Object 合并的, 所以 Cube 也实现了 Material 接口。

指针与值接收 Pointer vs Value receiver : 接口函数的实现,可以接受指针参数吗

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

func (r *Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func main() {
	r := Rect{5.0, 4.0}
	var s Shape = r
	area := s.Area()
	fmt.Println("area of rectangle is", area)
}

上面代码中, 函数Area 接收 *Rect , 所以接收器会得到一个指向type Area 的指针,但是运行会报编译错误

提示 Rect 没有实现 Shape接口的方法?

./prog.go:25:6: cannot use r (type Rect) as type Shape in assignment:
	Rect does not implement Shape (Area method has pointer receiver)

问题出在这里:

r := Rect{5.0, 4.0}
// 下面这行会报错
var s Shape = r

r 没法传给 Shape
仔细看有这样的提示 : Area() 方法有一个指针接受者
也就是説实现 Area() 方法的不的 r , 而是 r 的指针, 就是 &r
上面Area() 方法的receiver 实际上是 *Rect

原因: 接口有点不同于函数,函数的 value receiver 和 pointer receier
深层原因:
一个method , 他的receiver 不管是值还是指针,都是固定的,值到指针或指针到值的转换也都是稳定的。
然而,对于 interface, 值和指针都是可变的。

要解决这个问题,要改用如下写法:

	var s Shape = &r // assigned pointer
	area := s.Area()

接口的对比

接口可以用 == 和 != 做比较,

  1. 对于空接口,彼此都是相等的
var a, b interface{}
fmt.Println( a == b ) // true
  1. 对于非空接口,则他们的所有value 的值 和type都是相等的时候两个接口才相等

  2. 对于一些不可比较的值,如不同的数据,slice, function, map 等,比较则会抛出异常

  3. 如果一个interface 是 nil, 则 == 判断永远返回false

接口的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值