例如,在Kubernetes的api-machinery的实现中,我们可以看到带有以下签名的函数:
func Convert_Slice_string_To_string(input *[]string, out *string, s conversion.Scope) error
以及,在优先队列的示例中,我们可以再次找到类似的内容:
func (pq *PriorityQueue) Pop() interface{}
难道slice切片本来就不是一个指向底层数据的指针?
我们来利用Go-Playground进行一些调查,以测试所报告代码的行为。
在整个说明中,我将使用相同的示例:一个函数,该函数初始化切片,将其作为参数传递给第二个函数,
对其进行修改,然后打印该切片以验证内容。
通常认为切片是通过引用传递的,实际上,即使切片被初始化为[a,a],以下示例也将打印[b,b]和[b,b],因为在执行过程中对其进行了修改 字面量函数的变化,并且变化对main函数可见。
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice[0]="b"
slice[1]="b"
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
传递切片的指针会得到相同的结果,实际上,以下代码
func main() {
slice:= []string{"a","a"}
func(slice *[]string){
(*slice)[0]="b"
(*slice)[1]="b"
fmt.Print(*slice)
}(&slice)
fmt.Print(slice)
}
再次打印[b,b]和[b,b]。(看起来是一样的) 因此,通过指针传递它看起来毫无用处,并且无论如何,切片还是通过引用传递,并且在这两种情况下都修改了切片的数据。
但是为什么那些函数会有那样子的签名呢?(通过切片的指针传递)
说明
你可以把切片大致想象为这样的结构
type sliceHeader struct {
Length int
Capacity int
ZerothElement *byte //下标为0的元素指针
}
通过值将切片传递给函数,所有字段都会被复制一份,只有数据能够通过切片的指针副本被修改和外部获取。
但是,请记住,如果指针被覆盖或修改(复制,赋值或追加),则在函数外部将看不到任何更改,此外,初始的长度或容量也不会更改。
那么,问题的答案很简单,但是隐藏在slice本身的实现中:
当函数要修改切片的结构,大小或在内存中的位置时,指向切片的指针是必不可少的,并且每次更改都应对调用
该函数的用户可见。
当我们将切片作为参数传递给函数时,切片的值通过引用传递(因为我们传递了指针的副本),但是描述切片本身的所有元数据只是副本。
我们可以在函数中修改切片的数据,但是,如果由于某种原因导致指向数据的指针发生更改,或者更改了切片元数据,则外部函数可能看不到该更改,或者根本看不到该更改。
例如,如果在函数内重新分配切片,则使用新的内存地址,即使值相同,切片也会指向新内存地址,因此,值的任何修改都不会被看到,因为切片指向两个不同的位置(切片副本中的指针被覆盖)。
因此,在同一示例中,强制再次分配分片,
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice= append(slice, "a")
slice[0]="b"
slice[1]="b"
fmt.Print(slice) // [b b a]
}(slice)
fmt.Print(slice) // [a a]
}
将打印[b,b,a]和[a,a]。
将slice= append(slice,"a") 移动到下面两行之后,我们可以注意到结果是不一样的,因为切片在操作值之后重新分配地址,并且指针仍指向初始内存地址。 您可以使用以下代码进行检查:
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice[0]="b"
slice[1]="b"
slice= append(slice, "a") // 这一行原来在slice[0]="b"上面
fmt.Print(slice) // [b,b,a]
}(slice)
fmt.Print(slice) //[b,b]
}
这里会打印[b,b,a]和[b,b],因为没有再次分配数组,并且指针保持不变。神奇吧,这里又改动了
此行为可能导致发现棘手的错误,因为结果取决于初始数组的大小,例如,以下代码
func main() {
slice:= make([]string, 2, 3)
func(slice []string){
slice= append(slice, "a")
slice[0]="b"
slice[1]="b"
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
打印[b,b,a]和[b,b],因为没有再次分配数组,并且指针保持不变。
但是,在字符串中添加更多slice = append(slice,"a","a")时,将再次分配该数组,结果将是[b,b,a,a]和[](一个空数组,因为它不是初始化)。
func main() {
slice:= make([]string, 2, 3)
func(slice []string){
slice= append(slice, "a","a")
slice[0]="b"
slice[1]="b"
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
在数百行或数千行中间发现这种错误可能非常困难。
因此,请记住,如果您只想修改元素的值而不是数字的数量或位置,则可以按值传递切片,否则会不时出现怪异
的错误。
现在您已经准备好理解以下代码片段的结果,在 Golang playground中查看答案或在注释中写下它:
func main() {
slice:= make([]string, 1, 3)
func(slice []string){
slice=slice[1:3]
slice[0]="b"
slice[1]="b"
fmt.Print(len(slice))
fmt.Print(slice)
}(slice)
fmt.Print(len(slice))
fmt.Print(slice)
}