<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 專欄|接口 interface

          共 7567字,需瀏覽 16分鐘

           ·

          2021-08-31 21:14

          Duck Typing,鴨子類型,在維基百科里是這樣定義的:

          If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

          翻譯過來就是:如果某個東西長得像鴨子,游泳像鴨子,嘎嘎叫像鴨子,那它就可以被看成是一只鴨子。

          它是動態(tài)編程語言的一種對象推斷策略,它更關注對象能做什么,而不是對象的類型本身。

          例如:在動態(tài)語言 Python 中,定義一個這樣的函數(shù):

          def hello_world(duck):
              duck.say_hello()

          當調用此函數(shù)的時候,可以傳入任意類型,只要它實現(xiàn)了 say_hello() 就可以。如果沒實現(xiàn),運行過程中會出現(xiàn)錯誤。

          Go 語言作為一門靜態(tài)語言,它通過接口的方式完美支持鴨子類型。

          接口類型

          之前介紹的類型都是具體類型,而接口是一種抽象類型,是多個方法聲明的集合。在 Go 中,只要目標類型實現(xiàn)了接口要求的所有方法,我們就說它實現(xiàn)了這個接口。

          先來看一個例子:

          package main

          import "fmt"

          // 定義接口,包含 Eat 方法
          type Duck interface {
              Eat()
          }

          // 定義 Cat 結構體,并實現(xiàn) Eat 方法
          type Cat struct{}

          func (c *Cat) Eat() {
              fmt.Println("cat eat")
          }

          // 定義 Dog 結構體,并實現(xiàn) Eat 方法
          type Dog struct{}

          func (d *Dog) Eat() {
              fmt.Println("dog eat")
          }

          func main() {
              var c Duck = &Cat{}
              c.Eat()

              var d Duck = &Dog{}
              d.Eat()

              s := []Duck{
                  &Cat{},
                  &Dog{},
              }
              for _, n := range s {
                  n.Eat()
              }
          }

          使用 type 關鍵詞定義接口:

          type Duck interface {
              Eat()
          }

          接口包含了一個 Eat() 方法,然后定義兩個結構體類型 CatDog,分別實現(xiàn)了 Eat 方法。

          // 定義 Cat 結構體,并實現(xiàn) Eat 方法
          type Cat struct{}

          func (c *Cat) Eat() {
              fmt.Println("cat eat")
          }

          // 定義 Dog 結構體,并實現(xiàn) Eat 方法
          type Dog struct{}

          func (d *Dog) Eat() {
              fmt.Println("dog eat")
          }

          遍歷接口切片,通過接口類型可以直接調用對應方法:

          s := []Duck{
              &Cat{},
              &Dog{},
          }
          for _, n := range s {
              n.Eat()
          }

          // 輸出
          // cat eat
          // dog eat

          接口賦值

          接口賦值分兩種情況:

          1. 將對象實例賦值給接口

          2. 將一個接口賦值給另一個接口

          下面來分別說說:

          將對象實例賦值給接口

          還是用上面的例子,因為 Cat 實現(xiàn)了 Eat 接口,所以可以直接將 Cat 實例賦值給接口。

          var c Duck = &Cat{}
          c.Eat()

          在這里一定要傳結構體指針,如果直接傳結構體會報錯:

          var c Duck = Cat{}
          c.Eat()
          # command-line-arguments
          ./09_interface.go:25:6: cannot use Cat{} (type Cat) as type Duck in assignment:
              Cat does not implement Duck (Eat method has pointer receiver)

          但是如果反過來呢?比如使用結構體來實現(xiàn)接口,使用結構體指針來賦值:

          // 定義 Cat 結構體,并實現(xiàn) Eat 方法
          type Cat struct{}

          func (c Cat) Eat() {
              fmt.Println("cat eat")
          }

          var c Duck = &Cat{}
          c.Eat() // cat eat

          沒有問題,可以正常執(zhí)行。

          將一個接口賦值給另一個接口

          還是上面的例子,可以直接將 c 的值直接賦值給 d

          var c Duck = &Cat{}
          c.Eat()

          var d Duck = c
          d.Eat()

          再來,我再定義一個接口 Duck1,這個接口包含兩個方法 EatWalk,然后結構體 Dog 實現(xiàn)兩個方法,但是 Cat 只實現(xiàn) Eat 方法。

          type Duck1 interface {
              Eat()
              Walk()
          }

          // 定義 Dog 結構體,并實現(xiàn) Eat 方法
          type Dog struct{}

          func (d *Dog) Eat() {
              fmt.Println("dog eat")
          }

          func (d *Dog) Walk() {
              fmt.Println("dog walk")
          }

          那么在賦值時,使用 Duck1 賦值給 Duck 是可以的,反過來就會報錯。

          var c1 Duck1 = &Dog{}
          var c2 Duck = c1
          c2.Eat()

          所以,已經(jīng)初始化的接口變量 c1 直接賦值給另一個接口變量 c2,要求 c2 的方法集是 c1 的方法集的子集。

          空接口

          具有 0 個方法的接口稱為空接口,它表示為 interface {}。由于空接口有 0 個方法,所以所有類型都實現(xiàn)了空接口。

          func main() {
              // interface 形參
              s1 := "Hello World"
              i := 50
              strt := struct {
                  name string
              }{
                  name: "AlwaysBeta",
              }
              test(s1)
              test(i)
              test(strt)
          }

          func test(i interface{}) {
              fmt.Printf("Type = %T, value = %v\n", i, i)
          }

          類型斷言

          類型斷言是作用在接口值上的操作,語法如下:

          x.(T)

          其中 x 是接口類型的表達式,T 是斷言類型。

          作用是判斷操作數(shù)的動態(tài)類型是否滿足指定的斷言類型。

          有兩種情況:

          1. T 是具體類型

          2. T 是接口類型

          下面來分別舉例說明:

          具體類型

          類型斷言會檢查 x 的動態(tài)類型是否為 T,如果是,則輸出 x 的值;如果不是,程序直接 panic

          func main() {
              // 類型斷言
              var n interface{} = 55
              assert(n) // 55
              var n1 interface{} = "hello"
              assert(n1) // panic: interface conversion: interface {} is string, not int
          }

          func assert(i interface{}) {
              s := i.(int)
              fmt.Println(s)
          }

          接口類型

          類型斷言會檢查 x 的動態(tài)類型是否滿足接口類型 T,如果滿足,則輸出 x 的值,這個值可能是綁定實例的副本,也可能是指針的副本;如果不滿足,程序直接 panic

          func main() {
              // 類型斷言
              assertInterface(c) // &{}
          }

          func assertInterface(i interface{}) {
              s := i.(Duck)
              fmt.Println(s)
          }

          如果有兩個接收值,那么斷言不會在失敗時崩潰,而是會多返回一個布爾值,一般命名為 ok,來表示斷言是否成功。

          func main() {
              // 類型斷言
              var n1 interface{} = "hello"
              assertFlag(n1)
          }

          func assertFlag(i interface{}) {
              if s, ok := i.(int); ok {
                  fmt.Println(s)
              }
          }

          類型查詢

          語法類似類型斷言,只需將 T 直接用關鍵詞 type 替代。

          作用主要有兩個:

          1. 查詢一個接口變量綁定的底層變量類型

          2. 查詢一個接口變量的底層變量是否還實現(xiàn)了其他接口

          func main() {
              // 類型查詢
              SearchType(50)         // Int: 50
              SearchType("zhangsan"// String: zhangsan
              SearchType(c)          // dog eat
              SearchType(50.1)       // Unknown type
          }

          func SearchType(i interface{}) {
              switch v := i.(type) {
              case string:
                  fmt.Printf("String: %s\n", i.(string))
              case int:
                  fmt.Printf("Int: %d\n", i.(int))
              case Duck:
                  v.Eat()
              default:
                  fmt.Printf("Unknown type\n")
              }
          }

          總結

          本文從鴨子類型引出 Go 的接口,然后用一個例子簡單展示了接口類型的用法,接著又介紹了接口賦值,空接口,類型斷言和類型查詢。

          相信通過本篇文章大家能對接口有了整體的概念,并掌握了基本用法。


          文章中的腦圖和源碼都上傳到了 GitHub,有需要的同學可自行下載。

          地址: https://github.com/yongxinz/gopher/tree/main/sc

          關注公眾號 AlwaysBeta,回復「goebook」領取 Go 編程經(jīng)典書籍。

          Go 專欄文章列表:

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美三级在线视频麻豆 | 亚洲精品色视频 | 操B视频欧美 | 欧美三级特黄一区 | 91无码人妻精品1国产动漫 |