Golang中的Unicode和range使用详解
Golang中的Unicode和range使用详解 我正在尝试理解 Go 语言中的 Unicode 处理。以下代码包含一个单字符的字符串。将该字符串转换为字节切片并打印,显示该字符使用两个字节进行编码。我遍历该字符串并打印一些信息。
package main
import (
"fmt"
)
func main() {
s := "Ã"
b := []byte(s)
fmt.Println(b)
for _, r := range s {
fmt.Printf("%d=%c %T", r, r, r)
}
fmt.Println()
}
打印结果如下:
[195 131]
195=Ã int32
使用 %c 打印显示了正确的字符。
然而,使用 %d 只显示了第一个字节 (195)。那么 131 去哪了?它不应该也包含在 r 中吗?r 的类型是 int32。我认为 rune 是 int32 类型的意义就在于它可以包含构成字符的所有字节。
而且,如果 r 只包含 195,为什么 %c 知道如何打印正确的字符?
更多关于Golang中的Unicode和range使用详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html
啊,明白了。所以 rune 是码点,而不是 UTF-8 编码。这样就说得通了。谢谢大家!
更多关于Golang中的Unicode和range使用详解的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
MikeSolem:
理解 Go 中的 Unicode 处理
使用 Ã 这个字符来解释正在发生的事情是不太合适的。如果你使用任何其他字符,会更容易看清情况,例如 ã:Go Playground - The Go Programming Language,它会产生以下输出:
[195 163]
227
227=ã int32
实际发生的情况是第一个字节:195 是第 227 个 Unicode 码点的 2 字节编码的第一个字节(参见 维基百科上的 UTF-8 以了解其比特位的工作原理)。
碰巧的是,Ã 是第 195 个码点,所以看起来你只得到了这个 2 字节 UTF-8 编码的 rune 的第一个字节!
字符串的UTF-8表示中的字节与rune的字节并不相同。 rune是一个4字节的实体(int32的别名)。 for/range循环返回的每个rune对应字符串的1、2、3或4个字节,具体取决于每个字节的特定位的值。
字符Ã的Unicode码点是0xc3(十进制195)。 根据维基百科文章,它被映射为2字节的UTF-8表示,如下所示: 第一个UTF-8字节的高位是110(=192),低5位是码点的前5位(0b11)。 第二个UTF-8字节的高位是10(=128),低6位是码点的最后6位(0b11)。 最终得到的双字节UTF-8字符串是0xc383:十进制字节195(=192+3)和131(=128+3)。
这是一个关于Go语言中Unicode处理和range循环工作原理的常见误解。让我详细解释一下:
核心问题分析
你的代码中字符串 "Ã" 实际上是一个Unicode字符,其UTF-8编码为两个字节:[195 131]。当使用range遍历字符串时,每次迭代返回的是一个完整的Unicode码点(rune),而不是单个字节。
关键点解释
r包含的是完整的Unicode码点,不是单个字节%d打印的是码点的十进制值,不是字节值%c根据码点值显示对应的字符
正确的示例代码
package main
import (
"fmt"
)
func main() {
s := "Ã"
// 方法1:查看字节表示
b := []byte(s)
fmt.Printf("字节切片: %v\n", b) // [195 131]
fmt.Printf("字节十六进制: % x\n", s) // c3 83
// 方法2:查看Unicode码点
for i, r := range s {
fmt.Printf("位置 %d: 码点值(十进制)=%d, 码点值(十六进制)=%U, 字符=%c\n",
i, r, r, r)
}
// 方法3:验证码点值
runeValue := 'Ã'
fmt.Printf("\n字符'Ã'的码点值: %d (十进制), %U (Unicode格式)\n",
runeValue, runeValue)
// 方法4:手动解码UTF-8
fmt.Printf("\n手动验证UTF-8编码:\n")
fmt.Printf("字节[195 131]对应的Unicode码点是: U+00C3\n")
fmt.Printf("U+00C3的十进制值是: %d\n", 0xC3)
}
输出解释
对于字符 "Ã":
- UTF-8编码:
[195 131](两个字节) - Unicode码点:U+00C3
- 十进制值:195
所以当range遍历时:
r的值是195(完整的Unicode码点)%d显示195%c将码点195转换为字符"Ã"
验证其他字符
package main
import (
"fmt"
)
func main() {
// 测试多字节字符
testCases := []string{
"A", // ASCII - 1字节
"Ã", // Latin-1 - 2字节
"中", // 中文 - 3字节
"😀", // Emoji - 4字节
}
for _, s := range testCases {
fmt.Printf("\n字符: %s\n", s)
fmt.Printf("字节: %v\n", []byte(s))
for i, r := range s {
fmt.Printf(" 码点: U+%04X, 十进制: %d, 字符: %c\n",
r, r, r)
}
}
}
重要结论
rangeover string:每次迭代返回一个完整的rune(Unicode码点)- UTF-8解码:
range会自动处理UTF-8解码,将多个字节组合成单个码点 %dvs%c:%d显示码点的数值,%c显示该码点对应的字符- 字节 vs 码点:字节切片
[195 131]是UTF-8编码,码点195是Unicode值
在你的例子中,r的值195是正确的Unicode码点(U+00C3),%c使用这个码点在Unicode表中查找并显示对应的字符"Ã"。

