在Go中,map和slice结构在复制和内存工作语义上有着重要的特征,这常常导致经验不足的开发者出现意外的行为。
尽管Go被认为是一种严格的静态类型语言,并且默认不使用指针,但map和slice具有特殊的内部模型:这两种类型都是引用结构。这给复制和传递这些对象时施加了限制并产生了许多细微之处。
复制map和slice并不会导致内容的深度复制,而是形成一个指向同一对象的新引用,这会在数据修改、从函数中错误返回值和修改时导致意外的副作用。此外,将map或slice作为函数的返回值可能会引发额外的分配或泄漏。
b := a[:])获取新切片,则新切片指向相同的内存位置。对于元素的完整复制,需要使用内置函数copy()。正确的复制示例:
// 复制切片 a := []int{1, 2, 3} b := make([]int, len(a)) copy(b, a) // b现在独立于a // 复制map src := map[string]int{"x": 1} dst := make(map[string]int) for k, v := range src { dst[k] = v }
关键特性:
slice和map是引用类型,按描述符复制,而不是内容如果只是将一个map/slice赋值给另一个,然后修改其中一个,会发生什么?
map和slice都会指向内存中相同的数据:修改将影响两个对象。
为什么从函数返回slice或map时常常说“这在内存方面是高效的”?
因为返回的是描述符的副本,而不是整个内容,堆中的数据在有引用时会继续存在。
能否通过copy()函数实现map的“深度”复制?
不可以,copy()仅对切片和数组有效,对于map总是需要循环。
开发人员通过赋值复制slice或map,并更改副本以防止副作用:
优点:
缺点:
在修改必要数据之前,使用copy()处理slice,使用循环处理map:
优点:
缺点: