Go中的不可变字符串

13

有人能解释一下为什么在 changeMe() 函数中改变 &c1.name 的地址后,它的地址还是保持不变吗?我认为在 Go 语言中,字符串是不可变的。

package main

import "fmt"

type customer struct {
    name string
    age  int
}

func main() {
    c1 := customer{"Todd", 44}
    fmt.Println(&c1.name) // 0x8201e4120

    changeMe(&c1)

    fmt.Println(c1)       // {Rocky 44}
    fmt.Println(&c1.name) // 0x8201e4120
}

func changeMe(z *customer) {
    fmt.Println(z)       // &{Todd 44}
    fmt.Println(&z.name) // 0x8201e4120
    z.name = "Rocky"
    fmt.Println(z)       // &{Rocky 44}
    fmt.Println(&z.name) // 0x8201e4120
}

因为c1是一个唯一的实例,它的地址不会改变,所以你只能改变它的值。 - simon_xia
是的,c1 没有被改变,但它的字段 name 是不可变的,对吧?所以当我改变 name 时,它应该有新的地址。如果我想存储非常大的字符串,编译器会如何存储它,当它仍然使用相同的地址? - camabeh
1个回答

17

字符串的不可变性与变量的不可变性是不同的。

字符串的不可变性意味着字符串中的字符不能被改变。这在 Go 语言中得到了体现,例如下面的示例演示了如何切片字符串。

Go 中的变量始终是可变的。当一个字符串变量被更改时,该变量的内部字段(指针和长度)会被更改,但变量的地址永远不会更改。

下面的示例展示了 Go 字符串变量的内部情况。第一个整数是字符数组的地址,第二个整数是长度。

请参阅有关 Go 中字符串内部工作原理的文章:http://research.swtch.com/godata.

package main

import (
        "fmt"
        "reflect"
        "unsafe"
)

func main() {
    var x string = "abc"
    fmt.Println(x, &x, (*reflect.StringHeader)(unsafe.Pointer(&x)))
    x = "cde"
    fmt.Println(x, &x, (*reflect.StringHeader)(unsafe.Pointer(&x)))
    x = x[1:]
    fmt.Println(x, &x, (*reflect.StringHeader)(unsafe.Pointer(&x)))
}

Playground


2
在反射包中还有StringHeader,它反映了runtime/string.go中实际的字符串结构布局。 - JimB
谢谢。现在一切都清晰了。 - camabeh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接