轉自:https://segmentfault.com/a/1190000012329213segmentfault
先聲明一個結構體:函數
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() 。spa
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)
輸出結果爲:scala
M1調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2指針
下面猜想一下go會怎麼處理。code
先來約定一下:接收者能夠看做是函數的第一個參數,即這樣的: 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 。get
下面聲明一個 *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調用前: t2
M1調用後: t2
M2調用前: t2
M2調用後: 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沒有實現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 }
使用下面的例子測試一下:
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調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2
t1
將 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)
M1調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2
name2
唯一的區別是最後 t1 的值變了,由於咱們複製的是指針。
接着賦值給接口試試:
var intf Intf = s i
ntf.M1()
intf.M2()
fmt.Println(s.Name)
編譯沒有報錯。這裏咱們傳遞給 intf 的是值類型而不是指針,爲何能夠經過呢?
拷貝 s 的時候裏面的 T 是指針類型,因此調用 M2() 的時候傳遞進去的是一個指針。
var intf Intf = &s 的效果和上面同樣。