interface就是接口的意思。从名字可以推测有:对外,适配的含义。Go的接口是一系列函数的集合,如果一个类型实现了该接口所有函数,则说该类型实现了该接口。Go中的接口有什么用呢?可以实现duck typing。通过接口可以实现类似面向对象 语言中的多态特性,接口类比成基类,实现接口的类型类比成派生类。在Go中,接口被广泛使用,通过定义一些公共的接口,能够给编码实现带来极大的便利。Go强调接口应该小巧,通过多个小接口组合成大接口,这与Go强调组合的理念不谋而合。顺带说一下:Go没有继承。
下面看一个有关接口的例子:
package main
import (
"fmt"
)
type integer interface {
add(int)
show()
}
type num struct {
a int
}
func (n *num) add(i int) {
n.a += i
}
func (n num) show() {
fmt.Println(n.a)
}
func show(n integer) {
n.show()
}
func main() {
n := num{a: 0}
n.add(1)
show(&n) // 输出: 1
show(n) // 报错
}
当我试图使用show(n)打印变量n的值时,会报错:
# command-line-arguments
./a.go:31:6: cannot use n (type num) as type integer in argument to show:
num does not implement integer (add method has pointer receiver)
上面的意思是说,以值类型作为参数传递时,并没有实现integer接口的add方法。先直接说结论:
| T类型方法集 | *T类型方法集 | |
|---|---|---|
| T类型参数 | YES | NO |
| *T类型参数 | YES | YES |
为什么T类型参数不包含*T类型方法集呢?因为T类型参数可以是临时变量,一个临时变量可能在内存中并没有具体的地址,无法进行引用。而*T类型参数必定能进行解引用获取到变量,传递其值类型,所以包含T类型方法集。
空接口
Go中有一个特殊的接口类型。空接口类型:interface{}。在Go中所有的类型都自动实现了空接口类型(有点像C中的void类型)。下面看一个例子:
package main
import (
"fmt"
)
type integer interface {
add(int)
show()
}
type num struct {
a int
}
func (n *num) add(i int) {
n.a += i
}
func (n num) show() {
fmt.Println(n.a)
}
func show(n interface{}) {
realN, _ := n.(integer)
realN.show()
}
func main() {
n := num{a: 0}
n.add(1)
show(&n)
}
上面的例子仅是修改了show()函数的参数类型为interface{}类型,通过类型断言获得到真实的类型,实现相同的功能。但使用了interface{}参数类型,编译器对类型检查会变弱。上面调用show(n)函数,编译器不会报错,而是造成运行时恐慌。所以,即使interface{}类型看上去是万能的类型,但会削弱编译器的类型检查能力,很容易产生bug。
内嵌结构体
内嵌结构体是Go组合理念的体现。通过内嵌的方式,将不同类型的数据成员和方法集组合在一起。通过成员和方法提升的方式,实现类似继承的特性(Go没有继承,内嵌结构体不是继承)。下面看一个例子:
package main
import (
"fmt"
)
type integer interface {
add(int)
show()
}
type num struct {
a int
}
type bigNum struct {
num
b int
}
func (n *num) add(i int) {
n.a += i
}
func (n num) show() {
fmt.Println(n.a)
}
func show(n integer) {
n.show()
}
func main() {
n := bigNum{num: num{a: 0}, b: 0}
n.add(1)
show(&n) // 输出:1
}
上的例子中,bigNum结构体内嵌了num结构体,此时bigNum自动实现了integer接口。所以,show()函数能接收*bigNum类型变量n作为参数。能够实现该特性主要依赖于成员和方法提升,内嵌结构体内部的成员和方法被提升到外部,在例子中就是num->bigNum。提升也有其规律,如果外部存在同名变量或方法(无论参数或返回值是否相同)则内部的不会被提升。
内嵌结构体包括内嵌值类型和指针类型的结构体。它们有什么区别呢?先直接放结论:(假设外部结构体为: O, 内嵌结构体为: T)
| T类型方法集 | *T类型方法集 | |
|---|---|---|
| 内嵌T类型 | O能调试用该类型方法集:YES *O能调试用该类型方法集:YES |
O能调试用该类型方法集:NO *O能调试用该类型方法集:YES |
| 内嵌*T类型 | O能调试用该类型方法集:YES *O能调试用该类型方法集:YES |
O能调试用该类型方法集:YES *O能调试用含该类型方法集:YES |
下面看一个示例代码加深印象:
package main
import (
"fmt"
)
type integer interface {
add(int)
show()
}
type num struct {
a int
}
type bigNum struct {
num
b int
}
type smallNum struct {
*num
b int
}
func (n *num) add(i int) {
n.a += i
}
func (n num) show() {
fmt.Println(n.a)
}
func show(n integer) {
n.show()
}
func main() {
bn := bigNum{num: num{a: 0}, b: 0}
sn := smallNum{num: &num{a: 0}, b: 0}
// show(bn) // 报错
show(&bn)
show(sn)
show(&sn)
}
调用show(bn)会报错,说bigNum类型内有实现add方法。通过内嵌T类型的方式,并不会暴露*T类型的方法集给外部O类型的变量。