问题的历史: 在Go中,字符串(string)是基本且常用的类型,广泛用于数据交换、日志记录、解析等。Go的一个特点是字符串是不可变的,由不可变的字节序列组成,并可以包含UTF-8数据。
问题: 人们常常混淆字符串和字节切片([]byte)的操作,修改字符串时出错,切割字符时出错,以及在处理多字节字符(例如,西里尔字母、emoji)时出错。
解决方案:
字符串是不可变的,不能直接修改其元素——尝试修改s[0]是不允许的。字符串以UTF-8编码,也就是说,一个字符(rune)可能宽于一个字节。使用[]byte的操作成本更低,但需要手动控制。string <-> []byte之间的转换总是会创建副本。
代码示例:
s := "привет" fmt.Println(len(s)) // 12 字节(西里尔字母:每个2字节) fmt.Println(len([]rune(s))) // 6 个rune,字母数量 fmt.Println(string([]byte{228, 189, 160, 229, 165, 189})) // 中文汉字
主要特点:
1. 可以通过索引(例如,s[1] = 'a')更改字符串中的单个字符吗?
答案:不可以。字符串是不可变的,编译器会报错。需要创建一个新的切片[]rune或[]byte,修改后再转换回字符串。
2. 为什么len(str)不总是等于字符串中的字符数量?
答案:len(str)是字节数,而不是rune(字符)数。对于西里尔字母或emoji,较长的字符串可能会返回直观上意外的值。要获取字符数,请使用[]rune:
s := "мир 😀" fmt.Println(len(s)) // 7 fmt.Println(len([]rune(s))) // 5
3. 字符串是通过引用还是值传递给函数的?
答案:按值传递,但实际在内部存储的是指向内存的指针和长度。传递后,两个变量"指向"同一文本,副本不会自动创建。实际的内存副本在转换为[]byte或从[]byte转换为string时出现。
开发人员有一个包含俄文字符的字符串,取前4个字节,期望得到第一个字母,但结果只得到字符的一半——出现了“损坏”的字符。
优点: 实现快速且简短。 缺点: 对Unicode数据处理不当,出现“损坏”的字符串,在其他地方尝试解析这样的字符串时会引发崩溃。
字符串被转换为[]rune以处理字符,必要操作后再通过string()将其组装回来。只有在进行底层序列化时,才使用[]byte,并考虑编码。
优点: 正确处理Unicode,功能可靠。 缺点: 稍慢,占用更多内存,但对任何字符串都是安全的。