interface接口
概述
接口(interface)是 Go 语言中的一种类型,用于定义行为的集合,它通过描述类型必须实现的方法,规定了类型的行为契约。
interface把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
隐性实现
sh
1.Go 中没有关键字显式声明某个类型实现了某个接口。
2.只要一个类型实现了接口要求的所有方法,该类型就自动被认为实现了该接口。
实现接口的作用
如果A类型实现了B接口,由于接口也是类型,我们就可以声明B接口类型的变量来使用A类型的相关特性。
go
//传统变量的声明和使用
var 变量名 类型A = 类型A的值
//类型A实现了接口B后
/*
在实际编译过后,该变量就是类型A的变量
*/
var 变量名 类型B = 类型A的值
//类型C实现了接口B后
var 变量名 类型B = 类型C的值
接口的定义
接口定义使用关键字 interface,其中包含方法声明。
基本语法
go
type 接口类型名 interface{
//参数签名
方法名1(参数类型列表) 返回值类型
方法名2(参数类型列表) 返回值类型
...
}
接口定义举例
go
type Animaler interface{
GetName() string
GetAge() int
}
命名规范
- 使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
- 当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表和返回值列表中的参数变量名可以省略
接口的实现
一个实例对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
也可以理解为任意类型,只要他具备接口中的所有方法,那么该类型的实例就实现了接口。
举例
go
package interface_knowledge
import "fmt"
type Animaler interface{
GetName() string
GetAge() int
}
type Dog struct{
name string
age int
}
func (d Dog) GetAge() int{
return d.age
}
func (d Dog) GetName() string{
return d.name
}
func (d Dog) Say(){
fmt.Println("wang")
}
由于Dog结构体类型具备了接口Animal
中的所有方法,所以Dog结构体
实现了Animal接口。
指针类型方法
使用了指针类型接收者
如果我们实现接口时使用了指针类型接收者,则接口只能接收指针类型。
go
package interface_knowledge
import "fmt"
type Personer interface{
GetAge() int
GetName() string
}
type Student struct{
name string
age int
}
func (s *Student) GetAge() int{
return s.age
}
func (s Student) GetName() string{
return s.name
}
func GetPersonVal(){
var person Personer
/*
只要在实现接口的过程中使用了指针类型的接收者
则接口只能接收对应指针类型的变量
*/
/*
报错:
cannot use Student{} (value of type Student) as Personer value in assignment:
Student does not implement Personer (method GetAge has pointer receiver)
*/
// person = Student{}
//正确
person = &Student{}
fmt.Printf("接口实例的值为%#v\n",person)
}
运行结果
sh
接口实例的值为&interface_knowledge.Student{name:"", age:0}
没有使用指针类型接收者
如果没有使用指针类型接收者,则指针与非指针都可以。
go
package interface_knowledge
import "fmt"
type Personer interface{
GetAge() int
GetName() string
}
type Work struct{
salary int
name string
age int
}
func (w Work) GetAge() int{
return w.age
}
func (w Work) GetName() string{
return w.name
}
func GetWorkVal(){
var person Personer
person = Work{}
fmt.Printf("接口实例的值为%#v\n",person)
person = &Work{}
fmt.Printf("接口实例的值为%#v\n",person)
}
运行结果
sh
接口实例的值为interface_knowledge.Work{salary:0, name:"", age:0}
接口实例的值为&interface_knowledge.Work{salary:0, name:"", age:0}
接口的性质
接口本身就是一个动态类型,我们可以声明该类型的变量。
动态类型
如果一个类型A实现了接口B。那么接口B类型的变量X就可以接收A类型的值,编译过程中变量X的类型就是A。
举例
go
package interface_knowledge
import "fmt"
type Animaler interface{
GetName() string
GetAge() int
}
type Dog struct{
name string
age int
}
func (d Dog) GetAge() int{
return d.age
}
func (d Dog) GetName() string{
d.name = "yellow"
return d.name
}
type Cat struct{
NickName string
age int
weight int
}
func (c Cat) GetAge() int{
return c.age
}
func (c Cat) GetName() string{
c.NickName = "kitty"
return c.NickName
}
/*
Dog和Cat都实现了接口Animaler
*/
func GetVal(){
//此时变量anl的实际类型就是Cat
var anl Animaler = Cat{NickName: "kitty",age:25,weight: 255}
//此时变量myDog的实际类型就是Dog
var myDog Animaler = Dog{name:"yellow",age: 30}
fmt.Printf("anl的值为%#v\n",anl)
fmt.Printf("myDog的值为%#v\n",myDog)
}
结果
go
anl的值为interface_knowledge.Cat{NickName:"kitty", age:25, weight:255}
myDog的值为interface_knowledge.Dog{name:"yellow", age:30}
所以接口类型的变量的运行时类型与运行时值都是由赋值动态决定的,所以又叫做动态类型和动态值。
区别
go
func GetVal(){
//此时变量anl的实际类型就是Cat
var anl Animaler = Cat{NickName: "kitty",age:25,weight: 255}
//此时变量myDog的实际类型就是Dog
var myDog Animaler = Dog{name:"yellow",age: 30}
fmt.Printf("anl的值为%#v\n",anl)
fmt.Printf("myDog的值为%#v\n",myDog)
anl = Dog{name:"happy",age:20}
fmt.Printf("anl的值为%#v\n",anl)
}
结果
go
anl的值为interface_knowledge.Cat{NickName:"kitty", age:25, weight:255}
myDog的值为interface_knowledge.Dog{name:"yellow", age:30}
anl的值为interface_knowledge.Dog{name:"happy", age:20}
由此可以知道anl
的底层类型仍然是Animaler
,并没有因为赋值改变,所以他可以接收不同类型的值。
接口的零值
接口是个引用类型,在接口没有接受赋值时,接口类型变量的值为nil
.
举例
go
package interface_knowledge
import "fmt"
type Animaler interface{
GetName() string
GetAge() int
}
//接口的零值
func GetZero(){
var zero Animaler
fmt.Printf("接口类型变量的零值为%#v\n",zero)
}
结果
go
接口类型变量的零值为<nil>
注意
接口类型的零值虽然是nil
,但是此时已经分配了内存,所以可以直接赋值。
go
package interface_knowledge
import "fmt"
type Animaler interface{
GetName() string
GetAge() int
}
type Cat struct{
NickName string
age int
weight int
}
func (c Cat) GetAge() int{
return c.age
}
func (c Cat) GetName() string{
c.NickName = "kitty"
return c.NickName
}
//接口的零值
func GetZero(){
var zero Animaler
fmt.Printf("接口类型变量的零值为%#v\n",zero)
//给零值接口赋值
zero = Cat{}
fmt.Printf("赋值后变量的值为%#v\n",zero)
}
调用结果
go
接口类型变量的零值为<nil>
赋值后变量的值为interface_knowledge.Cat{NickName:"", age:0, weight:0}
接口的用法1--多态
应用场景
目前有如下代码:
go
type Dog struct{
name string
}
type Cat struct{
name string
}
//获得狗的名字
func GetNameDog(d Dog)string{
return d.name
}
//获取猫的名字
func GetNameCat(c Cat)string{
return d.name
}
这两个获取名字的方法相似度极高,我们想把他合并成一个方法,
go
func GetName(c Type) string{
return c.name
}
但是由于Dog
和Cat
是不同的类型,所以让传参出现了困难。
go
//传参
var dog Dog
GetNameDog(dog)
var Cat cat
GetNameCat(cat)
此时我们就可以用接口来解决这个问题。
实现接口
go
package interface_knowledge
import "fmt"
type Animaler interface{
GetName() string
GetAge() int
}
type Dog struct{
name string
age int
}
func (d Dog) GetAge() int{
return d.age
}
func (d Dog) GetName() string{
d.name = "yellow"
return d.name
}
type Cat struct{
NickName string
age int
weight int
}
func (c Cat) GetAge() int{
return c.age
}
func (c Cat) GetName() string{
c.NickName = "kitty"
return c.NickName
}
Dog
和Cat
都实现了接口Animaler
,则我们可以用Animaler
类型来接收Dog
和Cat
类型,即
go
func GetName(a Animaler) string{
return a.GetName()
}
调用
go
package main
import (
"fmt"
"go_learn/interface_knowledge"
)
func main(){
var dog interface_knowledge.Dog
str :=interface_knowledge.GetName(dog)
fmt.Printf("狗的名字为%#v\n",str)
var cat interface_knowledge.Cat
str1 := interface_knowledge.GetName(cat)
fmt.Printf("猫的名字为%#v\n",str1)
}
结果
go
狗的名字为"yellow"
猫的名字为"kitty"
由此我们就实现了用一个接口类型参数接收两个不同类型的其他参数。