Go 中值语义和指针语义的含义是什么?在本课程中,作者在解释数组和切片的内部结构时曾经多次提到上述术语,但我无法完全理解。
当您调用函数或方法并将参数传递给它时,将从值创建副本,并且函数只能访问这些副本。
这意味着如果函数尝试修改/更改副本,它不会更改原始值。
例如:
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
因此,如果我们传递某些内容,我们希望原始值在传递的值被修改时不会更改,除非我们传递指向它的指针。
指针语义意味着即使我们"按值"传递某些内容,被调用方仍然可以修改"原始"值,就像我们传递指向它的指针一样。
例如:
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 中的所有内容都是按值传递的,切片也是如此。但是切片是类似结构的数据结构,它们包含指向保存实际元素的基础数组的指针。当您传递切片时,会创建一个副本,但只会复制此切片标题(这是切片值)。副本将保存相同的指针,指向相同的后备数组。不复制后备阵列。因此,当被调用方修改切片的元素时,后备数组的元素也会被修改,这与原始切片的后备数组相同。
在这里阅读更多关于它的信息: 戈朗切片是按值传递的吗?
有许多类型具有按指针语义传递,例如切片、映射、通道。
值得注意的是,与切片不同,数组不在行中,数组值表示其所有值,传递数组会复制其所有元素。
与公认的答案相反,现实是切片也具有价值语义。尝试
func main() {
is := []int{1, 2}
fmt.Println("double:", len(doubles(is)))
fmt.Println("original is:", len(is))
}
func doubles(is []int) []int {
is = append(is, 2)
return is
}
问题是切片是一个长度和一个数组指针。值语义意味着接收该指针的浅拷贝。因此,元素是共享的,但长度和指针不是。如果在 double 中进行了足够的修改以调整内部数组的大小,则元素将不再共享。