介绍
- 在Go语言中接口(interface)是一种类型,一种抽象的类型。
- 接口是一组方法签名(方法列表)。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。
接口的定义
- 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
举个例子 看到这个接口之后,不知道是什么类型,只知道实现了call()方法实现打电话功能(或者说做了一些事情)
type caller interface{
call()
}
接口的实现
- 只要实现的call方法 就算实现了caller接口
- caller 类型变量 可以存储 huawei xiaomi iphone 三个类型的变量
type caller interface {
call() // 只要实现了call方法的变量都是caller类型, 方法签名
}
type huawei struct{}
type xiaomi struct{}
type iphone struct{}
func (h huawei) call() {
fmt.Println("使用华为手机打电话")
}
func (x xiaomi) call() {
fmt.Println("使用华为手机打电话")
}
func (i iphone) call() {
fmt.Println("使用苹果手机打电话")
}
func da(c caller) {
// 接收一个参数,传进来什么,我就用什么打电话
c.call() // 集体实现call方法
}
func main() {
var x1 xiaomi
var h1 huawei
var p1 iphone
da(x1)//使用华为手机打电话
da(h1)//使用华为手机打电话
da(p1)//使用苹果手机打电话
var ss caller // 定义一个接口类型:speaker 的变量:ss
ss = x1
ss = h1
ss = p1
fmt.Printf("%T\n", ss)
}
值接收者和指针接收者
-
使用值接收者实现接口,结构体类型和结构体指针类型的变量都能存.(通过go的语法糖实现的 ,自动对指针求值),
两者都行
-
指针接收者实现接口只能存结构体指针类型的变量.
值类型不能用
type caller interface {
call() // 只要实现了call方法的变量都是caller类型, 方法签名
}
type huawei struct{}
func (h huawei) call() {
fmt.Println("使用华为手机打电话")
}
func da(c caller) {
// 接收一个参数,传进来什么,我就用什么打电话
c.call() // 集体实现call方法
}
func main() {
var h1 huawei
da(h1)//报错 call method has pointer receiver
}
接口嵌套
- 接口与接口间可以通过嵌套创造出新的接口。
- 嵌套得到的接口的使用与普通接口一样.
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// 接口嵌套
type animal interface {
Sayer
Mover
}
空接口
- 空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。也就是任意类型的变量都能保存到空接口中.
- 空接口类型的变量可以存储任意类型的变量。
- 没有必要起名字,通常定义成下面的格式(这种写法应该是个语法糖):
interface{} // 空接口 带{}才是 接口 interface是关键字
- 空接口可以作为函数参数
// 空接口作为函数参数 也可以看print的
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
-
空接口可以作为数据类型使用 (任意数据类型)
-
使用空接口可以实现 python的dict 和list 功能 里面放置任意类型的数据 万物皆对象不是····
func main() { //类似于python的字典 下面定义的是 key是字符串 值是任意值 空接口作为数据类型使用 var m1 map[string]interface{} m1 = make(map[string]interface{}, 16) m1["name"] = "张三" m1["age"] = 23 m1["merried"] = true m1["hobby"] = [...]string{"唱", "跳", "rap"} fmt.Println(m1) // map[age:23 hobby:[唱 跳 rap] merried:true name:张三] //类似于python的list 值是任意类型值 m2 := []interface{}{"str", 10, "北京"} fmt.Println(m2) // [str 10 北京] }
类型断言
- 一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
- 想要判断空接口中的值这个时候就可以使用类型断言
x.(T)
- x:表示类型为interface{}的变量
- T:表示断言x可能是的类型。
- 该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为
true
则表示断言成功,为false
则表示断言失败。 - 断言通俗讲是猜猜看 猜对类型返回正确值,
v := x.(type) 只能用在switch里面
举例
func main() {
var x interface{}
x = "中国加油!!!"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}
如果多个类型判断
func justifyType(x interface{}) {
switch v := x.(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!")
}
}
func main() {
justifyType(100)//x is a int is 100
}
注意:只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
小结
- 接口指定类型应具有的方法,类型决定如何实现这些方法。
- 当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。
- 结构体定义类型有什么属性,接口规定了实现什么方法,两者组合起来类似于其他方法的类。
接口类型的变量
可以存入实现了接口类型的变量
- 一个自定义类可以实现多个接口,而接口间彼此独立,不知道对方的实现
- 多个自定义类可以实现同一个接口
- 一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
- 接口类似于什么呢,比如说有几个不同的类,苹果手机,华为手机,小米手机,oppo等,它们都有相同的方法打电话。现在如果实现打电话功能。不用接口写法需要把判断一下手机品牌,在执行对应的打电话方法,使用接口之后,只要是实现了打电话功能的类,直接传入参数,调用打电话方法就可以了。