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

2019-11-24 12:47:54 +08:00
 vcfghtyjc

根据 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的顺序也被改变了。

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

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

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

type B struct {
c int


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

指针跟 int 等类型无异,对其进行拷贝不会导致对被指向对象内容的拷贝
TypeError
2019-11-24 13:30:43 +08:00
饮用类型,和其他语言是类似的,比如 Python list 传进函数里也是可以修改的
TypeError
2019-11-24 13:30:57 +08:00
@TypeError 引用…
hellowoody
2019-11-24 13:50:32 +08:00
1.golang 里传参都是值传递,没有引用传递
2.你这段代码最简单的改法是把声明 make()语句放在 main 函数中,也就是放在方法体外就可以了
3.原理的话可以去网上搜一下,在这就不多做解释了
AzadCypress
2019-11-24 14:09:33 +08:00
go 里面能用 make 创建的 3 种类型都是引用类型( chan slice 和 map )
你传递的时候传递的是这个引用的复制
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
2019-11-24 16:13:11 +08:00
熟悉 c 系语言的同学 会明白 这所谓的浅拷贝 而你期望的是深拷贝
ihciah
2019-11-24 18:08:19 +08:00
写一波 rust 包你懂
index90
2019-11-25 12:22:51 +08:00
命令式编程的原罪
hijoker
2019-11-29 11:33:17 +08:00
切片,字典,channel 在 Go 里面就是引用

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/622587

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX