V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
vcfghtyjc
V2EX  ›  Go 编程语言

初学 Golang,有一个关于 Pointer receiver 的问题

  •  
  •   vcfghtyjc · 2019-11-24 12:47:54 +08:00 · 4096 次点击
    这是一个创建于 1587 天前的主题,其中的信息可能已经有所发展或是发生改变。

    根据 Go 文档中的例子 (链接), 如果 receiver 的类型是 *T ,那么传入的是指针;如果类型是 T, 则传入的是值的拷贝。 但是我在写代码的时候遇到一个奇怪的现象,明明我传入的是拷贝,但是函数对拷贝的操作影响到了原来的值。具体请看一面的代码:

    package main
    
    import (
    	"fmt"
    )
    
    type A struct {
    	a int
    }
    
    type Alist []*A
    
    func (a Alist) swap(i int, j int) {
    	a[i], a[j] = a[j], a[i]
    }
    
    func main() {
    	alist := make(Alist, 2)
    	alist[0] = &A{a: 3}
    	alist[1] = &A{a: 4}
    	fmt.Println(alist[0], alist[1]) // 输出: &{3} &{4}
    	alist.swap(0, 1)
    	fmt.Println(alist[0], alist[1]) // 输出: &{4} &{3}
    }
    

    swap 操作应该改变a,即 alist 的复制,而不应该改变 alist 中值的顺序。但事实就是alist的顺序也被改变了。

    请各位大神指出我哪里理解错了。

    第 1 条附言  ·  2019-11-24 23:22:28 +08:00

    感谢各位的指正,这里引用 @zhujinliang (8楼) 的回复解释一下。

    Slice 的结构体中有:指针 array,指向存放数组成员的内存区域;变量 len,表示一共有多少个成员;变量cap, 表示底下的数组中元素的数量,从Slice的第一个元素开始计算。

    所以当传递值的时候,这个结构体被复制了,但是在复制的Slice中,array 依然指向原数组。所以对复制的操作影响到了原结构体。

    Slice 结构体代码 ( 来源 ):

    type slice struct {
    	array unsafe.Pointer
    	len   int
    	cap   int
    }
    
    17 条回复    2019-11-29 11:33:17 +08:00
    octobersnow
        1
    octobersnow  
       2019-11-24 12:57:29 +08:00 via iPhone
    搞清楚拷贝的到底是什么。就跟 java 从来都没有引用传递一样
    EileenJ
        2
    EileenJ  
       2019-11-24 12:58:44 +08:00 via Android
    切片是引用类型
    Maboroshii
        3
    Maboroshii  
       2019-11-24 12:59:00 +08:00
    slice 就是引用,引用了底层数组
    MemoryCorner
        4
    MemoryCorner  
       2019-11-24 13:00:06 +08:00   ❤️ 1
    接收器是一个 []*A 切片,因此传的是引用
    kran
        5
    kran  
       2019-11-24 13:00:16 +08:00 via Android
    浅拷贝啦,拷贝的是 slice 结构体,底层引用的数组是同一个
    lhx2008
        6
    lhx2008  
       2019-11-24 13:02:35 +08:00 via Android
    指针类型,内容是指向内存的另外一个地方,值拷贝,只拷贝了这个指针的内容到一个新的指针,不会把指向的内容再拷贝。
    catror
        7
    catror  
       2019-11-24 13:04:53 +08:00 via Android
    接收器是 slice,你确实是拷贝了 slice 的值。但是 slice 内部存储的是指向数组的指针,操作指针自然会影响原有的值。
    zhujinliang
        8
    zhujinliang  
       2019-11-24 13:06:51 +08:00 via iPhone   ❤️ 1
    数组的结构体中有一个指针,指向存放数组成员的内存区域,再加一个变量,表示一共有多少个成员,( go 中还有个 cap 变量,这里不讨论)
    你的例子中传数组是传值,但不影响数组的成员,go 只会拷贝数组结构体(就是上面说的三个变量),但不拷贝数组中的成员(即指针指向的内存区域)

    类似的
    type A struct {
    a int
    b *B

    type B struct {
    c int


    如果通过传值传入 A 对象,不影响 A 结构体中引用的 B 结构体的内容,在接受 A 的函数中修改 b.c 一样会表现为传址

    指针跟 int 等类型无异,对其进行拷贝不会导致对被指向对象内容的拷贝
    TypeError
        9
    TypeError  
       2019-11-24 13:30:43 +08:00 via Android
    饮用类型,和其他语言是类似的,比如 Python list 传进函数里也是可以修改的
    TypeError
        10
    TypeError  
       2019-11-24 13:30:57 +08:00 via Android
    @TypeError 引用…
    hellowoody
        11
    hellowoody  
       2019-11-24 13:50:32 +08:00 via iPhone
    1.golang 里传参都是值传递,没有引用传递
    2.你这段代码最简单的改法是把声明 make()语句放在 main 函数中,也就是放在方法体外就可以了
    3.原理的话可以去网上搜一下,在这就不多做解释了
    AzadCypress
        12
    AzadCypress  
       2019-11-24 14:09:33 +08:00 via Android
    go 里面能用 make 创建的 3 种类型都是引用类型( chan slice 和 map )
    你传递的时候传递的是这个引用的复制
    hellowoody
        13
    hellowoody  
       2019-11-24 15:46:00 +08:00
    @hellowoody 纠正一下我的回复,刚才没太看清问题
    1.第一点没有问题,golang 里传参都是值传递,没有引用传递,所谓的引用传递也是地址的值传递
    2.这一点有问题,没看清题目,如果你想不改变数组的顺序,可以直接在 swap 方法内第一行加上 a = make(Alist, 2),顺序就不会改变了。注意,加上 a = make(Alist, 2)后输出的 2 行都是 3,4,而不是 3,4 和 nil,nil,这正好说明第一点 golang 里传参都是值传递,没有引用传递
    50infivedays
        14
    50infivedays  
       2019-11-24 16:13:11 +08:00
    熟悉 c 系语言的同学 会明白 这所谓的浅拷贝 而你期望的是深拷贝
    ihciah
        15
    ihciah  
       2019-11-24 18:08:19 +08:00 via iPhone
    写一波 rust 包你懂
    index90
        16
    index90  
       2019-11-25 12:22:51 +08:00
    命令式编程的原罪
    hijoker
        17
    hijoker  
       2019-11-29 11:33:17 +08:00
    切片,字典,channel 在 Go 里面就是引用
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2828 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:39 · PVG 22:39 · LAX 07:39 · JFK 10:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.