提问人:qizong007 提问时间:3/17/2022 更新时间:3/19/2022 访问量:576
关于 Go 的地图和切片的设计问题让我很困扰
A design question about Go's map and slice troubled me a lot
问:
为什么 Go 的切片有“复制陷阱”,而 map 没有?
假设我们有一个函数,它以 slice 作为输入参数,如果 slice 在函数中展开,则只更改复制的切片结构,而不是原来的切片结构。原始切片仍指向原始数组,函数中的切片由于扩展而更改了数组指针。
func main() {
a := []int{1, 2}
fmt.Println(a) // [1 2]
doslice(a)
fmt.Println(a) // [1 2]
}
func doslice(a []int) {
a = append(a, 3, 4, 5, 6, 7, 8, 9)
a[0] = 200
fmt.Println(a) // [200 2 3 4 5 6 7 8 9]
}
看看输出,如果切片是通过“引用”传递的,它不会像 [1 2] 那样。
func main() {
mp := map[int]int{1:1,2:2}
domap(mp)
fmt.Println(mp) // map[1:1 2:2 3:3 4:4 ...], the same as below
}
func domap(mp map[int]int) {
for i := 3; i < 100; i++ {
mp[i] = i
}
fmt.Println(mp) // map[1:1 2:2 3:3 4:4 ...]
}
看看这个,我们在 domap() 中所做的操作奏效了! 是陷阱吗?! 我的意思是,map由'reference'(*hmap)传递,slice由'value'(SliceHeader)传递。 为什么地图和切片被设计为所有内部引用类型,为什么它如此不一致? 也许这只是一个设计问题,但为什么呢?为什么要这样切片和映射?
以下是我对证明为什么只能通过“引用”传递的猜测:map
Assume that map is passed by value -> hmap struct type
(1) After Init: (hmap outside the function)
hmap.buckets = bucketA
hmap.oldbuckets = nil
(2) After passing the param, entering the function: (hmap inside the function)
hmap.buckets = bucketA
hmap.oldbuckets = nil
(3) After triggering the expanding: (hmap inside the function)
hmap.buckets = bucketB
hmap.oldbuckets = bucketA
但不一样!slice
There is no incremental migration,
and there is no oldbuckets,
so you can use a structure because the function is isolated from the outside,
whereas map is not, and oldbuckets are referenced outside the function.
我的意思是,这个设计的最初目的可能是传递值,以防止函数中原始变量的直接修改,但映射不能通过值传递。一旦该值被传递到地图,原始地图数据将在函数扩展过程中丢失。
如果有人能帮我解决这个问题,我将不胜感激。多谢!
(我为我可怜的Chinglish感到难过,我希望我描述得很好......:(这真的让我很困扰......
答:
在golang-nuts邮件列表上也提出了这个问题,这是Brian Candler回复的一部分:
我想你已经很好地理解了这个问题。从源代码 https://golang.org/src/runtime/slice.go :
类型 slice struct { 阵列不安全。指针 len int cap int }
正如你所发现的,这是按值传递的。你当然可以通过一个 显式指向此类值的指针(如果选择)。
是陷阱吗?!
嗯,这是你必须学习的语言,但我认为 这是可用设计选项中的最佳选择。
字符串和切片彼此一致:它们是普通的 structs,包含指向数据、长度和(用于切片)的指针 能力。
是否可以已实现切片和字符串,以便它们的值为 总是指向结构的指针?我想是的,但我想你会的 最终仍然在更深层次上遇到类似的问题。复制时 一个切片 (b := a),或者将其作为函数参数传递,那么你就会 复制指针,这样同一切片将有两个别名,并且 对一个的修改对另一个是可见的。但是当你 sub-slice (b := a[1:2]),则您将被迫分配一个新切片 结构。因此,切片的行为将取决于 确切地说,它是如何生成的,因此突变一个切片可能会也可能不会影响其他切片。我认为总体上会更令人困惑。
仅供参考:are-slices-passed-by-value
Go 中的所有内容都是按值传递的,切片也是如此。但切片值是 一个标头,描述后备数组的连续部分,以及一个 slice 值仅包含指向数组的指针,其中元素 实际存储。切片值不包括其元素 (与数组不同)。
因此,当您将切片传递给函数时,将从中复制 标头,包括指针,它将指向相同的后背 数组。修改切片的元素意味着修改 后备数组的元素,以及共享相同元素的所有切片 后备数组将“观察”更改。
若要查看切片标头中的内容,请查看以下类型:
reflect.SliceHeader
type SliceHeader struct { Data uintptr Len int Cap int }
请参阅相关/可能的重复问题:Golang 函数是 参数作为写入时复制传递?
阅读博客文章:Go Slices:用法和内部结构
评论