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