go中方法的接收者是值或者指標有什麼區別嗎?

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

參考文章:http://studygolang.com/articles/1113

希望解決的問題如下

假設有兩個方法,一個方法的接收者是指標類型,一個方法的接收者是實值型別,那麼:

  • 對於實值型別的變數和指標類型的變數,這兩個方法有什麼區別?
  • 如果這兩個方法是為了實現一個介面,那麼這兩個方法都可以調用嗎?
  • 如果方法是嵌入到其他結構體中的,那麼上面兩種情況又是怎樣的?

實值型別的變數和指標類型的變數

先聲明一個結構體:

type T struct {    Name string}func (t T) M1() {    t.Name = "name1"}func (t *T) M2() {    t.Name = "name2"}

M1() 的接收者是實值型別 T, M2() 的接收者是實值型別 *T , 兩個方法內都是改變Name值。

下面聲明一個 T 類型的變數,並調用 M1()M2()

    t1 := T{"t1"}    fmt.Println("M1調用前:", t1.Name)    t1.M1()    fmt.Println("M1調用後:", t1.Name)    fmt.Println("M2調用前:", t1.Name)    t1.M2()    fmt.Println("M2調用後:", t1.Name)

輸出結果為:

M1調用前: t1M1調用後: t1M2調用前: t1M2調用後: name2

下面猜測一下go會怎麼處理。

先來約定一下:接收者可以看作是函數的第一個參數,即這樣的: func M1(t T), func M2(t *T)。 go不是物件導向的語言,所以用那種看起來像物件導向的文法來理解可能有偏差。

當調用 t1.M1() 時相當於 M1(t1) ,實參和行參都是類型 T,可以接受。此時在M1()中的t只是t1的值拷貝,所以M1()的修改影響不到t1。

當調用 t1.M2() => M2(t1),這是將 T 類型傳給了 *T 類型,go可能會取 t1 的地址傳進去: M2(&t1)。所以 M2() 的修改可以影響 t1 。

T 類型的變數這兩個方法都是擁有的。

下面聲明一個 *T 類型的變數,並調用 M1()M2()

    t2 := &T{"t2"}    fmt.Println("M1調用前:", t2.Name)    t2.M1()    fmt.Println("M1調用後:", t2.Name)    fmt.Println("M2調用前:", t2.Name)    t2.M2()    fmt.Println("M2調用後:", t2.Name)

輸出結果為:

M1調用前: t2M1調用後: t2M2調用前: t2M2調用後: name2

t2.M1() => M1(t2), t2 是指標類型, 取 t2 的值並拷貝一份傳給 M1。

t2.M2() => M2(t2),都是指標類型,不需要轉換。

*T 類型的變數也是擁有這兩個方法的。

傳給介面會怎樣?

先聲明一個介面

type Intf interface {    M1()    M2()}

使用:

    var t1 T = T{"t1"}    t1.M1()    t1.M2()    var t2 Intf = t1    t2.M1()    t2.M2()

報錯:

./main.go:9: cannot use t1 (type T) as type Intf in assignment:    T does not implement Intf (M2 method has pointer receiver)

var t2 Intf = t1 這一行報錯。

t1 是有 M2() 方法的,但是為什麼傳給 t2 時傳不過去呢?

t1 是實值型別,賦值給 t2 時是複製值而不是指標,假設 t1 可以賦值給 t2, t2.M2() 修改 Name 的值時也是修改的拷貝的變數,無法影響到 t1,那把 t1 賦值給 t2 還有什麼意義呢?所以這種賦值是不被允許的。

當把 var t2 Intf = t1 修改為 var t2 Intf = &t1 時編譯通過,此時 t2 獲得的是 t1 的地址, t2.M2() 的修改可以影響到 t1 了。

如果聲明一個方法 func f(t Intf) , 參數的傳遞和上面的直接賦值是一樣的情況。

巢狀型別

聲明一個類型 S,將 T 嵌入進去

type S struct {    T}

使用下面的例子測試一下:

    t1 := T{"t1"}    s := S{t1}    fmt.Println("M1調用前:", s.Name)    s.M1()    fmt.Println("M1調用後:", s.Name)    fmt.Println("M2調用前:", s.Name)    s.M2()    fmt.Println("M2調用後:", s.Name)    fmt.Println(t1.Name)

輸出:

M1調用前: t1M1調用後: t1M2調用前: t1M2調用後: name2t1

將 T 嵌入 S, 那麼 T 擁有的方法和屬性 S 也是擁有的,但是接收者卻不是 S 而是 T。

所以 s.M1() 相當於 M1(t1) 而不是 M1(s)

最後 t1 的值沒有改變,因為我們嵌入的是 T 類型,所以 S{t1} 的時候是將 t1 拷貝了一份。

假如我們將 s 賦值給 Intf 介面會怎麼樣呢?

    var intf Intf = s    intf.M1()    intf.M2()

報錯:

cannot use s (type S) as type Intf in assignment:    S does not implement Intf (M2 method has pointer receiver)

還是 M2() 的問題,因為 s 此時還是實值型別。

var intf Intf = &s 這樣的話編譯通過了,如果在 intf.M2() 中改變了 Name 的值, s.Name 被改變了,但是 t1.Name 依然沒變,因為現在 t1 和 s 已經沒有聯絡了。

下面嵌入 *T 試試:

type S struct {    *T}

使用時這樣:

    t1 := T{"t1"}    s := S{&t1}    fmt.Println("M1調用前:", s.Name)    s.M1()    fmt.Println("M1調用後:", s.Name)    fmt.Println("M2調用前:", s.Name)    s.M2()    fmt.Println("M2調用後:", s.Name)    fmt.Println(t1.Name)

惟一的區別是最後 t1 的值變了,因為我們複製的是指標。

接著賦值給介面試試:

    var intf Intf = s    intf.M1()    intf.M2()    fmt.Println(s.Name)

編譯沒有報錯。這裡我們傳遞給 intf 的是實值型別而不是指標,為什麼可以通過呢?

拷貝 s 的時候裡面的 T 是指標類型,所以調用 M2() 的時候傳遞進去的是一個指標。

var intf Intf = &s 的效果和上面一樣。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.