返回

揭秘 Go 切片指针与值传递:为何函数内操作会影响外部切片?

见解分享

切片值传递与引用传递

在 Go 语言中,函数参数默认是值传递,这意味着函数内的参数副本与函数外的原始变量是分开的。但是,切片是一种引用类型,它存储的是底层数组的地址。因此,当函数的参数是切片时,实际上是将切片指向的底层数组的地址传递给了函数。这意味着,函数内的任何修改都会反映到函数外部的原始切片中。

以下是一个简单的例子来说明这一点:

package main

import "fmt"

func main() {
    // 定义一个切片
    slice := []int{1, 2, 3}

    // 将切片传递给函数
    modifySlice(slice)

    // 打印切片
    fmt.Println(slice)
}

func modifySlice(slice []int) {
    // 对切片进行排序
    sort.Ints(slice)
}

在上面的例子中,modifySlice 函数的参数是一个切片。当函数被调用时,切片指向的底层数组的地址被传递给了函数。函数内的 sort.Ints(slice) 语句对切片进行排序,这将修改切片指向的底层数组。当函数返回时,对切片的修改会反映到函数外部的原始切片中。因此,在主函数中打印切片时,将会输出排序后的切片。

向切片添加元素

当在函数内向切片添加新元素时,这些新元素不会添加到函数外部的原始切片中。这是因为原始切片的容量是固定的,无法动态扩展。因此,当在函数内向切片添加新元素时,实际上是创建了一个新的切片,指向一个更大的底层数组。这个新切片与函数外部的原始切片是分开的,因此,函数内的修改不会反映到函数外部的原始切片中。

以下是一个例子来说明这一点:

package main

import "fmt"

func main() {
    // 定义一个切片
    slice := []int{1, 2, 3}

    // 将切片传递给函数
    add元素(slice)

    // 打印切片
    fmt.Println(slice)
}

func add元素(slice []int) {
    // 向切片添加一个元素
    slice = append(slice, 4)
}

在上面的例子中,add元素 函数的参数是一个切片。当函数被调用时,切片指向的底层数组的地址被传递给了函数。函数内的 slice = append(slice, 4) 语句向切片添加了一个元素。但是,由于原始切片的容量是固定的,因此实际上是创建了一个新的切片,指向一个更大的底层数组。这个新切片与函数外部的原始切片是分开的,因此,在主函数中打印切片时,将会输出原始切片,不会包含函数内添加的新元素。

排序切片

当对函数内的切片进行排序时,函数外部的原始切片中的元素顺序也会发生改变,但元素数量保持不变。这是因为排序操作只是改变了切片中元素的顺序,而不会改变切片的容量。因此,当函数返回时,对切片的修改会反映到函数外部的原始切片中,但原始切片的元素数量保持不变。

以下是一个例子来说明这一点:

package main

import (
    "fmt"
    "sort"
)

func main() {
    // 定义一个切片
    slice := []int{3, 1, 2}

    // 将切片传递给函数
    sortSlice(slice)

    // 打印切片
    fmt.Println(slice)
}

func sortSlice(slice []int) {
    // 对切片进行排序
    sort.Ints(slice)
}

在上面的例子中,sortSlice 函数的参数是一个切片。当函数被调用时,切片指向的底层数组的地址被传递给了函数。函数内的 sort.Ints(slice) 语句对切片进行排序,这将改变切片指向的底层数组中元素的顺序。当函数返回时,对切片的修改会反映到函数外部的原始切片中。因此,在主函数中打印切片时,将会输出排序后的切片。

总结

通过上面的三个小测验,我们可以看到,Go 切片在函数内的操作可能会影响函数外部的原始切片,也可能不会。这取决于函数内对切片的具体操作。如果函数内对切片进行了排序,那么函数外部的原始切片中的元素顺序也会发生改变。如果函数内向切片添加了新元素,那么函数外部的原始切片不会包含这些新元素。为了避免在函数内对切片进行修改而影响函数外部的原始切片,我们可以使用切片的拷贝来进行操作。