這是『就要學習 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來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!