根据内置的api文档,当原始切片的容量不够大时,append()将重新分配并复制到新的数组块.
这是用于创建字母组合(在本例中为布尔值)的递归算法的(简化版本).将字母表的成员(true,false)递归地添加到切片中,直到它是正确的长度,此时它通过通道发送.
package main
import (
"fmt"
)
func AddOption(c chan []bool, combo []bool, length int) {
if length == 0 {
fmt.Println(combo, "!")
c <- combo
return
}
var newCombo []bool
for _, ch := range []bool{true, false} {
newCombo = append(combo, ch)
AddOption(c, newCombo, length-1)
}
}
func main() {
c := make(chan []bool)
go func(c chan []bool) {
defer close(c)
AddOption(c, []bool{}, 4)
}(c)
for combination := range c {
fmt.Println(combination)
}
}
Run Code Online (Sandbox Code Playgroud)
这是此代码的操场链接.在输出中:
[true true true true] !
[true true true false] !
[true true true false]
[true true true false]
[true true false true] !
[true true false false] !
[true true false false]
[true true false false]
[true false true true] !
[true false true false] !
[true false true false]
[true false true false]
[true false false true] !
[true false false false] !
[true false false false]
[true false false false]
[false true true true] !
[false true true false] !
[false true true false]
[false true true false]
[false true false true] !
[false true false false] !
[false true false false]
[false true false false]
[false false true true] !
[false false true false] !
[false false true false]
[false false true false]
[false false false true] !
[false false false false] !
[false false false false]
[false false false false]
Run Code Online (Sandbox Code Playgroud)
以感叹号结尾的行是从AddOption发送到通道的行.没有的是另一方出现的东西(即在main()中).很明显,通过频道发送的片段在发送后会发生变化.
由于AddOption在发送切片后立即返回,因此修改必须来自代码块
var newCombo []bool
for _, ch := range []bool{true, false} {
newCombo = append(combo, ch)
AddOption(c, newCombo, length-1)
}
Run Code Online (Sandbox Code Playgroud)
但是,根据文档,append()应该返回一个新的切片(cap(组合)不够大).根据这个答案,发送到AddOption的切片描述符应该是一个副本; 那不是真的吗?据我所知,作为AddOption()的第二个参数发送的值要么是指向切片描述符的指针,要么append()不返回新切片.
您将切片、数据类型与实际表示混淆了。切片描述符由一对 int 组成,一个用于 len,一个用于 cap,以及一个指向底层数据的指针。
因此, append 返回的确实是一个新切片,而传递给 add 选项的确实是切片描述符的副本。但是由于描述符有一个指向数据的指针,所以指针值(指向底层数据的地址)是相同的。
编辑:这是一个代码片段来说明我的观点:
package main
import "fmt"
func main() {
s := make([]int, 0, 5)
s = append(s, []int{1, 2, 3, 4}...)
a := append(s, 5)
fmt.Println(a)
b := append(s, 6)
fmt.Println(b)
fmt.Println(a)
}
Run Code Online (Sandbox Code Playgroud)
如果你运行这个,你会得到:
[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 6]
Run Code Online (Sandbox Code Playgroud)
因为既然s仍然有能力,都a和b共享相同的数据PTR。如果将容量更改为 4,则会打印:
[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 5]
Run Code Online (Sandbox Code Playgroud)
当append()创建一个新的片,它不会创建片,这只是一个比以前更大片.它实际上创建的切片已经是比前一个更大的元素.看看这段代码:
package main
import "fmt"
func main() {
var sl []bool
for i := 0; i < 100; i++ {
sl = append(sl, true)
fmt.Println(cap(sl))
}
}
Run Code Online (Sandbox Code Playgroud)
如果您运行此代码,您会看到容量最初会在每次分配时加倍; 对于较大的切片尺寸,这种策略当然会改变.