golang中的接口
接口
在go中,接口是一個自定義類型
接口類型是一個抽象類型,他不會暴露他代表的對象的內(nèi)部值的結(jié)構(gòu)和這個對象支持的基礎(chǔ)操作的集合,他們只會展示出自己的方法.因此接口類型不能將他實例化
定義
type Humaner interface {sayHi() }- 接口命名習(xí)慣以er結(jié)尾
- 接口只有方法聲明,沒有實現(xiàn),沒有數(shù)據(jù)字段
- 接口可以匿名嵌入其他接口,或嵌入到結(jié)構(gòu)中
實現(xiàn)
//定義接口類型 type Humaner interface {sayHi() }type Student struct {name stringid int } //Student類型實現(xiàn)了這個方法 func (tmp *Student) sayHi() {fmt.Println(*tmp) }func main() {//定義接口類型的變量var i Humaner//只要實現(xiàn)了此接口方法的類型,那么這個類型的變量(接收者類型)就可以給i賦值s := &Student{"Mike", 666}//引用賦值i= s//調(diào)用實現(xiàn)者的方法i.sayHi() }多態(tài)
//定義接口類型 type Humaner interface {sayHi() }type Student struct {name stringid int } //Student類型實現(xiàn)了這個方法 func (tmp *Student) sayHi() {fmt.Println(*tmp) }type Mystr string //MyStr實現(xiàn)了這個方法 func (tmp *Mystr) sayHi() {fmt.Println(*tmp) } //定義一個普通函數(shù),函數(shù)的參數(shù)為接口類型 //只有一個函數(shù),缺有不同表現(xiàn) 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類型實現(xiàn)了這個方法 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") }接口轉(zhuǎn)換
超級可以轉(zhuǎn)換為子集,反過來不可以
func main() {//定義一個接口類型的變量var i Personvar h Humaneri = h //不可以 }空接口
空接口(interface{})不包含任何方法,所有類型都實現(xiàn)了空接口,因此空接口可以存儲任意類型的數(shù)值.有點類似于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}當(dāng)函數(shù)可以接受任意的對象實例時,我們會將其聲明為interface{},最典型的例子就是標(biāo)準(zhǔn)庫fmt中PrintXXX系列的函數(shù)
func Printf(fmt string, args ...interface{}) //可變參數(shù)的空接口類型 func Println(args ...interface{}) func main() {var i interface{} = 1fmt.Println(i) }動態(tài)類型
對于任何數(shù)據(jù)類型,只要它的方法集合中完全包含了一個接口的全部特征(即全部的方法),那么它就一定是這個接口的實現(xiàn)類型。比如下面這樣:
type Pet interface {SetName(name string)Name() stringCategory() string }怎樣判定一個數(shù)據(jù)類型的某一個方法實現(xiàn)的就是某個接口類型中的某個方法呢?
這有兩個充分必要條件,一個是“兩個方法的簽名需要完全一致”,另一個是“兩個方法的名稱要一模一樣”。顯然,這比判斷一個函數(shù)是否實現(xiàn)了某個函數(shù)類型要更加嚴(yán)格一些。
如果你查閱了上篇文章附帶的最后一個示例的話,那么就一定會知道,雖然結(jié)構(gòu)體類型Cat不是Pet接口的實現(xiàn)類型,但它的指針類型*Cat卻是這個的實現(xiàn)類型。
我聲明的類型Dog附帶了 3 個方法。其中有 2 個值方法,分別是Name和Category,另外還有一個指針方法SetName。
這就意味著,Dog類型本身的方法集合中只包含了 2 個方法,也就是所有的值方法。而它的指針類型*Dog方法集合卻包含了 3 個方法,
也就是說,它擁有Dog類型附帶的所有值方法和指針方法。又由于這 3 個方法恰恰分別是Pet接口中某個方法的實現(xiàn),所以*Dog類型就成為了Pet接口的實現(xiàn)類型。
dog := Dog{"little pig"} var pet Pet = &dog正因為如此,我可以聲明并初始化一個Dog類型的變量dog,然后把它的指針值賦給類型為Pet的變量pet。
這里有幾個名詞需要你先記住。對于一個接口類型的變量來說,例如上面的變量pet,我們賦給它的值可以被叫做它的實際值(也稱動態(tài)值),而該值的類型可以被叫做這個變量的實際類型(也稱動態(tài)類型)。
比如,我們把取址表達(dá)式&dog的結(jié)果值賦給了變量pet,這時這個結(jié)果值就是變量pet的動態(tài)值,而此結(jié)果值的類型*Dog就是該變量的動態(tài)類型。
動態(tài)類型這個叫法是相對于靜態(tài)類型而言的。對于變量pet來講,它的靜態(tài)類型就是Pet,并且永遠(yuǎn)是Pet,但是它的動態(tài)類型卻會隨著我們賦給它的動態(tài)值而變化。
比如,只有我把一個*Dog類型的值賦給變量pet之后,該變量的動態(tài)類型才會是*Dog。如果還有一個Pet接口的實現(xiàn)類型*Fish,并且我又把一個此類型的值賦給了pet,那么它的動態(tài)類型就會變?yōu)?Fish。
還有,在我們給一個接口類型的變量賦予實際的值之前,它的動態(tài)類型是不存在的
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的實現(xiàn)類型嗎: %v\n", ok)_, ok = interface{}(&dog).(Pet)fmt.Printf("*Dog是接口Pet的實現(xiàn)類型嗎: %v\n", ok)fmt.Println()// 示例2var pet Pet = &dogfmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())}實現(xiàn)規(guī)則
接口變量的值并不等同于這個可被稱為動態(tài)值的副本。它會包含兩個指針,一個指針指向動態(tài)值,一個指針指向類型信息
規(guī)則一:如果使用指針方法來實現(xiàn)一個接口,那么只有指向那個類型的指針才能夠?qū)崿F(xiàn)對應(yīng)的接口。
規(guī)則二:如果使用值方法來實現(xiàn)一個接口,那么那個類型的值和指針都能夠?qū)崿F(xiàn)對應(yīng)的接口
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或打印一個變量的值的時候,會判斷這個變量是否實現(xiàn)了Stringer接口,如果實現(xiàn)了,則調(diào)用這個變量的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值做類型判斷?
當(dāng)i是個接口變量的時候,可以用i.(type)來對這個接口變量hold的值類型做判斷
switch v := i.(type) { case int:... case string:... default:... }注意:之前類型斷言是用i.($TYPE)比如i.(int)來判斷是不是int類型,但是這里用關(guān)鍵字type。關(guān)鍵字type只能用在switch語句里,如果用在switch外面會報錯,比如:
a := i.(type) fmt.Printf("%v %T\n", a, a)報錯:
use of .(type) outside type switch除了能識別內(nèi)置類型,也可以識別其他類型,比如函數(shù)類型,或者帶指針的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喔。
這個例子是函數(shù):
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可以用是否實現(xiàn)接口做判斷?
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只能用于類型判斷(包括實現(xiàn)接口),沒有辦法判斷是否為一個接口。就是說沒有辦法判斷一個變量是否為接口變量,即使用反射也是無法判斷的
總結(jié)
以上是生活随笔為你收集整理的golang中的接口的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang中的权限
- 下一篇: golang中的方法