golang基础小记(16)——接口(interface):基本概念、接口嵌套、空接口、类型断言(获取接口底层值)

基本概念

定义

Go语言中接口(interface)是一种类型,一种抽象的类型。接口类型是由一组方法签名定义的集合。
接口的定义格式:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2}
  • 接口类型名:自定义,一般会在后面添加er,表明接口类型
  • 方法名首字母和接口类型名首字母都大写时,该方法可以被其他包访问
  • 参数列表和返回值列表中可以省略变量名,只保留类型名

实现接口

其它类型只要实现了一个接口的所有方法,那么就实现了这个接口。
示例:

// 定义一个包含say()方法的接口
type sayer interface {
	say()
}

type dog struct{}

type cat struct{}

// dog类型实现say()方法
func (d dog) say() {
	fmt.Println("汪~")
}

// cat类型实现say()方法
func (c cat) say() {
	fmt.Println("喵~")
}

func main() {
	var a sayer
	a = dog{}
	a.say() // 汪~
	a = cat{}
	a.say() // 喵~
}

接口类型变量可以存储所有实现该接口的类型变量。上例中,接口类型变量a既可以存储dog类型变量,又可以存储cat类型变量,因为dogcat类型都实现了该接口的所有方法。
有了接口,所有拥有相同方法的自定义类型都可以抽象的用接口类型表示,那么在代码中,如果要对这些自定义类型变量进行相同的操作,就不需要逐个定义和操作,只需要定义一个接口类型变量和操作,就可以适用于所有实现该接口的类型变量。
示例:

type sayer interface {
	say()
}

type dog struct{}

type cat struct{}

func (d dog) say() {
	fmt.Println("汪~")
}

func (c *cat) say() {
	fmt.Println("喵~")
}

func speak(s sayer) {
	s.say()
}

func main() {
	d := &dog{}
	c := &cat{}
	speak(d) // 汪~
	speak(c) // 喵~
}

上例中的speak函数以接口类型变量作为参数,所有实现该接口的类型变量都可以使用该函数,减少了代码的冗余。

接口值

  • 接口也是值。它们可以像其它值一样传递。
  • 接口值可以用作函数的参数或返回值。
  • 在内部,接口值可以看做包含值和具体类型的元组:(value, type)。接口类型变量还未赋值时,valuetype都是nil
  • 接口值保存了一个具体底层类型的具体值。
  • 接口值调用方法时会执行其底层类型的同名方法。

示例接上例:

var a sayer
fmt.Printf("%v, %T\n", a, a) // <nil>, <nil>
a = &dog{}
fmt.Printf("%v, %T\n", a, a) // &{}, *main.dog

方法中使用值接收者和指针接收者的区别

值接收者

接着上例,dog类型的say方法使用的是值接收者,尝试分别用值类型和指针类型的dog类型值给接口类型变量赋值,结果如下:

var a sayer
a = dog{}
a.say() // 汪~
a = &dog{}
a.say() // 汪~

可以发现,使用值接收者时,值类型和指针类型的dog类型值都可以赋值给接口变量。这是因为Go语言在编译时会自动给指针求值。

指针接收者

还是上例,将cat类型的say方法改成使用指针接收者:

func (c *cat) say() {
	fmt.Println("喵~")
}

尝试分别用值类型和指针类型的cat类型值给接口类型变量赋值,结果如下:

a = &cat{} // 正常接收
a.say()    // 喵~
a = cat{}  // 不能接收
a.say()

可以发现,使用指针接收者时,只能使用指针类型的cat类型值给接口类型变量赋值。

类型与接口的关系

  • 一个类型可以实现多个接口。因为一个类型只要实现一个接口的所有方法,就实现了这个接口。
  • 多个类型实现同一接口。上面的例子中,dog类型和cat类型都实现了sayer接口的所有方法,所以它们都实现了sayer接口。
    通过嵌套匿名结构体可以让结构体"继承"匿名结构体的方法(详情见golang基础小记(14)中的嵌套结构体),那么一个接口的方法,不一定需要由一个类型完全实现,而可以通过在类型中嵌入其他类型或者结构体来实现。

示例:

扫描二维码关注公众号,回复: 11552689 查看本文章
// 实现washingMachine接口需要实现wash()和dry()接口
type washingMachine interface {
	wash()
	dry()
}

