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 the value of a field item. Because v is addressable, it is automatically referenced as the pointer receiver for the print() method. So v.print() is using the address of v 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 a field value. When you call v.print() in this case, you are operating on the value that v points to, which is stored in data, 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会被复用。
---------------------------------------------------------------------------------------------------
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值