go语言方法Value Receiver, Pointer Receiver各种不同情况的实验笔记

htyu_0203_39 · · 808 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

注意: 此文章只是我的个人笔记,如有谬误,错误, 请一定指出!
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会被复用。
---------------------------------------------------------------------------------------------------

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:htyu_0203_39

查看原文:go语言方法Value Receiver, Pointer Receiver各种不同情况的实验笔记

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

808 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传