golang中的接口
接口
在go中,接口是一個自定義類型
接口類型是一個抽象類型,他不會暴露他代表的對象的內部值的結構和這個對象支持的基礎操作的集合,他們只會展示出自己的方法.因此接口類型不能將他實例化
定義
type Humaner interface {sayHi() }- 接口命名習慣以er結尾
- 接口只有方法聲明,沒有實現,沒有數據字段
- 接口可以匿名嵌入其他接口,或嵌入到結構中
實現
//定義接口類型 type Humaner interface {sayHi() }type Student struct {name stringid int } //Student類型實現了這個方法 func (tmp *Student) sayHi() {fmt.Println(*tmp) }func main() {//定義接口類型的變量var i Humaner//只要實現了此接口方法的類型,那么這個類型的變量(接收者類型)就可以給i賦值s := &Student{"Mike", 666}//引用賦值i= s//調用實現者的方法i.sayHi() }多態
//定義接口類型 type Humaner interface {sayHi() }type Student struct {name stringid int } //Student類型實現了這個方法 func (tmp *Student) sayHi() {fmt.Println(*tmp) }type Mystr string //MyStr實現了這個方法 func (tmp *Mystr) sayHi() {fmt.Println(*tmp) } //定義一個普通函數,函數的參數為接口類型 //只有一個函數,缺有不同表現 func WhoSayHi(i Humaner) {i.sayHi() }func main() {s := &Student{"Mike", 666}t := &Student{"sds", 6556}var str Mystr = "HELLO"//要傳地址WhoSayHi(s)WhoSayHi(t)WhoSayHi(&str) }接口的繼承
//定義接口類型 type Humaner interface { //子集sayHi() }type Person interface { //超集Humaner //繼承了sayHi()sing(lrc string) }type Student struct {name stringid int } //Student類型實現了這個方法 func (tmp *Student) sayHi() {fmt.Println(*tmp) }func (tmp *Student) sing(lrc string) {fmt.Println(*tmp) }func main() {//定義一個接口類型的變量var i Persons := &Student{"mike", 666}i = si.sayHi() //繼承過來的方法i.sing("abc") }接口轉換
超級可以轉換為子集,反過來不可以
func main() {//定義一個接口類型的變量var i Personvar h Humaneri = h //不可以 }空接口
空接口(interface{})不包含任何方法,所有類型都實現了空接口,因此空接口可以存儲任意類型的數值.有點類似于c語言的void *類型
//將int類型賦值給interface{} var v1 interface{} = 1 //將string類型賦值給interface{} var v2 interface{} = "abc" //將*interface{]類型賦值給interface{} var v3 interface{} = &v2 var v4 interface{} = struct {X int }{1} var v5 interface{} = struct {x int }{1}當函數可以接受任意的對象實例時,我們會將其聲明為interface{},最典型的例子就是標準庫fmt中PrintXXX系列的函數
func Printf(fmt string, args ...interface{}) //可變參數的空接口類型 func Println(args ...interface{}) func main() {var i interface{} = 1fmt.Println(i) }動態類型
對于任何數據類型,只要它的方法集合中完全包含了一個接口的全部特征(即全部的方法),那么它就一定是這個接口的實現類型。比如下面這樣:
type Pet interface {SetName(name string)Name() stringCategory() string }怎樣判定一個數據類型的某一個方法實現的就是某個接口類型中的某個方法呢?
這有兩個充分必要條件,一個是“兩個方法的簽名需要完全一致”,另一個是“兩個方法的名稱要一模一樣”。顯然,這比判斷一個函數是否實現了某個函數類型要更加嚴格一些。
如果你查閱了上篇文章附帶的最后一個示例的話,那么就一定會知道,雖然結構體類型Cat不是Pet接口的實現類型,但它的指針類型*Cat卻是這個的實現類型。
我聲明的類型Dog附帶了 3 個方法。其中有 2 個值方法,分別是Name和Category,另外還有一個指針方法SetName。
這就意味著,Dog類型本身的方法集合中只包含了 2 個方法,也就是所有的值方法。而它的指針類型*Dog方法集合卻包含了 3 個方法,
也就是說,它擁有Dog類型附帶的所有值方法和指針方法。又由于這 3 個方法恰恰分別是Pet接口中某個方法的實現,所以*Dog類型就成為了Pet接口的實現類型。
dog := Dog{"little pig"} var pet Pet = &dog正因為如此,我可以聲明并初始化一個Dog類型的變量dog,然后把它的指針值賦給類型為Pet的變量pet。
這里有幾個名詞需要你先記住。對于一個接口類型的變量來說,例如上面的變量pet,我們賦給它的值可以被叫做它的實際值(也稱動態值),而該值的類型可以被叫做這個變量的實際類型(也稱動態類型)。
比如,我們把取址表達式&dog的結果值賦給了變量pet,這時這個結果值就是變量pet的動態值,而此結果值的類型*Dog就是該變量的動態類型。
動態類型這個叫法是相對于靜態類型而言的。對于變量pet來講,它的靜態類型就是Pet,并且永遠是Pet,但是它的動態類型卻會隨著我們賦給它的動態值而變化。
比如,只有我把一個*Dog類型的值賦給變量pet之后,該變量的動態類型才會是*Dog。如果還有一個Pet接口的實現類型*Fish,并且我又把一個此類型的值賦給了pet,那么它的動態類型就會變為*Fish。
還有,在我們給一個接口類型的變量賦予實際的值之前,它的動態類型是不存在的
type Pet interface {SetName(name string)Name() stringCategory() string }type Dog struct {name string }func (dog *Dog) SetName(name string) {dog.name = name }func (dog *Dog) Name() string {return dog.name }func (dog *Dog) Category() string {return "dog" }func main() {// 示例1dog := Dog{"little pig"}_, ok := interface{}(dog).(Pet)fmt.Printf("Dog是接口Pet的實現類型嗎: %v\n", ok)_, ok = interface{}(&dog).(Pet)fmt.Printf("*Dog是接口Pet的實現類型嗎: %v\n", ok)fmt.Println()// 示例2var pet Pet = &dogfmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())}實現規則
接口變量的值并不等同于這個可被稱為動態值的副本。它會包含兩個指針,一個指針指向動態值,一個指針指向類型信息
規則一:如果使用指針方法來實現一個接口,那么只有指向那個類型的指針才能夠實現對應的接口。
規則二:如果使用值方法來實現一個接口,那么那個類型的值和指針都能夠實現對應的接口
type Pet interface {SetName(name string)Name() stringCategory() string }type Dog struct {name string // 名字。 }func (dog *Dog) SetName(name string) {dog.name = name }func (dog Dog) Name() string {return dog.name }func (dog Dog) Category() string {return "dog" }func main() {// 示例1。dog := Dog{"little pig"}_, ok := interface{}(dog).(Pet)fmt.Printf("Dog implements interface Pet: %v\n", ok)_, ok = interface{}(&dog).(Pet)fmt.Printf("*Dog implements interface Pet: %v\n", ok)fmt.Println()// 示例2。var pet Pet = &dogfmt.Printf("This pet is a %s, the name is %q.\n",pet.Category(), pet.Name()) }Stringer
在fmt包里有一個interface叫做Stringer:
type Stringer interface {String() string }作用:fmt.Println或打印一個變量的值的時候,會判斷這個變量是否實現了Stringer接口,如果實現了,則調用這個變量的String()方法,并將返回值打印到屏幕上
fmt.Printf的%v也會讀取Stringer
例子:
package mainimport "fmt"type Person struct {Name stringAge int }func (p Person) String() string {return fmt.Sprintf("(Name: %v) (Age: %v)", p.Name, p.Age) }func main() {a := Person{"benz", 21}fmt.Println(a)fmt.Printf("%v\n", a) }輸出
(Name: benz) (Age: 21) (Name: benz) (Age: 21)可以看出,用String()修改了輸出格式
再舉個例子:
輸出
loopback: [127 0 0 1] googleDNS: [8 8 8 8]如果把上面的String的注釋去掉,輸出則是
loopback: 127.0.0.1 googleDNS: 8.8.8.8type switch
對接口變量hold值做類型判斷?
當i是個接口變量的時候,可以用i.(type)來對這個接口變量hold的值類型做判斷
switch v := i.(type) { case int:... case string:... default:... }注意:之前類型斷言是用i.($TYPE)比如i.(int)來判斷是不是int類型,但是這里用關鍵字type。關鍵字type只能用在switch語句里,如果用在switch外面會報錯,比如:
a := i.(type) fmt.Printf("%v %T\n", a, a)報錯:
use of .(type) outside type switch除了能識別內置類型,也可以識別其他類型,比如函數類型,或者帶指針的struct,比如這個例子是帶指針的struct
type Foo interface {foo() int }type MyStruct struct {X, Y int }func (a *MyStruct) foo() int {return a.X + a.Y }func main() {var f Foos := MyStruct{3, 4}f = &sfmt.Printf("%v,%T\n", f, f)switch v := f.(type) {case *MyStruct:fmt.Printf("1,%v,%T\n", v, v)default:fmt.Printf("2,%v,%T\n", v, v)} }輸出
&{3 4},*main.MyStruct 1,&{3 4},*main.MyStruct注意:這個type用%T顯示出來的是*main.MyStruct,而case里是*MyStruct,沒有main喔。
這個例子是函數:
func main() {pos := func () int { return 1 }fmt.Printf("%v,%T\n", pos, pos)var i interface{}i = posfmt.Printf("%v,%T\n", i, i)switch v := i.(type) {case int:fmt.Printf("1,%v,%T\n", v, v)case func() int:fmt.Printf("2,%v,%T\n", v, v)case func(int) int:fmt.Printf("3,%v,%T\n", v, v)default:fmt.Printf("4,%v,%T\n", v, v)} }輸出:
0x1088f30,func() int 0x1088f30,func() int 2,0x1088f30,func() int可以看出:case后面跟的就是i值的類型
注意:case后面不能跟不存在的自定義類型,比如:
func main() {var i interface{}i = 1fmt.Printf("%v,%T\n", i, i)switch v := i.(type) {case int:fmt.Printf("1,%v,%T\n", v, v)case func() int:fmt.Printf("2,%v,%T\n", v, v)case func(int) int:fmt.Printf("3,%v,%T\n", v, v)case *MyStruct:fmt.Printf("4,%v,%T\n", v, v)default:fmt.Printf("5,%v,%T\n", v, v)} }報錯:
undefined: MyStruct可以用是否實現接口做判斷?
package mainimport "fmt"type Adder interface {Add() }type MyStruct struct {X, Y int }func (this MyStruct) Add() {fmt.Println(this.X + this.Y) }func main() {s := MyStruct{3, 4}//var i interface{} = svar i Adder = sswitch v := i.(type) {case MyStruct:fmt.Printf("case MyStruct: %T %v\n", v, v)case interface{}:fmt.Printf("case interface{}: %T %v\n", v, v)case Adder:fmt.Printf("case Adder: %T %v\n", v, v)default:fmt.Printf("not case: %T %v\n", v, v)} }
輸出
case MyStruct: main.MyStruct {3 4}另外上面
var i interface{} = s或var i Adder = s用哪個都一樣
這3個case不管用哪個,第一個case都能匹配到
Warning
case只能用于類型判斷(包括實現接口),沒有辦法判斷是否為一個接口。就是說沒有辦法判斷一個變量是否為接口變量,即使用反射也是無法判斷的
總結
以上是生活随笔為你收集整理的golang中的接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang中的权限
- 下一篇: golang中的方法