"value semantics’" 和 "pointer semantics" 在 Go 中是什么意思?

What do "value semantics’" and "pointer semantics" mean in Go?

Go 中的值语义指针语义是什么意思?在this course中,作者在解释数组和切片的内部结构时多次提到上述术语,我无法完全理解。

当您调用函数或方法并向其传递参数时,会从值中创建一个副本,而函数只能访问这些副本。

这意味着如果函数尝试修改/更改副本,它不会更改原始值。

例如:

func main() {
    i := 1
    fmt.Println("double:", double(i))
    fmt.Println("original i:", i)
}

func double(i int) int {
    i *= 2
    return i
}

此输出(在 Go Playground 上尝试):

double: 2
original i: 1

即使 double() 修改了它的 i 参数,调用方的变量(其值被传递)没有改变。

要改变它,我们需要改变签名以期待一个指针,传递一个指针,并修改指向的值:

func main() {
    i := 1
    fmt.Println("double:", doublep(&i))
    fmt.Println("original i:", i)
}

func doublep(i *int) int {
    *i *= 2
    return *i
}

此输出(在 Go Playground 上尝试):

double: 2
original i: 2

所以如果我们传递一些东西,我们期望原始值不会随着传递值的修改而改变,除非我们传递一个指向它的指针。

指针语义意味着即使我们传递了一些东西"by value",被调用者仍然可以修改"original"值,就好像我们已经传递了一样指向它的指针。

例如:

func main() {
    is := []int{1, 2}
    fmt.Println("double:", doubles(is))
    fmt.Println("original is:", is)
}

func doubles(is []int) []int {
    for i := range is {
        is[i] *= 2
    }
    return is
}

此输出(在 Go Playground 上尝试):

double: [2 4]
original is: [2 4]

即使我们没有传递指针(is不是指针),calle修改了它的元素,原始切片的值也改变了。

我们说虽然在Go中一切都是按值传递,但传递的切片具有指针语义,因为如果被调用者修改元素,它会反映在原来的。

推理

Go 中的一切都是按值传递的,切片也是。但是切片在本质上是类似于结构的数据结构,它包含指向包含实际元素的底层数组的指针。当你传递一个切片时,会生成一个副本,但只会复制这个切片头(这是切片值)。副本将持有相同的指针,指向相同的后备数组。不复制支持数组。所以当callee修改slice的元素时,backing array的元素也被修改了,和原来slice的backing array是一样的。

在此处阅读更多相关信息:

有很多类型具有传递指针语义,例如切片、映射、通道。

值得注意的是,与切片不同,数组不在行中,数组值表示其所有值,传递数组会复制其所有元素。