『就要學習 Go 語言』系列--第 24 篇分享文章html
以前的文章給你們總結過方法的一些基本用法,最近在學 Go 面向對象式編程,對方法又有一些新的認識,總結一下。
方法分爲值方法和指針方法,這篇文章主要來說講這二者的區別。二者的定義:接收者類型爲 T 的方法稱爲值方法;接收者類型爲 *T 的方法稱爲指針方法 其中 T 必須知足以下條件:golang
能夠認爲 T 是 *T 的基本類型。編程
以前也講過這個,值方法的接收者是原類型值的副本,指針方法的接收者是原類型值的指針副本。在值方法內對副本修改不會影響到原值,注意有例外,除非這個類型是引用類型的別名類型,例如切片、字典。而在指針方法內,對指針副本指向的值作的修改必定會體如今原值上。bash
type Book struct {
pages int
}
type Books []Book
func (books Books)modify() {
// 原值已被修改
books[0].pages = 188
// 下面這行代碼不會修改原值
books = append(books,Book{234})
}
func main() {
books := Books{
{123},
{456},
}
books.modify()
fmt.Println(books) // 輸出:[{188} {456}]
}
複製代碼
輸出:併發
[{188} {456}]
複製代碼
append() 調用不會影響到原值是由於該操做會從新申請一塊內存存放接收者 books,但不會影響到原值。咱們只將這兩行代碼換下順序,其餘代碼不變:app
func (books Books)modify() {
fmt.Println(books) // [{123} {456}]
books = append(books,Book{234})
fmt.Println(books) // [{123} {456} {234}]
books[0].pages = 188
fmt.Println(books) // [{188} {456} {234}]
}
func main() {
books := Books{
{123},
{456},
}
books.modify()
fmt.Println(books) // [{123} {456}]
}
複製代碼
輸出:函數
[{123} {456}]
[{123} {456} {234}]
[{188} {456} {234}]
[{123} {456}]
複製代碼
從結果能夠看出,上面代碼段的第 5 行,這個賦值操做隻影響新建立的切片,不會影響到原來的值。
若是想讓方法內的修改體如今原值上,可使用指針接收者。post
func (books *Books) modify() {
*books = append(*books, Book{234})
(*books)[0].pages = 188
}
func main() {
books := Books{
{123},
{456},
}
books.modify()
fmt.Println(books) // [{188} {456} {234}]
}
複製代碼
輸出:學習
[{188} {456} {234}]
複製代碼
咱們給結構體聲明兩個方法:ui
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
複製代碼
上面代碼聲明瞭兩個方法,一個值方法,一個指針方法。
每一個方法聲明的時候,編譯器會各自聲明相對應的隱式函數。例如上面這兩個方法對應的隱式函數是:
func Book.Pages(b Book) int {
return b.pages
}
func (*Book).SetPages(b *Book, pages int) {
b.pages = pages
}
複製代碼
從代碼能夠看出,接收者被當作形參,函數體與方法體依然保持一致。看下函數名 Book.Pages 和 (*Book).SetPages,能夠看做是 Type.MethodName 的結果,但函數不能包含特殊字符,因此這兩個函數不能顯式聲明,但咱們能夠調用這兩個函數。
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
func main() {
var book Book
(*Book).SetPages(&book, 188)
fmt.Println(Book.Pages(book)) // 188
}
複製代碼
事實上,編譯器不只隱式聲明瞭方法對應的函數,並且還重寫了方法,讓聲明的方法去調用隱式聲明的函數,就像下面這樣:
func (b Book) Pages() int {
return Book.pages(b)
}
func (b *Book) SetPages(pages int) {
(*Book).SetPages(b, pages)
}
複製代碼
理解方法集很是重要,來理一下,方法集是一組關聯到自定義類型的值或指針的方法。一個自定義類型 T 的方法集合僅包括它的值方法,該類型的指針類型 *T 的方法集包括全部的值方法和指針方法。例如:
type Dog struct {
Name string
}
func (d Dog) getName() string {
return d.Name
}
func (d *Dog) SetName(name string) {
d.Name = name
}
複製代碼
自定義類型 Dog,聲明瞭值方法 getName() 和指針方法 SetName()。Dog 類型的方法集合只包括值方法,即 getName(),而 *Dog 類型的方法集合包含這兩個方法。
嚴格意義上來講,基本類型 Dog 只能調用它的值方法,但實際寫代碼的時候,也能夠經過 Dog 類型調用到指針方法,是由於編譯器爲自動爲咱們轉譯了。
func main() {
var dog Dog
dog.SetName("dog") // 經過 Dog 類型調用指針方法
//(&dog).SetName("dog")
fmt.Println(dog.Name)
}
複製代碼
咱們來看一個經典的例子,對比下:
// 定義接口 notifier
type notifier interface {
notify()
}
type user struct {
name string
email string
}
func (u *user)notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// 接收一個 notifier 接口類型的參數,併發送通知
func sendNotification(n notifier) {
n.notify()
}
func main() {
u := user{"Seeklaod", "email@gmail.com"}
sendNotification(u)
}
複製代碼
上面的代碼定義了接口類型 notifier,只包含一個方法 notify(),實現了該方法的類型就認爲實現了接口 notifier,*user 類型實現了該方法,即實現了接口。另外還定義了一個函數 sendNotification(),該函數接收一個接口類型的值。 編譯運行下程序,發現報錯:
cannot use u (type user) as type notifier in argument to sendNotification:
user does not implement notifier (notify method has pointer receiver)
複製代碼
兩個錯誤:
其實主要就是由於第 2 個錯誤引發的,上面已經講過是 *user 實現了接口 notifier。
使用 *user 類型實現接口時爲何 user 類型沒法實現接口呢?這就須要瞭解上面說過的方法集,關於方法集,Go 語言規範是這樣定義的:
Values | Method Receivers |
---|---|
T | (t T) |
*T | (t T) 或 (t *T) |
是這個意思,自定義類型 T 的方法集合僅包括它的值方法,而該類型的指針類型 *T 的方法集包括全部的值方法和指針方法,上面也講過。對應到本例子,user 類型的方法集不包括方法 notify(),也就沒有實現接口 notifier。
如今知道問題出在哪了,修改下程序,讓程序跑起來:
func main() {
u := user{"Seeklaod", "email@gmail.com"}
sendNotification(&u) // 傳入地址
}
複製代碼
上面的代碼編譯經過。由於使用指針接收者實現的接口,只有 user 類型的指針能夠傳給 sendNotification 函數。
如今的問題是,爲何這種狀況下,編譯器沒有爲咱們自動轉譯呢?事實上,編譯器並非總能自動得到一個值的地址,這就是其中一種。
由於不是總能獲取一個值的地址,因此值的方法集只包括了使用值接收者實現的方法。
但願這篇文章可以幫助你,Good day!
參考資料:
1.教女友寫方法
2.Methods in Go
3.Methods, Interfaces and Embedded Types in Go
原創文章,若需轉載請註明出處!
歡迎掃碼關注公衆號「Golang來啦」或者移步 seekload.net ,查看更多精彩文章。
給你準備了學習 Go 語言相關書籍,公號後臺回覆【電子書】領取!