在Go中一个接口被定义为特定值预期具体的一组方法,后面跟一个{},内部有一组方法,以及方法期望参数和返回值.
可以把接口看作需要struct实现的一组行为.
接口定义type myInterface interface {
method() // 无参数无返回值的方法
methodWithParam(float64) // 带参数的方法
methodWithReturn() string // 带返回值的方法
}
一个拥有接口定义的所有方法的类型被称作满足那个接口.一个满足接口的类型可以用在任何需要接口的地方.
方法名,参数类型(可选)和返回值(可选)都需要与接口定义的一致.除了接口中列出的方法之外,类型还可以有更多的方法,但它不能缺少接口中的任何方法,否则就不满足那个接口.
一个类型可以满足多个接口,一个接口可以有多个类型满足它.
定义满足接口的类型下面的例子定义了有三个方法的接口,然后定义了几个类型,正好可以满足该myInterface .
为了满足myInterface 接口,需要有三个方法.
然后我们声明另外一个类型,MyType.这个例子中,MyType的基础类型并不重要,我们就用string,为了MyType满足myInterface,我们定义了接口需要的所有方法,另外包含一个并不属于接口的额外的方法.
type Mtinterface interface {
Method()
MethodWithArg(int)
MethodWithReturn() string
}
type MyType string
func (m MyType) Method() {
fmt.Println("method")
}
func (m MyType) MethodWithArg(n int) {
fmt.Println("method", n)
}
func (m MyType) MethodWithReturn() string {
fmt.Println("method with return")
return "9"
}
func (m MyType) Me() {
fmt.Println("me")
}
如果声明了一个指针类型的接收器方法,只能将那个类型的指针传递给接口变量
当Go判断值是否满足一个接口的时候,指针方法并没有包含直接的值.它包含了指向那个值的地址.解决方法是将一个指向该值的指针赋值给被调用的变量,来代替直接的地址.
type Animal interface {
Sound()
}
type Cat struct {
Name string
}
// 指针类型的接收器方法
func (c *Cat) Sound() {
fmt.Println("miao")
}
// 该函数需要传递指针类型的参数
func Sound(a *animals.Cat) {
a.Sound()
}
func main() {
var c animals.Cat
c.Name = "小花"
Sound(&c)
}
具体类型和接口类型
一个具体类型既定义了
- 它的值可以做什么:可以调用哪些方法.
- 它是什么:它定义了保存值的数据的基础类型.
接口类型并不描述是哪个值,
- 它不说它的基础类型是什么,或者数据是如何存储的.
- 它们仅仅描述了这个值能做什么.它有哪些方法.
当你有一个接口类型的变量时,它可以保存满足此接口的任何类型的值.
假设我们有Cat和Dag类型,它们都满足Sound方法.我们可以创建一个Aninal接口来代替声明了Sound方法的所有类型
如果我们定义了 Animal类型的变量animal后,可以把Dag和Cat赋值给它.甚至之后再定义的任何类型,只要它有Sound方法,都可以.
我们可以调用 任何赋值给aninal变量的值上的Sound方法.虽然我们并不知道 aninal保存值的具体类型是什么,但我们知道它能做什么:Sound.如果它的类型没有Sound方法,那么它不满足Animal接口.我们就不能给它赋值.
aninal/a.go
package animals
import "fmt"
type Animal interface {
Sound()
}
type Cat struct {
Name string
}
// 具有Sound方法
func (c *Cat) Sound() {
fmt.Println("miao")
}
type Dog struct {
Name string
}
// 具有Sound方法
func (d *Dog) Sound() {
fmt.Println("wang")
}
main.go
package main
import (
"fmt"
"inter/animals"
)
func main() {
fmt.Println("hello")
// 再次复习指针类型的接收器
var animal animals.Animal
animal = &d
animal.Sound() // wang
animal = &c
animal.Sound() // miao
}
函数的参数也可以是接口类型.函数的参数也是变量.如果声明一个Sound的函数来接收Aninal类型,我们可以传入任何包含了Sound的方法的值来发声.
接口类型的名称首字母大写则是可以导出的.否则不能被导出
类型断言我们在类型转换中提到过类型断言.现在讲一讲细节
在上面的例子中,我们在Dog类型中增加一个Swamming方法.由于Cat不能swaaming所以不会有这个方法.
我们再添加一个函数Action,传入一个Animal接口变量,让它先调用Sound再调用Swamming.由于Cat类型没有Swamming方法,调用会失败.因为我们只有一个Animal的值,并没有Cat的值,Animal值上没有Swamming方法.
在VScode中直接提示报错,aninal中未定义Swamming方法.
我们需要一个方法来取回具体类型的值,就是那个确定含有Swamming方法的值.
根据第类型转换的指引,们是不是可以把它用类型转换来处理呢?
以上是直接使用类型转换,并不能编译通过,仍然报错.
类型断言能让你从接口类型的值取回具体类型的值.它的语法像是函数和类型转换的合体.在一个接口值后面.输入一个点.接一对(),括号中间是具体类型
// 指针类型接收器,必须要断言为指针类型
func Action(a animals.Animal) {
a.Sound()
dog, ok := a.(*animals.Dog) // 第二个参数表示类型断言是否成功
if ok {
dog.Swamming()
}
}
// 非指针类型接收器
func MyAction(i animals.MyInterface) {
my, ok := i.(animals.MyType) // 第二个参数表示类型断言是否成功
if ok {
my.Me()
}
}