Go 語言接口詳解(一)

這是『就要學習 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,動態值都是空結構體。

有時候,接口的動態類型又稱爲具體類型,當咱們訪問接口類型的時候,返回的是底層動態值的類型。

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

公衆號二維碼
相關文章
相關標籤/搜索