一个让人困惑的go指针问题

我在stackoverflow上看到一个让人困惑的用例:

package main

type Point struct {
    x int
    y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func modifyPointer(point *Point) {
    point.x = 5
    point.y = 5
}

func modifyReference(point *Point) {
    point = &Point{5, 5}
}

func main() {
    p := Point{0, 0}
    fmt.Println(p) // prints (0, 0)

    modifyPointer(&p)
    fmt.Println(p) // prints (5, 5)

    p = Point{0, 0}
    modifyReference(&p)
    fmt.Println(p) // prints (0, 0) 困惑在这里,我期待的是(5, 5)
}

在以上代码中modifyPointermodifyReference都传递了指针,但只有modifyPointer成功改变了原来的值,请问原因是什么?它们的用法有什么不同?

阅读 3.1k
6 个回答

看到这种问题,我就觉得挺有意思,但是看了各位的回答,虽然都说到了值传递这个东西,但是没有说到点子上,关于值传递这个东西。我有篇文章讲得听清楚的。先贴一下:

golang中对“引用传递”的误解

理解了值传递后,然后回到问题本身。

  1. 首先我们看一下main里初始化p := Point{0, 0}
  2. 然后这个p的地址假设为:0xc00000a0b0,也就是从这个内存地址开始,存储了p这个数据,这个p的值为Point{0, 0}
  3. 然后来看两个func的定义:func modifyPointer(point *Point)func modifyReference(point *Point),这里两个func的参数point都是Point的指针类型,所以传进来的肯定是个指针。
  4. 然后看调用,都没问题,传递了两个指针的值&p,也就是上面的0xc00000a0b0
  5. 但是呢,回到值传递的概念,调用这两个func的时候,既然是值传递,那么肯定需要一个变量来存储这个值,也就是定义在func参数里的point *Point,所以来说,这个point存储的是指针值0xc00000a0b0。但是每个point有自己的地址,可以分别在modifyPointermodifyReference里把这两个point的地址打印出来,假设这两个地址分别为:0xc0000060300xc000006038,这两个point的地址是不一样的。

以上几个步骤说完了之后,我们来单独看modifyPointermodifyReference这两个方法的写法区别:

func modifyPointer(point *Point) {
    point.x = 5
    point.y = 5
}

func modifyReference(point *Point) {
    point = &Point{5, 5}
}

这里的区别很大的,先看modifyPointer里的处理方式,point.x = 5和以及point.y = 5,这种写法,你要搞清楚一个基础就是,也不说基础吧,从语义上来说,就拿point.x = 5来说,这里的意思是访问point.x并把point.x的值改为5,golang在访问数据的时候,如果这个数据是指针,然后自动寻址,找到原数据的位置(虽然这里的point也是地址的拷贝),然后呢,根据具体操作把原数据地址上的数据就行修改了。(注意:这里说的是访问数据)

然后看modifyReference,看起来好像是没啥区别,但是这里的意思是直接把point地址位置的值赋值为&Point{5, 5},由于point是地址拷贝,那么赋值之后,与modifyReference外部就没啥关系了,也就是说这里直接操作的是point

也就是说,对于modifyReference来说,操作的是point;而对于modifyPointer来说,操作的是point.xpoint.y,那么再操作point.xpoint.y的时候,访问xy的时候,发现xypoint的属性,但是呢,point只是一个指针,然后根据这个指针(位置)找到具体的point的值Point{0, 0},也就是一开始定义的p := Point{0, 0},自然原数据也会被修改了。

总体来说,就是两个函数的操作方式不一样,导致结果不一样,modifyReference修改的是指针的副本,与外部无关。对于modifyPointer来说,有个访问原数据的调用过程。

如果你想要再modifyReference实现同样的效果,也是可以的,代码如下:

func modifyReference(point *Point) {
    *point = Point{5, 5}
}

也就是说,通过point指针拿到原数据位置,然后修改原数据位置的值即可。

go全部都是值传递。

第二种情况的原因是
你下面那个p初始化为0,0了,
假如他的地址也就是&p是123,
你传参进去的只是&p的一个拷贝123,
函数得到参数123后在里面重新生成了一个&point把拷贝进去的123变成了456。
但外层的123没变。

第一种情况是
与上面前面都差不多
函数接受到123这个地址之后,他是在123这个地址上进行修改数据的。

modifyPointer相当于告诉施工队,你把我们家窗户和门改成黑色的吧
modifyReference相当于直接换了一套窗户和门是黑色的房子,老房子还是白门白窗户

==========

// 省略其他

func modifyReference(point *Point) *Point {
    point = &Point{5, 5}
    return point
}

func main() {
    p := Point{0, 0}
    fmt.Println(p) // prints (0, 0)

    modifyPointer(&p)
    fmt.Println(p) // prints (5, 5)

    p = Point{0, 0}
    p = *modifyReference(&p)
    fmt.Println(p) // prints (0, 0) 困惑在这里,我期待的是(5, 5)
}

感觉解释的不是太好,反正就是着重去理解“&”取地址,“*”取值,以及指针变量等几个相关概念。然后就是这个例子的目的,什么是引用传递,什么是值传递。

发现回答都不准确,
记住一点指针传递的实质,任然是值传递,依然有拷贝,只是这里的值是一个指针地址。

具体可以参考我这个回答
https://segmentfault.com/q/10...

新手上路,请多包涵

结构体根本不是引用类型,&之后只是取地址,比如拿到0x10,调用第二个函数之后把这个地址值传了进去存到point变量,然后你又创新了新的结构体,地址假如是0x20,那也只是替代了point变量的值,对外面的的p根本没任何影响,所以也就没有任何变化

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