Go 中的“值语义”和“指针语义”是什么意思?

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

提问人:Sathishkumar Rakkiyasamy 提问时间:7/10/2018 最后编辑:iczaSathishkumar Rakkiyasamy 更新时间:3/1/2023 访问量:2496

问:

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

Go 指针 数据结构 按值传递

评论


答:

18赞 icza 7/10/2018 #1

当您调用函数或方法并将参数传递给它时,会从这些值创建一个副本,并且该函数只能访问这些副本。

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

例如:

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]

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

我们说,尽管在 Go 中一切都是按值传递的,但传递切片具有指针语义,因为如果被调用方修改了元素,它将反映在原始元素中。

推理

Go 中的所有内容都是按值传递的,切片也是如此。但是,在引擎盖下,切片是类似结构的数据结构,它包含指向包含实际元素的基础数组的指针。当您传递一个切片时,会创建一个副本,但只会复制这个切片标头(这是切片值)。副本将包含指向同一后备数组的相同指针。不会复制后备阵列。所以当被调用方修改切片的元素时,会修改后备数组的元素,这与原始切片的后备数组相同。

在这里阅读更多关于它的信息: golang 切片是按价值传递的吗?

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

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

0赞 Timm Felden 3/1/2023 #2

与公认的答案相反,现实情况是切片也具有价值语义。尝试

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 中执行足够的修改以调整内部数组的大小,则元素将不再共享。