1. 程式人生 > >go語言方法Value Receiver, Pointer Receiver各種不同情況的實驗筆記

go語言方法Value Receiver, Pointer Receiver各種不同情況的實驗筆記

注意: 此文章只是我的個人筆記,如有謬誤,錯誤, 請一定指出!
for range 問題 package main import (       "fmt"     "time" ) type field struct {       name string } func (p *field) print() {       fmt.Printf("print: p: %p, v: %s\n", p, p.name) } func main() {       data := []field{ {"one"},{"two"},{"three"} }     for _,v := range data { // 注意:for語句中的迭代變數(如: v)在每次迭代時被重新使用, 一直複用         go v.print() // 注意: 此處可理解為: go (&v).print(), 也就是用v的指標去呼叫, 而且v會在每次迭代時複用,所以每一個呼叫的receiver都是共同指向v的指標, 而且v在最後一次迭代後, 被 賦值為:"three", 所以 才有了打印出3個"three"的結果.     }     time.Sleep(3 * time.Second)     //goroutines print: three, three, three } package main import (       "fmt"     "time" ) type field struct {       name string } func (p *field) print() {       fmt.Println(p.name) } func main() {       data := []*field{ {"one"},{"two"},{"three"} }     for _,v := range data {         go v.print() //v本身就是指標, 指向one, two, three; 迭代時會改變指向, 直接呼叫沒有複製,直接呼叫,故每次呼叫時v都是分別指向one, two , three的地址值 , 當然會打印出正確的結果 ; 與上例不同, 上例中,因為v的複用,每一次呼叫的pointer receiver都共同指向v, 但v的值在迴圈後最終賦值為:three, 所以出現錯誤結果。     }     time.Sleep(3 * time.Second) } //goroutines print: one, two, three 注意:四個要素解釋以上兩例子:

前提條件:方法定義時為Pointer receiver.
func (p *field) print() {       fmt.Printf("print: p: %p, v: %s\n", p, p.name) }
(1) 滿足前提條件下, Pointer Receiver不復制,所以當以值方式呼叫 時, 直接 &value取地址作為pointer receiver. (2)滿足前提條件下,若for _,v := range data中的v本身就是指標, 則直接呼叫. (3)for range會在每次迭代中複用v (4)go語言本身是值語義的, 也就是說傳參,呼叫, 迭代都會複製, 只不過像:指標, 引用型別只是複製了其本身, 而非其指向的data, 這樣複製代價很低很低;不過陣列為值型別的,會整體複製喲。 後而參考資料連結中有詳細的分析。
//stackoverflow中的解釋: 這在Go中是個很常見的技巧。for語句中的迭代變數在每次迭代時被重新使用。這就意味著你在for迴圈中建立的閉包(即函式字面量)將會引用同一個變數(而在那些goroutine開始執行時就會得到那個變數的值)。 In the first loop,v is thevalue of afield item. Becausev is addressable, it is automatically referenced as the pointer receiver for theprint() method. Sov.print() is using the address ofv
itself, and the contents of that address is overwritten each iteration of the loop. When you change the declaration to use a*field,v is now a pointer to afield value. When you callv.print() in this case, you are operating on the value thatv points to, which is stored indata, and the overwriting of v has no effect. ----------------------------------------------------我的試驗程式碼--------------------------------- package main import "fmt" import "time" type A int func (a A) ValueReceiver(){

fmt.Printf("ValueReceiver, p: %p, v: %d\n", &a, a) } func (a A) ValueReceiverIngo(){

fmt.Printf("ValueReceiverIngo, p: %p, v:%d\n", &a, a) } func (a A) ValueReceiverInDefer(){ fmt.Printf("ValueReceiverInDefer, p: %p, v:%d\n", &a, a) } func (a A) ValueRececiverInforRange(){ fmt.Printf("ValueRececiverInforRange, p: %p, v: %d\n", &a, a) } func main() { var a A = 1 fmt.Printf("main, p: %p, v: %d\n", &a, a) a.ValueReceiver() p := &a p.ValueReceiver() //------------call in goroutine-------- go a.ValueReceiverIngo() go p.ValueReceiverIngo() time.Sleep(3* time.Second) //---------call in defer---------- defer a.ValueReceiverInDefer() defer p.ValueReceiverInDefer() //---call in for range array, value receiver--- as := [5]A{1,2,3,4,5} fmt.Printf("as[0]: %p, %d\n", &as[0], as[0]) for _, a := range as { fmt.Printf("as in for: %p, %d\n", &as[0], as[0]) fmt.Printf("a in for: %p, v: %d\n", &a, a) a.ValueRececiverInforRange() pf := &a pf.ValueRececiverInforRange() } } //結果 main, p: 0x10434114, v: 1
ValueReceiver, p: 0x1043411c, v: 1
ValueReceiver, p: 0x10434134, v: 1
ValueReceiverIngo, p: 0x1043413c, v:1
ValueReceiverIngo, p: 0x10434144, v:1
as[0]: 0x10430240, 1
as in for: 0x10430240, 1
a in for: 0x10434150, v: 1
ValueRececiverInforRange, p: 0x1043415c, v: 1
ValueRececiverInforRange, p: 0x10434164, v: 1
as in for: 0x10430240, 1
a in for: 0x10434150, v: 2
ValueRececiverInforRange, p: 0x10434174, v: 2
ValueRececiverInforRange, p: 0x1043417c, v: 2
as in for: 0x10430240, 1
a in for: 0x10434150, v: 3
ValueRececiverInforRange, p: 0x1043418c, v: 3
ValueRececiverInforRange, p: 0x10434194, v: 3
as in for: 0x10430240, 1
a in for: 0x10434150, v: 4
ValueRececiverInforRange, p: 0x104341a4, v: 4
ValueRececiverInforRange, p: 0x104341ac, v: 4
as in for: 0x10430240, 1
a in for: 0x10434150, v: 5
ValueRececiverInforRange, p: 0x104341bc, v: 5
ValueRececiverInforRange, p: 0x104341c4, v: 5
ValueReceiverInDefer, p: 0x104341cc, v:1
ValueReceiverInDefer, p: 0x104341d4, v:1 結論:通過分析以上地址, 對於value receciver method, 各種呼叫方式下, 都是對於原值的複製, 也就是說,以副本為receiver呼叫, 即使是以指標方式呼叫,也是以*pointer 生成副本後再呼叫 ;同時也注意到for range中複用了a (for _, a := range as ).-------------------------------------------------------------------------------------- -------------------------------------Pointer Receiver Test-------------------------- package main import "fmt" import "time" type A int func(p *A)PointerReceiver(){ fmt.Printf("PointerReceiver, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiveringo(){ fmt.Printf("PointerReceiveringo, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiverinDefer(){ fmt.Printf("PointerReceiverinDefer, p: %p, v: %d\n", p, *p) } func(p *A)PointerReceiverinforRange(){ fmt.Printf("PointerReceiverinforRange, p: %p, v: %d\n", p, *p) } func main() { var a A = 1 fmt.Printf("main, p: %p, v: %d\n", &a, a) a.PointerReceiver() p := &a p.PointerReceiver() //------------------ go a.PointerReceiveringo() go p.PointerReceiveringo() time.Sleep(3* time.Second) //------------------ defer a.PointerReceiverinDefer() defer p.PointerReceiverinDefer() //------------------ as := [5]A{1,2,3,4,5} fmt.Printf("as[0], p: %p, v: %d\n", &as[0], as[0]) for _, a := range as { fmt.Printf("as in for: p: %p, v: %d\n", &as[0], as[0]) fmt.Printf("a in for: p: %p, v: %d\n", &a, a) a.PointerReceiverinforRange() p := &a p.PointerReceiverinforRange() } } 結果: main, p: 0x10434114, v: 1
PointerReceiver, p: 0x10434114, v: 1
PointerReceiver, p: 0x10434114, v: 1
PointerReceiveringo, p: 0x10434114, v: 1
PointerReceiveringo, p: 0x10434114, v: 1
as[0], p: 0x10430240, v: 1
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 1
PointerReceiverinforRange, p: 0x10434140, v: 1
PointerReceiverinforRange, p: 0x10434140, v: 1
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 2
PointerReceiverinforRange, p: 0x10434140, v: 2
PointerReceiverinforRange, p: 0x10434140, v: 2
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 3
PointerReceiverinforRange, p: 0x10434140, v: 3
PointerReceiverinforRange, p: 0x10434140, v: 3
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 4
PointerReceiverinforRange, p: 0x10434140, v: 4
PointerReceiverinforRange, p: 0x10434140, v: 4
as in for: p: 0x10430240, v: 1
a in for: p: 0x10434140, v: 5
PointerReceiverinforRange, p: 0x10434140, v: 5
PointerReceiverinforRange, p: 0x10434140, v: 5
PointerReceiverinDefer, p: 0x10434114, v: 1
PointerReceiverinDefer, p: 0x10434114, v: 1 結論: 通過以上地址的分析, 對於pointer receiver method, 在呼叫時, 不會生成副本, 而是原物件本身的地址;也就是說沒有複製; 在(for _, a := range as) 中的a會被複用。 ---------------------------------------------------------------------------------------------------