这是『就要学习 Go 语言』系列的第 19 篇分享文章golang
在一些面向对象的编程语言中,例如 Java、PHP 等,接口定义了对象的行为,只指定了对象应该作什么。行为的具体实现取决于对象。编程
在 Go 语言中,接口是一组方法的集合,但不包含方法的实现、是抽象的,接口中也不能包含变量。当一个类型 T 提供了接口中全部方法的定义时,就说 T 实现了接口。接口指定类型应该有哪些方法,类型决定如何去实现这些方法。编程语言
接口的声明相似于结构体,使用类型别名且须要关键字 interface,语法以下:函数
type Name interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
复制代码
实际定义一个接口:学习
type Shape interface {
Area() float32
}
复制代码
上面的代码定义了接口类型 Shape,接口中包含了一个不带参数、返回值为 float32 的方法 Area()。任何实现了方法 Area() 的类型 T,咱们就说它实现了接口 Shape。spa
type Shape interface {
Area() float32
}
func main() {
var s Shape
fmt.Println("value of s is", s)
fmt.Printf("type of s is %T\n", s)
}
复制代码
输出:.net
value of s is <nil>
type of s is <nil>
复制代码
上面的代码,因为接口是一种类型,因此能够建立 Shape 类型的变量 s,你是否是很疑惑 s 的类型为何是 nil?让咱们来看下一节!指针
变量的类型在声明时指定、且不能改变,称为静态类型。接口类型的静态类型就是接口自己。接口没有静态值,它指向的是动态值。接口类型的变量存的是实现接口的类型的值。该值就是接口的动态值,实现接口的类型就是接口的动态类型。code
type Iname interface {
Mname()
}
type St1 struct {}
func (St1) Mname() {}
type St2 struct {}
func (St2) Mname() {}
func main() {
var i Iname = St1{}
fmt.Printf("type is %T\n",i)
fmt.Printf("value is %v\n",i)
i = St2{}
fmt.Printf("type is %T\n",i)
fmt.Printf("value is %v\n",i)
}
复制代码
输出:cdn
type is main.St1
value is {}
type is main.St2
value is {}
复制代码
变量 i 的静态类型是 Iname,是不能改变的。动态类型倒是不固定的,第一次分配以后,i 的动态类型是 St1,第二次分配以后,i 的动态类型是 St2,动态值都是空结构体。
有时候,接口的动态类型又称为具体类型,当咱们访问接口类型的时候,返回的是底层动态值的类型。
咱们来看个例子:
type Iname interface {
Mname()
}
type St struct {}
func (St) Mname() {}
func main() {
var t *St
if t == nil {
fmt.Println("t is nil")
} else {
fmt.Println("t is not nil")
}
var i Iname = t
fmt.Printf("%T\n", i)
if i == nil {
fmt.Println("i is nil")
} else {
fmt.Println("i is not nil")
}
fmt.Printf("i is nil pointer:%v",i == (*St)(nil))
}
复制代码
输出:
t is nil
*main.St
i is not nil
i is nil pointer:true
复制代码
是否是很惊讶,咱们分配给变量 i 的值明明是 nil,然而 i 却不是 nil。 来看下怎么回事!
动态类型在上面已经讲过,动态值是实际分配的值。记住一点:当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码,给变量 i 赋值以后,i 的动态值是 nil,可是动态类型倒是 *St, i 是一个 nill 指针,因此相等条件不成立。
看下 Go 语言规范:
var x interface{} // x is nil and has static type interface{}
var v *T // v has value nil, static type *T
x = 42 // x has value 42 and dynamic type int
x = v // x has value (*T)(nil) and dynamic type *T
复制代码
经过这一节学习,相信你已经很清楚为何上一节的 Shape 类型的变量的 s 输出的类型是 nil,由于 var s Shape 声明时,s 的动态类型是 nil。
看示例:
type Shape interface {
Area() float32
}
type Rect struct {
width float32
height float32
}
func (r Rect) Area() float32 {
return r.width * r.height
}
func main() {
var s Shape
s = Rect{5.0, 4.0}
r := Rect{5.0, 4.0}
fmt.Printf("type of s is %T\n", s)
fmt.Printf("value of s is %v\n", s)
fmt.Println("area of rectange s", s.Area())
fmt.Println("s == r is", s == r)
}
复制代码
输出:
type of s is main.Rect
value of s is {5 4}
area of rectange s 20
s == r is true
复制代码
上面的代码,建立了接口 Shape、结构体 Rect 以及方法 Area()。因为 Rect 实现了接口定义的全部方法,虽然只有一个,因此说 Rect 实现了接口 Shape。
在主函数里,建立了接口类型的变量 s ,值为 nil,并用 Rect 类型的结构体初始化,由于 Rect 结构体实现了接口,因此这是有效的。赋值以后,s 的动态类型变成了 Rect,动态值就是结构体的值 {5.0,4.0}。
能够直接使用 .
语法调用 Area() 方法,由于 s 的具体类型是 Rect,而 Rect 实现了 Area() 方法。
一个不包含任何方法的接口,称之为空接口,形如:interface{}。由于空接口不包含任何方法,因此任何类型都默认实现了空接口。
举个例子,fmt 包中的 Println() 函数,能够接收多种类型的值,好比:int、string、array等。为何,由于它的形参就是接口类型,能够接收任意类型的值。
func Println(a ...interface{}) (n int, err error) {}
复制代码
咱们来看个例子:
type MyString string
type Rect struct {
width float32
height float32
}
func explain(i interface{}) {
fmt.Printf("type of s is %T\n", i)
fmt.Printf("value of s is %v\n\n", i)
}
func main() {
ms := MyString("Seekload")
r := Rect{5.0, 4.0}
explain(ms)
explain(r)
}
复制代码
输出:
type of s is main.MyString
value of s is Seekload
type of s is main.Rect
value of s is {5 4}
复制代码
上面的代码,建立了自定义的字符串类型 MyString 、结构体 Rect 和 explain() 函数。explain() 函数的形参是空接口,因此能够接收任意类型的值。
关于接口使用的第一部分就讲到这,后续会再开文章给你们讲剩余部分,继续关注!
原创文章,若需转载请注明出处!
欢迎扫码关注公众号「Golang来啦」或者移步 seekload.net ,查看更多精彩文章。
公众号「Golang来啦」给你准备了一份神秘学习大礼包,后台回复【电子书】领取!