go中方法的接收者是值或者指針的區別


值類型的變量和指針類型的變量

先聲明一個結構體:

type T struct {
    Name string
}

func (t T) M1() {
    t.Name = "name1"
}

func (t *T) M2() {
    t.Name = "name2"
}
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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)
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

輸出結果為:

M1調用前: t1
M1調用后: t1
M2調用前: t1
M2調用后: name2
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

下面猜測一下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)
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

輸出結果為:

M1調用前: t2
M1調用后: t2
M2調用前: t2
M2調用后: name2
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

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

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

傳給接口會怎樣?

先聲明一個接口

type Intf interface { M1() M2() }
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

使用:

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

    var t2 Intf = t1
    t2.M1()
    t2.M2()
   
   
   
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

報錯:

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

var t2 Intf = t1 這一行報錯。

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

簡單來說,按照接口的理論:傳過去【賦值】的對象必須實現了接口要求的方法,而t1沒有實現M2(),t1的指針實現了M2()。另外和c語言一樣,函數名本身就是指針

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

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

嵌套類型

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

type S struct { T }
  
  
  
          
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

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

    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)
  
  
  
          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

輸出:

M1調用前: t1 M1調用后: t1 M2調用前: t1 M2調用后: name2 t1
  
  
  
          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

將 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()
  
  
  
          
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

報錯:

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

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

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


下面嵌入 *T 試試:

type S struct { *T }
  
  
  
          
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

使用時這樣:

    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)
  
  
  
          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

惟一的區別是最后 t1 的值變了,因為我們復制的是指針。

接着賦值給接口試試:

    var intf Intf = s     intf.M1()     intf.M2()     fmt.Println(s.Name)
  
  
  
          
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

編譯沒有報錯。這里我們傳遞給 intf 的是值類型而不是指針,為什么可以通過呢?

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM