一、接口是什么、为什么需要接口
是什么:接口(interface)是一组仅包含方法名、参数、返回值的未具体实现的方法的集合。接口只定义规范而不去实现,细节由具体的对象来实现。golang中接口是一种抽象的类型。
为什么:大多情况下,一些数据可能包含不同的类型,但是可能会有一个或者多个共同点,这些共同点就是抽象的基础。Go的接口类似c++的多态,接口相当于实现一个父类,然后派生类具体实现父类的虚函数,最后通过父类指针指向不同的派生类对象去调用那个派生类对象的虚函数,从而实现多态。举例说明一下,比如三角形,四边形,圆形都能计算面积,那就可以在图形接口中写一个计算面积的方法,然后每一种图形都可以具体去实现它,这样就能计算自己的面积(每种图形计算面积的公式不同)了,大概就是这个意思。
二、接口的定义
格式如下:
type 接口名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
说明:
1.接口名:用type将接口自定义的一个类型名。Go语言的接口名单词后面通常会加 er,比如写操作的接口叫 Writer 等。接口名要尽量突出接口类型的含义。
2.方法名:当方法名的首字母以及该接口名首字母都是大写时,那么这个方法可以被接口所在的包(package)之外的代码访问。
3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可省略。
代码演示:
//定义一个Sayer接口
type Sayer interface {
say()
}
type dog struct{}
type cat struct{}
//实现接口中具体的方法
func (d dog) say() {
fmt.Println("狗:汪汪")
}
func (c cat) say() {
fmt.Println("猫:喵喵")
}
func main() {
d := dog{} //实例化结构体
d.say()
c := cat{}
c.say()
}
三、值接收和指针接收 实现接口的区别
代码演示:
//定义一个Sayer接口
type Sayer interface {
say()
}
type dog struct{}
type cat struct{}
//值类型接收者实现接口
func (d dog) say() {
fmt.Println("狗:汪汪")
}
//指针类型接收者实现接口
func (c *cat) say() {
fmt.Println("猫:喵喵")
}
func main() {
var x Sayer
d1 := dog{}
x = d1 //x可以接受dog类型
x.say()
d2 := &dog{}
x = d2 //x可以接受*dog类型
x.say()
c1 := cat{}
x = c1 //报错,x不可以接受cat类型
x.say()
c2 := &cat{}
x = c2 //x可以接受*cat类型
x.say()
}
分析:
1、使用值接收实现接口后,不管是值类型的 dog 结构体还是指针类型的 *dog 都可以赋值给该接口变量。
2、使用指针接收实现接口后,只能将指针类型的 *cat 赋值给该接口变量,而值类型的 cat 不能赋值。
四、类型与接口的关系
1、一个类型实现多个接口
一个类型可以同时实现多个接口,且接口间相互独立,彼此不知道对方如何实现。 举例,狗会叫,狗也会跳。此时可以分别定义 Sayer 接口和 Jumper 接口。
//定义一个Sayer接口
type Sayer interface {
say()
}
//定义一个Jumper接口
type Jumper interface {
jump()
}
type dog struct{}
func (d dog) say() {
fmt.Println("狗会叫:汪汪")
}
func (d dog) jump() {
fmt.Println("狗会跳:跳跳")
}
func main() {
d1 := dog{}
d1.say()
d1.jump()
}
2、多个类型实现同一个接口
例如狗可以动,车同样也可以动,go支持多个类型实现相同的接口。
//定义一个Mover接口
type Mover interface {
move()
}
type dog struct{
name string
}
type car struct{
logo string
}
//值类型接收者实现接口
func (d dog) move() {
fmt.Printf("%s会跑\n", d.name)
}
//指针类型接收者实现接口
func (c car) move() {
fmt.Printf("%s豪车会跑,速度杠杠的\n", c.logo)
}
func main() {
d := dog{name:"小强"}
d.move()
c:= car{logo: "奔驰"}
c.move()
}
五、接口的嵌套
新接口可以由原来的接口嵌套得到。
type Jumper interface {
jump()
}
type Sayer interface {
say()
}
type animal interface {
Jumper
Sayer
}
type dog struct{
name string
}
func (d dog) jump() {
fmt.Printf("%s会跳\n", d.name)
}
func (d dog) say() {
fmt.Printf("%s会叫\n", d.name)
}
func main() {
var x animal
x = dog{name: "小强"}
x.jump()
x.say()
}
六、空接口
1、空接口的定义和使用
空接口就是没定义任何方法的接口。也就是说任何类型都实现了空接口。空接口类型的变量可以存储任意类型的变量。
举例:
func main() {
var x interface{} // 定义一个空接口
s := "zhang"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 10
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
由于空接口能够接受任意类型的变量,在go中,空接口常用作函数的参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
func main() {
s := "zhang"
show(s)
b := true
show(b)
}
2、类型断言
如前面所说空接口可以接受任意类型的变量,既然如此,那如何才能知道空接口接收到的具体的值和值得类型呢?此时需要用到类型断言
语法格式: x.(T),其中:
x:类型为 interface{} 的变量
T:断言 x 可能是的类型
该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言成功,为 false 则表示断言失败。
举例:
func main() {
var x interface{}
x = 888
switch val := x.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", val)
case int:
fmt.Printf("x is a int,value is %v\n", val)
case bool:
fmt.Printf("x is a bool,value is %v\n", val)
default:
fmt.Println("unsupport type!")
}
}