type dryer struct{}

// dryer类型实现了dry()方法
func (d dryer) dry() {
	fmt.Println("甩一甩")
}

// haier类型嵌套了匿名结构体,此时haier类型拥有了dryer类型的dry()方法
type haier struct {
	dryer
}

// haier类型实现了wash()方法
func (h haier) wash() {
	fmt.Println("洗刷刷")
}

func main() {
	a := haier{dryer{}}
	// 可以看到haier类型的变量能够直接使用dryer类型的dry()方法
	a.dry() // 甩一甩
	var b washingMachine
	// 因为haier类型能直接使用dry()方法和wash()方法,所以其实现了washingMachine接口
	b = a
	b.dry() // 甩一甩
}

所以,对于接口来说,一个类型如何实现该接口需要的所有方法并不重要,只要该类型变量可以直接使用这些方法,那么这个类型就实现了该接口。

接口嵌套

直接给例子:

type washer interface {
	wash()
}

// washingMachine接口嵌套了washer接口
type washingMachine interface {
	washer
	dry()
}

type dryer struct{}

func (d dryer) dry() {
	fmt.Println("甩一甩")
}

func (d dryer) wash() {
	fmt.Println("洗刷刷")
}

func main() {
	a := dryer{}
	var b washingMachine
	b = a // dryer类型实现了washingMachine接口
	b.dry()  // 甩一甩
	b.wash() // 洗刷刷
}

接口可以嵌套接口,那么如果一个类型想实现这个接口,不仅要实现该接口包含的方法,也要实现嵌套接口包含的方法。比如一堆类型既实现了a接口,又实现了b接口,这时就可以利用接口嵌套创造新的接口c,其嵌套了a接口和b接口,那么这堆类型的变量就都可以用接口c类型的变量来存储了。

空接口

定义

指定了零个方法的接口被称为空接口。
格式:interface {}
空接口可以保存任何类型的值。
示例:

type emptyer interface{}

func main() {
	var a emptyer
	a = "爱中国"
	fmt.Printf("%T %v\n", a, a) // string 爱中国
	a = 15
	fmt.Printf("%T %v\n", a, a) // int 15
	a = true
	fmt.Printf("%T %v\n", a, a) // bool true
}

应用1——作为函数参数

// show函数可以接收任意类型的参数
func show(e interface{}) {
	fmt.Printf("%T %v\n", e, e)
}

func main() {
	a := 15
	show(a) // int 15
	b := false
	show(b) // bool false
	c := "爱中国"
	show(c) // string 爱中国
}

应用2——作为map的值

// m的值可以是任意类型
var m map[int]interface{}
m = make(map[int]interface{}, 3)
m[1] = 5
m[2] = true
m[3] = "爱中国"
m[4] = []int{1, 2, 3}
fmt.Println(m) // map[1:5 2:true 3:爱中国 4:[1 2 3]]

类型断言

类型断言提供了访问接口底层具体值的方式。
格式:

v := i.(T)

该语句断言接口类型变量 i保存了具体类型 T,并将其底层类型为 T的值赋予变量v。若i并未保存 T类型的值,该语句就会触发一个panic
如果不想触发panic,需要让类型断言返回两个值:底层值以及报告断言是否成功的布尔值。
格式:

v, ok := i.(T)

i保存了一个 T,那么 v将会是其底层值,而 oktrue
否则,ok 将为falsev 将为 T 类型的零值,程序并不会产生panic
示例:

// e是空接口类型变量,让其存储一个string类型的值
var e interface{}
e = "爱中国"
v1, ok1 := e.(string) // 断言底层类型是string类型
fmt.Println(v1, ok1)  // 爱中国 true
v2, ok2 := e.(int)    // 断言底层类型是int类型
fmt.Println(v2, ok2)  // 0 false

可以用switch实现多次断言:

var e interface{}
e = "爱中国"
// .(type)只能用在switch中
switch v := e.(type) {
case string:
	fmt.Printf("x is a string,value is %v\n", v)
case int:
	fmt.Printf("x is a int is %v\n", v)
case bool:
	fmt.Printf("x is a bool is %v\n", v)
default:
	fmt.Println("unsupport type!")
} // x is a string,value is 爱中国

参考1
参考2

猜你喜欢

转载自blog.csdn.net/m0_37710023/article/details/107746887