這是『就要學習 Go 語言』系列的第 19 篇分享文章
什麼是介面
在一些物件導向的程式語言中,例如 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。
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)
}
複製程式碼
輸出:
value of s is <nil>
type of s is <nil>
複製程式碼
上面的程式碼,由於介面是一種型別,所以可以建立 Shape 型別的變數 s,你是不是很疑惑 s 的型別為什麼是 nil?讓我們來看下一節!
介面型別值
靜態型別和動態型別
變數的型別在宣告時指定、且不能改變,稱為靜態型別。介面型別的靜態型別就是介面本身。介面沒有靜態值,它指向的是動態值。介面型別的變數存的是實現介面的型別的值。該值就是介面的動態值,實現介面的型別就是介面的動態型別。
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)
}
複製程式碼
輸出:
type is main.St1
value is {}
type is main.St2
value is {}
複製程式碼
變數 i 的靜態型別是 Iname,是不能改變的。動態型別卻是不固定的,第一次分配之後,i 的動態型別是 St1,第二次分配之後,i 的動態型別是 St2,動態值都是空結構體。
有時候,介面的動態型別又稱為具體型別,當我們訪問介面型別的時候,返回的是底層動態值的型別。
nil 介面值
我們來看個例子:
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來啦」給你準備了一份神祕學習大禮包,後臺回覆【電子書】領取!