在Golang
世界中,有一種叫interface
的東西,非常神奇。算法
若是你事前並不知道變量是哪一種數據類型,不知道它是整數仍是字符串,可是你仍是想要使用它。編程
Golang
就產生了名爲interface{}
的數據類型,表示並不知道它是什麼類型。舉例子:segmentfault
package main import ( "fmt" "reflect" ) func print(i interface{}) { fmt.Println(i) } func main() { // 聲明一個未知類型的 a,代表不知道是什麼類型 var a interface{} a = 2 fmt.Printf("%T,%v\n", a, a) // 傳入函數 print(a) print(3) print("i love you") // 使用斷言,判斷是不是 int 數據類型 v, ok := a.(int) if ok { fmt.Printf("a is int type,value is %d\n", v) } // 使用斷言,判斷變量類型 switch a.(type) { case int: fmt.Println("a is type int") case string: fmt.Println("a is type string") default: fmt.Println("a not type found type") } // 使用反射找出變量類型 t := reflect.TypeOf(a) fmt.Printf("a is type: %s", t.Name()) }
輸出:數組
int,2 2 3 i love you a is int type,value is 2 a is type int a is type: int
咱們使用interface{}
,能夠聲明一個未知類型的變量a
:安全
// 聲明一個未知類型的 a,代表不知道是什麼類型 var a interface{} a = 2 fmt.Printf("%T,%v\n", a, a)
而後給變量賦值一個整數:a=2
,這時a
仍然是未知類型,使用佔位符%T
能夠打印變量的真實類型,佔位符%v
打印值,這時fmt.Printf
在內部會進行類型判斷。數據結構
咱們也能夠將函數的參數也定爲interface
,和變量的定義同樣:併發
func print(i interface{}) { fmt.Println(i) }
使用時:數據結構和算法
// 傳入函數 print(a) print(3) print("i love you")
咱們傳入print
函數的參數能夠是任何類型,如整數3
或字符串i love you
等。進入函數後,函數內變量i
丟失了類型,是一個未知類型,這種特徵使得咱們若是想處理不一樣類型的數據,不須要寫多個函數。函數式編程
固然,結構體裏面的字段也能夠是interface{}
:函數
type H struct { A interface{} B interface{} }
咱們定義了interface{}
,可是實際使用時,咱們有判斷類型的需求。有兩種方法能夠進行判斷。
使用斷言:
// 使用斷言,判斷是不是 int 數據類型 v, ok := a.(int) if ok { fmt.Printf("a is int type,value is %d\n", v) }
直接在變量後面使用.(int)
,有兩個返回值v, ok
會返回。ok
若是是true
代表確實是整數類型,這個整數會被賦予v
,而後咱們能夠拿v
愉快地玩耍了。不然,ok
爲false
,v
爲空值,也就是默認值 0。
若是咱們每次都這樣使用,會很難受,由於一個interface{}
類型的變量,數據類型多是.(int)
,多是.(string)
,可使用switch
來簡化:
// 使用斷言,判斷變量類型 switch a.(type) { case int: fmt.Println("a is type int") case string: fmt.Println("a is type string") default: fmt.Println("a not type found type") }
在swicth
中,斷言再也不使用.(具體類型)
,而是a.(type)
。
最後,還有一種方式,使用的是反射包reflect
來肯定數據類型:
// 使用反射找出變量類型 t := reflect.TypeOf(a) fmt.Printf("a is type: %s", t.Name())
這個包會直接使用非安全指針來獲取真實的數據類型:
func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) }
通常平常開發,不多使用反射包。
咱們如今都是函數式編程,或者是結構體方法式的編程,難道沒有其餘語言那種面向對象,對象繼承的特徵嗎?有,Golang
語言叫作面向接口編程。
package main import ( "fmt" "reflect" ) // 定義一個接口,有一個方法 type A interface { Println() } // 定義一個接口,有兩個方法 type B interface { Println() Printf() int } // 定義一個結構體 type A1Instance struct { Data string } // 結構體實現了Println()方法,如今它是一個 A 接口 func (a1 *A1Instance) Println() { fmt.Println("a1:", a1.Data) } // 定義一個結構體 type A2Instance struct { Data string } // 結構體實現了Println()方法,如今它是一個 A 接口 func (a2 *A2Instance) Println() { fmt.Println("a2:", a2.Data) } // 結構體實現了Printf()方法,如今它是一個 B 接口,它既是 A 又是 B 接口 func (a2 *A2Instance) Printf() int { fmt.Println("a2:", a2.Data) return 0 } func main() { // 定義一個A接口類型的變量 var a A // 將具體的結構體賦予該變量 a = &A1Instance{Data: "i love you"} // 調用接口的方法 a.Println() // 斷言類型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String()) // 將具體的結構體賦予該變量 a = &A2Instance{Data: "i love you"} // 調用接口的方法 a.Println() // 斷言類型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String()) // 定義一個B接口類型的變量 var b B //b = &A1Instance{Data: "i love you"} // 不是 B 類型 b = &A2Instance{Data: "i love you"} fmt.Println(b.Printf()) }
輸出:
a1: i love you &{i love you} *main.A1Instance a2: i love you not a A1 *main.A2Instance a2: i love you 0
咱們能夠定義一個接口類型,使用type 接口名 interface
,這時候再也不是interface{}
:
// 定義一個接口,有一個方法 type A interface { Println() } // 定義一個接口,有兩個方法 type B interface { Println() Printf() int }
能夠看到接口A
和B
是一種抽象的結構,每一個接口都有一些方法在裏面,只要結構體struct
實現了這些方法,那麼這些結構體都是這種接口的類型。如:
// 定義一個結構體 type A1Instance struct { Data string } // 結構體實現了Println()方法,如今它是一個 A 接口 func (a1 *A1Instance) Println() { fmt.Println("a1:", a1.Data) } // 定義一個結構體 type A2Instance struct { Data string } // 結構體實現了Println()方法,如今它是一個 A 接口 func (a2 *A2Instance) Println() { fmt.Println("a2:", a2.Data) } // 結構體實現了Printf()方法,如今它是一個 B 接口,它既是 A 又是 B 接口 func (a2 *A2Instance) Printf() int { fmt.Println("a2:", a2.Data) return 0 }
咱們要求結構體必須實現某些方法,因此能夠定義一個接口類型的變量,而後將結構體賦值給它:
// 定義一個A接口類型的變量 var a A // 將具體的結構體賦予該變量 a = &A1Instance{Data: "i love you"} // 調用接口的方法 a.Println()
若是結構體沒有實現該方法,將編譯不經過,沒法編譯二進制。
固然也可使用斷言和反射來判斷接口類型是屬於哪一個實際的結構體struct
。
// 斷言類型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String())
Golang
很智能判斷結構體是否實現了接口的方法,若是實現了,那麼該結構體就是該接口類型。咱們靈活的運用接口結構的特徵,使用組合的形式就能夠開發出更靈活的程序了。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。