不可变字符串和指针地址

7
在Go语言中,规范中写到:

字符串是不可变的:一旦创建,就无法更改字符串的内容。

我有以下代码:
str := "hello"
fmt.Printf("%p\n",&str) // 0x1040c128
fmt.Printf("%v\n",str) // hello
ptr := &str
*ptr = "world"
fmt.Printf("%p\n",&str) // 0x1040c128
fmt.Printf("%v\n",str) // world  

我本以为在执行 *ptr = "world"&str 地址会发生变化,就像在Java中重新分配字符串引用一样。

这里的“不可变性”是指什么?

1个回答

16

string 是不可变的。

str 不是一个 string 值。它是一个 变量(类型为 string)。而变量的值可以被改变,这是任何编程语言都应该具备的特性。

"hello" 是一个 string 值,而这个值是不可变的。 "world" 是另一个 string 值,当你将 "world" 赋值给 str 时,你只是将另一个不同的值赋给了 str 变量。无论你是直接对 str 进行赋值还是通过指针进行赋值,你都只是在改变由 str 指定的变量的值。

不可变意味着你不能取出 string"world" 并更改它的第二个字符,例如。如果你有一个函数,它接收一个 string 参数,那么无论它接收到什么(比如 "hello"),你都可以确定它始终保持不变。无论何时/如何打印此 string 值,它都将始终是 "hello"

string 值在底层是一个结构值,由 reflect.StringHeader 类型表示:

type StringHeader struct {
    Data uintptr
    Len  int
}

它基本上存储了一个数据指针(指向保存文本的UTF-8编码的字节数组),以及字符串值的字节长度。数据数组及其长度不会暴露给您,因此您无法修改它们。这是确保字符串值是不可变的一部分。另一个元素是,虽然字符串值可以被索引(索引其字节),但不能为索引表达式分配新值。例如,使用值"abc"[0]是有效的,但像"abc"[0] = 'x'这样为其赋新值是无效的。同样,您也不能获取索引表达式的地址来索引字符串值(否则您可以修改指向的值,从而间接地修改字符串值)。
这就是language spec所保证的。请注意,仍有某些方法可以改变字符串值,例如使用unsafe包,但这超出了规范的保证范围:

Unsafe 包含越过 Go 程序类型安全检查的操作。

导入 unsafe 的包可能是非便携的,并且不受 Go 1 兼容性指南的保护。

当你导入unsafe包时,你将失去语言规范提供的任何保证和安全性,并且从那时起你不能抱怨任何事情。但是,如果不使用这些“特殊”手段,则不可能发生string值被更改的情况。
阅读博客文章Go中的字符串、字节、符文和字符,了解在Go中如何实现和工作string
参见相关问题: Go中的字符串和[]byte有什么区别? 在go中使用不安全的[]byte转换为string的可能后果是什么?

3
为了扩展,"string"变量基本上是一个修改过的切片头部。字符串头本身位于内存中的某个位置(在你的例子中为0x1040c128),该位置保存两个值,即指向底层数组的指针和字符串的大小(与切片不同,字符串没有"容量"值,因为它们是不可变的,其容量始终等于其长度)。当你给一个字符串变量赋值时,实际上是改变了存储在该头部中的指针的值。头部本身的地址不会改变。 - Kaedys
2
我还要补充,对于任何 Go 语言初学者,这篇文章这篇文章(按照这个顺序)都是必读的。;-) - kostix

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