根据 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
的顺序也被改变了。
请各位大神指出我哪里理解错了。
感谢各位的指正,这里引用 @zhujinliang (8楼) 的回复解释一下。
Slice
的结构体中有:指针 array
,指向存放数组成员的内存区域;变量 len
,表示一共有多少个成员;变量cap
, 表示底下的数组中元素的数量,从Slice
的第一个元素开始计算。
所以当传递值的时候,这个结构体被复制了,但是在复制的Slice
中,array
依然指向原数组。所以对复制的操作影响到了原结构体。
Slice
结构体代码 ( 来源 ):
type slice struct {
array unsafe.Pointer
len int
cap int
}
1
octobersnow 2019-11-24 12:57:29 +08:00 via iPhone
搞清楚拷贝的到底是什么。就跟 java 从来都没有引用传递一样
|
2
EileenJ 2019-11-24 12:58:44 +08:00 via Android
切片是引用类型
|
3
Maboroshii 2019-11-24 12:59:00 +08:00
slice 就是引用,引用了底层数组
|
4
MemoryCorner 2019-11-24 13:00:06 +08:00 1
接收器是一个 []*A 切片,因此传的是引用
|
5
kran 2019-11-24 13:00:16 +08:00 via Android
浅拷贝啦,拷贝的是 slice 结构体,底层引用的数组是同一个
|
6
lhx2008 2019-11-24 13:02:35 +08:00 via Android
指针类型,内容是指向内存的另外一个地方,值拷贝,只拷贝了这个指针的内容到一个新的指针,不会把指向的内容再拷贝。
|
7
catror 2019-11-24 13:04:53 +08:00 via Android
接收器是 slice,你确实是拷贝了 slice 的值。但是 slice 内部存储的是指向数组的指针,操作指针自然会影响原有的值。
|
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 等类型无异,对其进行拷贝不会导致对被指向对象内容的拷贝 |
9
TypeError 2019-11-24 13:30:43 +08:00 via Android
饮用类型,和其他语言是类似的,比如 Python list 传进函数里也是可以修改的
|
11
hellowoody 2019-11-24 13:50:32 +08:00 via iPhone
1.golang 里传参都是值传递,没有引用传递
2.你这段代码最简单的改法是把声明 make()语句放在 main 函数中,也就是放在方法体外就可以了 3.原理的话可以去网上搜一下,在这就不多做解释了 |
12
AzadCypress 2019-11-24 14:09:33 +08:00 via Android
go 里面能用 make 创建的 3 种类型都是引用类型( chan slice 和 map )
你传递的时候传递的是这个引用的复制 |
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 里传参都是值传递,没有引用传递 |
14
50infivedays 2019-11-24 16:13:11 +08:00
熟悉 c 系语言的同学 会明白 这所谓的浅拷贝 而你期望的是深拷贝
|
15
ihciah 2019-11-24 18:08:19 +08:00 via iPhone
写一波 rust 包你懂
|
16
index90 2019-11-25 12:22:51 +08:00
命令式编程的原罪
|
17
hijoker 2019-11-29 11:33:17 +08:00
切片,字典,channel 在 Go 里面就是引用
|