日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者)

發布時間:2023/11/28 生活经验 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 接口的定義

接口是和調用方的一種約定,它是一個高度抽象的類型,不用和具體的實現細節綁定在一起。接口要做的是定義好約定,告訴調用方自己可以做什么,但不用知道它的內部實現,這和我們見到的具體的類型如 intmapslice 等不一樣。

接口的定義和結構體稍微有些差別,雖然都以 type 關鍵字開始,但接口的關鍵字是 interface,表示自定義的類型是一個接口。

也就是說 person 是一個接口,它有兩個方法 sayName() stringsayAge() int,整體如下面的代碼所示:

type person interface {sayName() stringsayAge() int
}
}

針對 person 接口來說,它會告訴調用者可以通過它的 sayName() 方法獲取姓名字符串,通過它的 sayAge() 方法獲取年齡,這就是接口的約定。至于這個字符串怎么獲得的,長什么樣,接口不關心,調用者也不用關心,因為這些是由接口實現者來做的。

接口特點:

  • 接口只有方法聲明、沒有實現,沒有數據字段
  • 接口可以匿名嵌入其它接口,或者嵌入到結構中

接口是用來定義行為的類型,這些被定義的行為不由接口直接實現,而是由用戶定義的類型實現,一個實現了這些方法的具體類型是這個接口類型的實例。

如果用戶定義的類型實現了某個接口類型聲明的一組方法,那么這個用戶定義的類型的值就可以賦給這個接口類型的值。這個賦值會把用戶定義的類型存入接口類型的值。

2. 接口的實現

接口的實現者必須是一個具體的類型,以 student 結構體為例,

type student struct {name stringage  int
}

讓它來實現 person 接口,如下代碼所示:

func (s student) sayName() string {fmt.Printf("name is %v\n", s.name)return s.name
}func (s student) sayAge() int {fmt.Printf("name is %v\n", s.age)return s.age
}

給結構體類型 student 定義一個方法,這個方法和接口里方法的簽名(名稱、參數和返回值)一樣,這樣結構體 student 就實現了 person接口。

注意:如果一個接口有多個方法,那么需要實現接口的每個方法才算是實現了這個接口。

3. 接口賦值

接口賦值在 Go 語言中分為如下兩種情況:

  • 將對象實例賦值給接口;
  • 將一個接口賦值給另一個接口。

3.1 對象實例賦值給接口

先討論將某種類型的對象實例賦值給接口,這要求該對象實例實現了接口要求的所有方法,如下:

type Integer intfunc (a Integer) Less(b Integer) bool {return a < b
}func (a *Integer) Add(b Integer) {*a += b
}

相應地,我們定義接口 LessAdder ,如下:

type LessAdder interface {Less(b Integer) boolAdd(b Integer)
}

現在有個問題:假設我們定義一個 Integer 類型的對象實例,怎么將其賦值給 LessAdder接口呢?
應該用下面的語句(1),還是語句(2)呢?

var a Integer = 1
var b LessAdder = &a ... (1)
var b LessAdder = a ... (2)

答案是應該用語句(1)。原因在于,Go 語言可以根據下面的函數:

func (a Integer) Less(b Integer) bool {return a < b
}

自動生成一個新的 Less() 方法:

func (a *Integer) Less(b Integer) bool {return (*a).Less(b)
}

這樣,類型 *Integer 就既存在 Less() 方法,也存在 Add() 方法,滿足 LessAdder 接口。
而從另一方面來說,根據

	func (a *Integer) Add(b Integer)

這個函數無法自動生成以下這個成員方法:

func (a Integer) Add(b Integer) {(&a).Add(b)
}

因為 (&a).Add() 改變的只是函數參數a,對外部實際要操作的對象并無影響,這不符合用
戶的預期。

所以,Go 語言不會自動為其生成該函數。因此,類型 Integer 只存在 Less()方法,缺少 Add() 方法,不滿足 LessAdder 接口,故此上面的語句(2)不能賦值。

為了進一步證明以上的推理,我們不妨再定義一個 Lesser 接口,如下:

type Lesser interface {Less(b Integer) bool
}

然后定義一個 Integer 類型的對象實例,將其賦值給 Lesser 接口:

var a Integer = 1
var b1 Lesser = &a ... (1)
var b2 Lesser = a ... (2)

正如我們所料的那樣,語句(1)和語句(2)均可以編譯通過。

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

