代码1.0
package main
import "fmt"
func main() {
a := make([]int64, 0, 0) // 改为 a := make([]int64, 0, 2) 时执行输出也都一样的
println(fmt.Sprintf("a: %v", a))
// 输出:a: []
solve(a)
println(fmt.Sprintf("a: %v", a))
// 输出:a: []
}
func solve(currA []int64) {
currA = append(currA, 1)
println(fmt.Sprintf("currA: %v", currA))
// 输出:currA: [1]
}
在调solve函数时,发生了引用的复制:currA = a,引用指的是这个东西:
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度 注意:append时len是占位的
cap int // 容量
}
也就是产生了slice struct的两个实例——currA和a,其中currA中各项(包含:array指针、len、cap三个字段)的值是从a的各项值复制来的,其中array指针相同则意味着currA和a指向的底层数组是一个,但是后续currA和a会各自维护自己的len和cap。
这也就是为什么【在子函数solve里append后不会影响父函数main里的切片,但是在子函数solve里用下标修改元素值后是会影响父函数main里的切片(下标修改的过程见下面代码)】的原因。
package main
import "fmt"
func main() {
a := make([]int64, 0, 0)
a = append(a, 1)
println(fmt.Sprintf("a: %v", a))
// 输出:a: []
solve(a)
println(fmt.Sprintf("a: %v", a))
// 输出:a: [2]
}
func solve(currA []int64) {
currA[0] = 2
println(fmt.Sprintf("currA: %v", currA))
// 输出:currA: [2]
}
代码2.0
package main
import "fmt"
func main() {
a := make([]int64, 0, 2) // 后续不需要扩容
println(fmt.Sprintf("a: %v", a))
// 输出:a: []
b := append(a, 1)
println(fmt.Sprintf("a: %v, b: %v", a, b))
// 输出:a: [], b: [1]
c := append(a, 2)
println(fmt.Sprintf("a: %v, b: %v, c: %v", a, b, c))
// 输出:a: [], b: [2], c: [2]
}
总结分析
append时
没有发生底层数组数据的直接复制;相反,append
函数返回了一个新的切片值,该值可能与原始切片共享相同的底层数组(在不需要扩容的情况下),但具有不同的长度和(可能)容量。
另外,append时会会根据原接片的长度去追加。例如原切片长度为1(下标=0),则append时会在下标为0+1的地方追加。
详细分析
当你执行 b := append(a, 1)
时,这里发生了几件事情:
-
如果
append
操作不需要扩展底层数组(即新元素的添加不会导致切片的容量不足),那么append
会直接在底层数组的末尾添加新元素,并返回一个新的切片值。这个新的切片值会包含原始切片的所有元素(在这个例子中是空的,因为没有元素),加上新追加的元素。重要的是,这个新的切片值会共享原始切片的底层数组(如果可能的话),但它会有自己的长度(现在至少为 1,因为我们添加了一个元素)。 -
在这个特定的例子中,由于
a
是空的,且其容量足以容纳至少一个额外的元素(因为我们设置了容量为 2),append(a, 1)
实际上是在a
的底层数组的末尾(尽管这个数组目前还是空的)添加了一个元素,并返回了一个新的切片值b
。这个新的切片值b
指向与a
相同的底层数组(在这个例子中这个“相同”的底层数组实际上还没有被使用来存储任何元素,但它已经为存储元素做好了准备),但它的长度是 1,因为它现在包含一个元素。 -
然而,重要的是要理解
a
和b
是两个不同的切片值。尽管它们可能(在这个例子中确实)共享相同的底层数组,但每个切片值都有自己独立的长度和容量。当你将b
赋值给一个新变量时,你并没有复制底层数组,而是创建了一个新的切片头(包含指向底层数组的指针、长度和容量),并将其赋值给该变量。 -
因此,当你说“包含了 a 的所有元素”时,你实际上是在说新切片
b
包含了原始切片a
在追加操作之前所拥有的所有元素(在这个例子中是空的),并且额外添加了一个新的元素。这并不意味着底层数组的数据被复制了;相反,它意味着新的切片值b
指向了与a
相同的底层数组(或一个新的、更大的数组,如果追加操作导致了切片的重新分配),但具有不同的长度。
代码2.1
package main
import "fmt"
func main() {
a := make([]int64, 0, 0) // 后续不需要扩容
println(fmt.Sprintf("a: %v", a))
// 输出:a: []
b := append(a, 1)
println(fmt.Sprintf("a: %v, b: %v", a, b))
// 输出:a: [], b: [1]
c := append(a, 2)
println(fmt.Sprintf("a: %v, b: %v, c: %v", a, b, c))
// 输出:a: [], b: [1], c: [2]
}
如果append时需要扩容,则将原数组的值复制到扩容后的数组。执行到【b := append(a, 1)】和【c := append(a, 2)】时会发生扩容,但扩容不会对a产生任何影响,因为没有【a := append(a, 1或2)】的操作。