摘要:Go支持接口,接口是方法特征的命名集合。
go语言接口
go语言中有接口的概念,接口是方法特征的命名集合。它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
注意,实现了这些方法就算实现了这个接口。
定义接口
// 定义geometry接口
type geometry interface {
area() float64
peri() float64
}
接口的定义也比较简单。定义和实现规则如下:
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
实现接口
Go语言中接口的实现都是隐式的,默认实现了接口的所有方法就隐式地实现了接口。
假如现已定义上文中的geometry接口,现在要实现该接口。
// 定义rect结构体
type rect struct {
width, height float64
}
// rect 实现geometry接口,实现接口的方式与定义方法类似
func (r rect) area() float64 {
return r.width * r.height
}
// rect 实现geometry接口,只有实现了接口的所有方法才算实现接口
func (r rect) peri() float64 {
return r.height*2 + r.width*2
}
注意:只有实现了接口的所有方法才算实现接口。
调用方式:
var geo1 geometry = rect{height: 1, width: 3}
fmt.Println("geo1.area()", geo1.area())
fmt.Println("geo1.peri()", geo1.peri())
接口也可以作为函数参数,传参传入具体的实现。
// 定义调用接口方法的函数
func measure(g geometry) {
fmt.Println(g.peri())
fmt.Println(g.area())
}
// --- main()
rect1 := rect{height: 3, width: 2}
measure(rect1)
接口类型
接口也是Go语言中的一种类型,这种类型可以出现在变量的定义、函数的入参、函数的返回值中。
// define Print
func Print(i interface{}) {
fmt.Println(i)
}
v1 := 22
v2 := "ss"
v3 := make([]string, 1)
v3 = append(v3, "333")
Print(v1)
Print(v2)
Print(v3)
// 结果
// 22
// ss
// [ 333]
Print()函数中,参数为interface{}类型,但并不是任意类型,在调用函数时,将参数转换成了interface{}类型,这里是类型转换。
接口和指针
看以下两个例子:
- 通过结构体实现接口
// 定义rect结构体
type rect struct {
width, height float64
}
// rect 实现geometry接口,实现接口的方式与定义方法类似
func (r rect) area() float64 {
return r.width * r.height
}
// rect 实现geometry接口,只有实现了接口的所有方法才算实现接口
func (r rect) peri() float64 {
return r.height*2 + r.width*2
}
- 通过结构体指针实现接口
// 定义rect结构体
type rect struct {
width, height float64
}
// rect 实现geometry接口,实现接口的方式与定义方法类似
func (r *rect) area() float64 {
return r.width * r.height
}
// rect 实现geometry接口,只有实现了接口的所有方法才算实现接口
func (r *rect) peri() float64 {
return r.height*2 + r.width*2
}
在Go语言中,结构体实现接口有两种方式,通过结构体实现、通过结构体指针实现。但两种方式不能同时存在,这两种实现方式也会导致接口的调用有些差别。
var geo1 geometry = rect{height: 1, width: 3} // 使用结构体初始化变量
var geo1 geometry = &rect{height: 1, width: 3} // 使用结构体指针初始化变量
结构体实现接口 | 结构体指针实现接口 | |
---|---|---|
结构体指针初始化变量 | 通过 | 通过 |
结构体初始化变量 | 通过 | 不通过 |
当使用结构体实现接口时,无论初始化变量是结构体还是结构体指针,都可以编译通过;当使用结构体指针实现接口时,初始化变量为结构体时无法编译通过。
为什么使用结构体指针实现接口时,初始化变量为结构体时无法编译通过?
原因是在函数调用阶段,Go语言在传递参数时都是值传递。无论初始变量是rect{}还是&rect{},在调用方法时都会发生值拷贝。
- 对于&rect{}来说,方法调用阶段会拷贝一个新的&rect{}指针,新的指针与原来的指针都指向同一个结构体,所以编译器可以隐式地解引用(dereference),获取到原始的结构体。
- 对于rect{}来说,方法调用阶段会拷贝一个新的结构体,这是一个全新的rect{},但因为方法的参数是*rect,编译器不会无中生有创建一个新的指针,就算创建新的指针,也不会指向原始的结构体。
当我们使用指针实现接口时,只有指针类型的变量才会实现该接口;当我们使用结构体实现接口时,指针类型和结构体类型都会实现该接口。当然这并不意味着我们应该一律使用结构体实现接口,这个问题在实际工程中也没那么重要。
再谈接口类型
了解了接口参数的隐式转换后,再看上文提到的,接口不是任何一种类型,看以下示例:
type TestStruct struct{}
func NilOrNot(v interface{}) bool {
return v == nil
}
func main() {
var s *TestStruct
fmt.Println(s == nil) // true
fmt.Println(NilOrNot(s)) // false
}
调用NilOrNot
函数时,发生了隐式类型转换,除了向方法传入参数之外,变量的赋值也会触发隐式类型转换,在转换过程中,*TestStruct
类型会转换为interface{}
类型,转换后的变量不仅包含转换前的变量,还包含变量的类型信息,所以转换后的变量与 nil 不相等。