一. 匿名字段
go支持只提供类型而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
- 同名字段的情况
- 所以自定义类型和内置类型都可以作为匿名字段使用
- 指针类型匿名字段
二.接口
接口定义了一个对象的行为规范,但是定义规范不实现,由具体的对象实现规范的细节。
2.1 接口类型
在Go语言中接口(interface)是一种类型,一种抽象类型。
interface是一组method的集合,接口做的事情就像定义一个协议,不关心属性(数据),只关心行为(方法)。
2.2 为什么使用接口
查看下面的图片,只是由于类型不同,就需要定义两个逻辑一样的函数。如果后面出现了其它动物,也会需要定义函数。
Go语言为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一个抽象类型。当你看到一个接口类型是,你不知道他是什么,唯一知道的是通过它的方法能做什么。
2.3 接口定义
Go语言提倡面向接口编程。
- 接口在底层实现上包含两部分,即类型(type)和数据(data)。
- 接口是一个或多个方法签名的集合
- 任何类型的方法集中只要拥有该接口对应的全部方法,就表示它实现了该接口,无须在该类型上显示声明实现了那个接口。这称为Structural Typing。
- 所谓对应方法,是指有相同名称,参数列表(不包括参数名)以及返回值。
- 当然,该类型还可以有其它方法。
- 接口只有方法声明,没有实现,没有数据字段(属性)。
- 接口可以匿名嵌入其它接口,或者嵌入到其它结构中。
- 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针。即如果对象是结构体或者基本数据类型,它会被值拷贝到接口中。如果对象是指针类型,这个指针指向的结构体实现了接口,那么接口中存储的是指针的副本,而不是指针本身。
- 只有当接口存储的类型和对象都为nil时,接口才为nil。
- 接口调用不会做receiver的自动转换。
- 接口同样支持匿名字段方法。
- 接口可以实现类似面向对象编程(OOP)的多态。
- 空接口可以作为任何类型数据的容器。空接口是没有声明任何方法的接口。但是,也无法通过空接口来调用对象的方法或访问其属性。
- 一个类型可以实现多个接口。
- 接口命名习惯以er结尾。
每一个接口由数个方法组成,接口的定义格式如下:
type 接口类型名 interface{
方法1(参数列表1)返回值列表1
方法2(参数列表2)返回值列表2
...
}
其中:
- 接口名:使用type将接口名定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,接口名最好要能突出该接口的类型含义。
- 方法名:当方法名首字母是大写且接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表和返回值列表:参数列表和返回值列表中的参数变量名可以省略。
举个例子:
type writer interface{
Write([]byte) error
}
当看到这个接口的时候,你不知道他是什么,唯一知道的是可以通过它的Write方法来做一些事情。
2.4 实现接口的条件
一个对象只要全部实现了接口的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
package main
import "fmt"
type Sayer interface {
Say()
}
type Cat struct{}
//实现了Sayer接口
func (c *Cat) Say() {
fmt.Println("喵喵喵...")
}
type Dog struct{}
//实现了Sayer接口
func (d *Dog) Say() {
fmt.Println("汪汪汪...")
}
2.5 接口类型变量
接口类型变量能够存储所有实现了该接口的实例。
需要指针传入是因为方法的receiver为指针类型,对象的指针类型(*T)的方法集为值类型(T)和指针类型(*T)。
2.6 值接收者和指针接收者实现接口的区别
值接收者和指针接收者实现接口区别在于:方法集的不同。接口接收值类型(T)对象方法集为值接收者(T)实现的方法。接口接收指针类型对象(*T)方法集为值接收者(T)和指针接收者(*T)现象的方法。
- 值接收者
- 指针接收者
2.7 类型与接口的关系
2.7.1 一个类型实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
2.7.2 多个类型实现同一个接口
Go语言中不同的类型还可以实现同一接口。
一个接口的方法不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其它类型或者结构体来实现。
2.7.3 接口嵌套
接口与接口之间可以通过嵌套创造出新的接口。
type Sayer interface {
Say()
}
type Mover interface {
Move()
}
type Animal interface {
Sayer
Mover
}
嵌套的接口的使用和普通接口一样,这里实现嵌套的接口。
2.8 空接口
2.8.1 空接口的定义
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型变量可以存储任意类型的变量。
2.8.2 空接口的应用
-
空接口作为函数参数
使用空接口实现可以接收任意类型的函数参数。
- 空接口作为map的值
使用空接口实现可以保存任意值的字典。
2.8.3 类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
- 接口值
一个接口的值(简称接口值)是由一个具体类型和具体类型的值俩个部分组成。这两部分分别称为接口的动态类型和动态值。
我们来看一个例子:
package main
import (
"bytes"
"io"
"os"
)
func main() {
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
}
图解:
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
- x:表示类型为interface{}的变量
- T :表示断言x可能是的类型
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个是布尔值,若为true表示断言成功,为false则表示断言失败。
举个例子:
自定义类型:
我们还可以使用switch语句来实现多个类型的断言:
因为空接口可以存储任意类型值的特点,所以空接口在Go语言中使用十分广泛。
但是接口需要注意的是,只有当有两个或者两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样会增加不必要的抽象,导致不必要的运行时消耗。