Go 語言中,只要兩個接口擁有相同的方法列表(次序不同不要緊),那么它們就是等同的,可以相互賦值。

4. 接口調用

實現了 person 接口后就可以使用了。首先我先來定義一個可以打印 person 接口的函數,如下所示:

func printPersonInfo(p person) {fmt.Printf("name is %v\n", p.sayName())fmt.Printf("age is %v\n", p.sayAge())
}

這個被定義的函數 printPersonInfo,它接收一個 person 接口類型的參數,然后打印出 person 接口的 sayName() 方法返回的字符串,sayAge() 方法返回年齡

printPersonInfo 這個函數的優勢就在于它是面向接口編程的,只要一個類型實現了 Stringer 接口,都可以打印出對應的字符串,而不用管具體的類型實現。

因為 student 實現了 person 接口,所以變量 stu 可以作為函數 printPersonInfo 的參數,可以用如下方式打印:

printPersonInfo(stu)

輸出

name is wohu
age is 20

現在讓結構體 teacher 也實現 person 接口,如下面的代碼所示:

type teacher struct {name stringage  int
}func (t teacher) sayName() string {return t.name
}func (t teacher) sayAge() int {return t.age
}

因為結構體 teacher 也實現了 person 接口,所以 printPersonInfo 函數不用做任何改變,可以直接被使用,如下所示:

printPersonInfo(t)

輸出結果:

name is Jack
age is 40

這就是面向接口的好處,只要定義和調用雙方滿足約定,就可以使用,而不用管具體實現。接口的實現者也可以更好的升級重構,而不會有任何影響,因為接口約定沒有變。

5. 值接收者和指針接收者

我們已經知道,如果要實現一個接口,必須實現這個接口提供的所有方法,而且在上節課講解方法的時候,定義一個方法,有值類型接收者和指針類型接收者兩種。二者都可以調用方法,因為 Go 語言編譯器自動做了轉換,所以值類型接收者和指針類型接收者是等價的。但是在接口的實現中,值類型接收者和指針類型接收者不一樣,下面我會詳細分析二者的區別。

上面已經驗證了結構體類型實現了 person 接口,那么結構體對應的指針是否也實現了該接口呢?我通過下面這個代碼進行測試:

printPersonInfo(&s)

輸出結果:

name is wohu
age is 20

測試后會發現,把變量 t 的指針作為實參傳給 printPersonInfo 函數也是可以的,編譯運行都正常。這就證明了以值類型接收者實現接口的時候,不管是類型本身,還是該類型的指針類型,都實現了該接口。

示例中值接收者(s student)實現了 person 接口,那么類型 student 和它的指針類型 *student 就都實現了 person 接口。

現在,我把接收者改成指針類型,如下代碼所示:

func (s *student) sayName() string {return s.name
}func (s *student) sayAge() int {return s.age
}

修改成指針類型接收者后會發現,示例中這行 printPersonInfo(stu) 代碼編譯不通過,提示如下錯誤:

demo.go:46:17: cannot use stu (type student) as type person in argument to printPersonInfo:student does not implement person (sayAge method has pointer receiver)

意思就是類型 student 沒有實現 person 接口。這就證明了以指針類型接收者實現接口的時候,只有對應的指針類型才被認為實現了該接口。

總結:

  • 當值類型作為接收者時,student 類型和 *student 類型都實現了該接口;

  • 當指針類型作為接收者時,只有 *student 類型實現了該接口;

可以發現,實現接口的類型都有 *student,這也表明指針類型比較萬能,不管哪一種接收者,它都能實現該接口。

package mainimport "fmt"type student struct {name stringage  int
}type teacher struct {name stringage  int
}type person interface {sayName() stringsayAge() int
}func (t teacher) sayName() string {return t.name
}func (t teacher) sayAge() int {return t.age
}func (s *student) sayName() string {// fmt.Printf("name is %v\n", s.name)return s.name
}func (s *student) sayAge() int {// fmt.Printf("age is %v\n", s.age)return s.age
}func printPersonInfo(p person) {fmt.Printf("name is %v\n", p.sayName())fmt.Printf("age is %v\n", p.sayAge())
}func main() {stu := student{name: "wohu", age: 20}// t := teacher{name: "Jack", age: 40}printPersonInfo(stu)// printPersonInfo(t)}

總結

以上是生活随笔為你收集整理的Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。