go语言之接口
一:接口的基本概念
1 接口聲明
接口字面量,接口命名類型,接口聲明使用interface關鍵字。
1)接口字面量類型聲明語法如下:
interface{methodSignature1 methodSignature1 }? 2)接口命名類型使用type關鍵字聲明
type interfaceName interface {MethodSignature1MethodSignature2 } 接口定義大括號內可以是方法聲明的集合,也可以嵌入另一個接口類型匿名字段,還可以是二者的混合。 接口支持嵌入匿名接口宇段,就是一個接口定義里面可以包括其他接口, Go編譯器會自動進行展開 理, type Reader interface { Read(p []byte ) (n int , err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer } type ReadWr ter interface { Reader Wr te(p []byte) (n int, err error) } type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n nt err error) }3)方法聲明
MethodName (InputTypeList)OutputTypeList? ? 4)聲明新接口類型的特點
(I)接口的命名一般以“er ”結尾 (2)接口定義的內部方法聲明不需要 func 引導。 (3)在接口定義中,只有方法聲明沒有方法實現。5) 接口的定義與實現
package mainimport ("fmt" )type Humaner interface {//方法 Say() } //學生結構體 type Student struct {name stringscore int }func (s*Student) Say() {fmt.Println("Student[%s,%d]瞌睡不斷\n",s.name,s.score) } type Teacher struct {name stringgroup string }func (t *Teacher) Say() {fmt.Println("Teacher[%s,%s] 誨人不倦\n",t.name,t.group) } //自定義類型 type Mystr stringfunc (str Mystr) Say() {fmt.Println("Mystr[%s] 統治醒醒,還有個bug\n",str) } //參數為接口類型 func Whosay(i Humaner) {i.Say() } func main(){s :=&Student{"學生",88}t :=&Teacher{"老師","GO語言"}var tmp Mystr="字符串"s.Say()t.Say()tmp.Say()//多態,條用同一接口不同的表現 Whosay(s)Whosay(t)Whosay(tmp)//make()x :=make([]Humaner,3)x[0],x[1],x[2] = s,t,tmpfor _,value :=range x{value.Say()} }接口的繼承
package mainimport "fmt" //定義接口 type Humaner interface {//方法 Say() } type Personer interface {//相當于寫了say() 方法的繼承 Humaner//唱歌Sing(lyrics string) } type Student struct {name stringscore int } func (s *Student) Say() {fmt.Printf("Student[%s,%d] 瞌睡不斷\n",s.name,s.score) //Student[學生,80] 瞌睡不斷 }func (s *Student) Sing(lyrics string){fmt.Printf("Student sing[%s]!!\n",lyrics) //Student sing[葫蘆娃]!! } func main() {s := &Student{"學生",80}//調Personer方法var p Personerp = sp.Say()p.Sing("葫蘆娃") }2 接口初始化
單純地聲明一個接口變量沒有任何意義,接口只有被初始化為具體的類型時才有意義。接口作為一個膠水層或抽象層,起到抽象和適配的作用 。沒有初始化的接口變量,其默認值是 nil。
3 接口綁定具體類型的實例的過程稱為接口初始化。接口變量支持兩種直接初始化方法
1)實例賦值接口
如果具體類型實例的方法集是某個接口的方法集的超集,則稱該具體類型實現了接口,可 以將該具體類型的實例直接賦值給接口類型的變 ,此時編譯器會進行靜態的類型檢查。接口 被初始化后,調用接口的方法就相當于調用接口綁定的具體類型的方法,這就是接口調用的語義。2)接口變量賦值接口變量
已經初始化的接口類型變量a直接賦值給另一種接口變量b ,要求b的方法集是a的方法即 的子集 此時 Go 編譯器會在編譯時進行方法集靜態檢查 這個過程也是接口初始化的一種 方式,此時接口變量 綁定的具體實例是接口變量 綁定的具體實例的副本。 file ,_ := os .OpenFile (” notes.txt”, os.O_RDWR |os.O CREATE , 0755 ) var rw io .ReadWriter = file //io.ReadWriter 口可以直接賦位給 io.Writer接口變量 var w o.Writer = rw4 接口方法的調用
接口方法調用和普通的函數調用是有區別的。接口方法調用的最終地址是在運行期決定的, 將具體類型變量賦值給接口后,會使用具體類型的方法指針初始化接口變量,當調用接口變量的方法時, 實際上是間接地調用實例的方法。接口方法調用不是 種直接的調用,有 定的運行時開銷直接調用禾初始化的接口變 的方法會產生 panic 。
package main type printer interface {Print() } type S struct {} func (s S) Print() {println("print") } func main() {var i printer//沒有初始化的接口調用其他方法會產生panic//必須初始化i = S{}i.Print() }5 接口動態類型和靜態類型
1)動態類型
接口綁定的具體實例的類型稱為接口的動態類型。接口可以綁定不同類型的實例,所以接 口的動態類型是隨著其綁定的不同類型實例而發生變化的。2) 靜態類型
接口被定義時, 其類型就已經被確定 這個類型 接口的靜態類型。接口的靜態類型在其 定義 就被確定,靜態類型的本質特征就是接口的方法簽名集合。兩個接口如果方法簽名集合 相同(方法的順序可以不同),則這兩個接口在語義上完全等價,它們之間不需要強制類型轉換就可以相互賦值。 原因是 Go 編譯器校驗接口是否能賦值,是比較二者的方法集,而不是看具體接口類型名。二: 接口運算
1 語法:
i.(TypeNname) i必須是接口變 ,如果是具體類型變量,則編譯器會報 on interface type xxx on left, TypeNname 可以是接口類型名,也可以是具體類型名。2 接口查詢的兩層含義
(1)如果 TypeNname 是一個具體類型名,則類型斷言用于判斷接口變量 綁定的實例類 型是否就是具體類型 TypeNname (2)如果 TypeName 是一個接口類型名,則類型斷言用于判斷接口變量 綁定的實例類型 是否同時實現了 TypeName 接口。3 接口斷言的兩種語法表現
直接賦值模式
o := i.(TypeName)語義分析:
(1) TypeNam 是具體類型名,此時如果接 綁定的實例類型就是具體類型 TypeName, 變量 。的類型就是 TypeName 變量。的值就是接口綁定的實例值的副本(當然實例可能是 指針值,那就是指針值的副本) (2) TypeName 是接口類型名 如果接口i綁定的實例類型滿足接口類型 TypeName ,則變量o 的類型就是接口類型 TypeName,o底層綁定的具體類型實例是i綁定的實例的副本(當然實例可能是指針值,那就是指針值的副本〉。 (3)如果上述兩種情況都不滿足, 則程序拋 panic示例
package mainimport "fmt"type Inter interface {Ping()Pang() } type Anter interface {InterString() } type St struct {Name string }func (St) Ping() {println("ping") } func (*St) Pang() {println("pang") } func main() {st := &St{"andes"}var i interface{}=st//判斷i綁定的實例是否實現了接口類型Intero :=i.(Inter)o.Ping()o.Pang()//如下語句會引發panic,因為i沒有實現接口Anter//p :=i.(Anter)//p.String()//判斷 i綁定的實例是否就是具體類型Sts := i.(*St)fmt.Printf("%s",s.Name) }4? comma,ok 表達模式如下
if o,ok :=i.(TypeName);ok{ }語法分析
(1)TypeName是具體類型名,此時如果接口i綁定的實例類型就是具體類型TypeName,則ok為true變量。變量o的類型就是TypeName, 變量o的值就是接口綁定的實例值的副本(當然實例可能是指針值,那就是指針值的副本) (2)TypeName是接口類型名,此時如果接口i綁定的實例類型滿足接口類型TypeName,則ok為true,變量o的類型就是接口類型TypeName,o底層綁定的具體類型實例是i綁定的實例的副本(當然實例可能是指針值,那就是指針值的副本)。 (3)如果上述兩個都不滿足,則 ok為 false 變量o是TypeName 類型的“零值”,此種條 件分支下程序邏輯不應該再去引用o,因為此時的o沒有意義
示例:
package mainimport ("fmt" )type Inter interface {Ping()Pang() } type Anter interface {InterString() } type St struct {Name string } func (St) Ping(){println("ping") } func (*St) Pang(){println("pang") } func main(){st := &St{"andes"}var i interface{} = st//判斷i綁定的實例是否實現了接口類型Interif o,ok := i.(Inter);ok{o.Ping() //pingo.Pang() //pang }if p,ok := i.(Anter);ok{//i沒有實現接口Anter,所以程序不會執行到這里 p.String()}//判斷i 綁定的實例是否就是具體類型Stif s,ok := i.(*St);ok{fmt.Printf("%s",s.Name) //andes } }5? 類型查詢
語法格式:
switch v :=工. (type) { case typel : xx xx case type2 : xx xx default : xx xx語義分析:
語義: 1 查詢一個接口變量底層綁定的底層變量的具體類型是什么, 2 查詢接口變量綁定的底層變量是否實現了其他接口1)i 必須是接口類型
描述: 具體類型實例的類型是靜態的 在類型聲明后就不再變化,所 具體類型的變量不存在類 型查詢 類型查詢一定是對一個接口變量進行操作。也 就是說,上文中的i必須是接口變 如果 是未初始 接口變量,則的值是nil 。 var i io.Reader switch v := i.(type) { //此處i是為未初始化的接口變量,所以v為nilcase nil : fmt.Printf( " %T\n ”,v ) //<nil> default : fmt.Printf (”default”) }( 2 ) case 字句后面可 m~ 非接口類型名,也可以跟接口類型名,匹配是按照 case 子句的
順序進行的。
示例:
package mainimport ("io""log""os" )func main() {f,err := os.OpenFile ("notes.txt",os.O_RDWR|os.O_CREATE, 0755)if err != nil {log.Fatal(err)}defer f.Close()var i io.Reader = fswitch v :=i.(type) {//i的綁定的實例是*osFile類型,實現 io.ReadWriter接口,所以case匹配成功case io.ReadWriter://v是io.ReadWriter 接口類型,所以可以調用Write方法v.Write( []byte ("io.ReadWriter\n" ))//由于上一個case 已經匹配,就算這個case 也匹配,也不會走到這里case *os.File:v.Write ([]byte ("*os.File\n"))v.Sync ()default:return} } 如果case后面跟著多個類型,使用逗號分隔,接口變量i綁定的實例類型只要和其中一個類型匹配, 則直接使用o賦值給 v,相當于v := o 這個語法有點奇怪,按理說編譯器不應該允許這種操作, 語言實現者可能想讓 type switch 語句和普通的 sw itch 語句保持一樣的語法規則,允許發生這種情況。 package mainimport ("fmt""io""log""os" )func main(){f,err := os.OpenFile("notes1.txt",os.O_RDWR|os.O_CREATE,0756)if err != nil {log.Fatal(err)}defer f.Close()var i io.Reader = fswitch v := i.(type) {//多個類型,f滿足其中任何一個就算匹配case *os.File,io.ReadWriter://此時相當于執行v :=i ,v和i是等價的,使用v沒有意義if v==i{fmt.Println(true) //true }default:return} }6 標準庫的使用
格式:
switch i := i.(type) { }類型查詢和類型斷言
(1)類型查詢和類型斷言具有相同的語義,只是語法格式不同。 二者都能判斷接口變量綁 定的實例的具體類型,以及判斷接口變量綁定的實例是否滿足另一個接口類型。 (2)類型查詢使用 case 字句一次判斷多個類型,類型斷言一次只能判斷一個類型, 當然類型斷言也可以使用 if else if 語句達到同樣的效果7? 接口優點和使用形式
接口優點
(1)解禍:復雜系統進行垂 和水平的分割是常用的設計手段,在層與層之間使用接口進 行抽象和解輯是 種好的編程策略 Go 的非侵入式的接口使層與層之間的代碼更加干凈, 具體類型和實現的接口之間不需要顯式聲明,增加了接口使用的自由度 (2)實現泛型:由于現階段Go語言還不支持泛型,使用空接口作為函數或方法參數能夠用在需要泛型的場景中接口的使用形式
(3)作為結構 嵌字段。 (2)作為函數或方法的形參。 (3)作為函數或方法的返回值。 (4)作為其他接口定義的嵌入宇段。三: 空接口
概述: 沒有任何方法的接口,我們稱之為空接 。空接口表示為 interface{} 用途: 空接口和泛型 Go 語言沒有泛型, 如果一個函數需要接收任意類型的參數, 則參數類型可以使用空接口,這是彌補沒有泛型的一種手段 //典型的就是 fmt 標準 里面的 print 函數 func Fprint (w io.Writer, a . . . interface(}) (n int, err error) 空接口和反射 空接口是反射實現 基礎 反射庫就是將相關具體的類型轉換并賦值給空接 后才去處理,1 空接口和nil
空接口不是真的為空,接口有類型和值兩 概念
示例
package main import ("fmt" ) type Inter interface {Ping()Pang() } type St struct {}func (St) Ping(){println("ping") } func (*St) Pang(){println("pang") //pamg } func main(){var st *St = nilvar it Inter = stfmt.Printf("%p\n",st) //0x0 fmt.Printf("%p\n",it) //0x0if it !=nil {it.Pang()//下面的語句會導致 panic//方法轉換為函數調用,第 一個參數是St類型,由于 St是nil ,無法獲取指針所指的//對象佳,所以導致 panic//it.Ping } }? comma-ok斷言
package mainimport ("fmt" )//空接口 type Element interface {}type Person struct {name stringage int }func main() {//3容量的切片list := make([]Element,3)list[0] = 1 //intlist[1]="Hello" //stringlist[2] = Person{"zhangsan",18}for index,element := range list {//類型斷言: value,ok =element,(T)if value,ok :=element.(int);ok {fmt.Printf("list[%d]是int類型,值是%d\n",index,value) //list[0]是int類型,值是1}else if value,ok := element.(string);ok { fmt.Printf("list[%d]是string類型,值是%s\n",index,value) //list[1]是string類型,值是Hello}else {fmt.Printf("list[%d]是其他類型\n",index) //list[2]是其他類型 }} }switch 接口測試
package mainimport "fmt" //空接口 type Element interface{}type Person struct {name stringage int } func main() {list := make([]Element, 3)list[0] = 1 //intlist[1] = "Hello" //stringlist[2] = Person{"zhangsan", 18}for index,element := range list{switch value := element.(type) {case int :fmt.Printf("list[%d]是int類型,值是%d\n",index,value)case string:fmt.Printf("list[%d]是string類型,值是%s\n",index,value)default:fmt.Printf("list[%d]是其他類型\n",index)}} }?
?
四: 接口的內部實現(這個涉及底層很多東西,我不會)
?
轉載于:https://www.cnblogs.com/liucsxiaoxiaobai/p/10806634.html
總結
 
                            
                        - 上一篇: 京东怎么投诉商家,对商家有什么影响
- 下一篇: 全民k歌app怎么下载(全民娱乐的互动直
