for range遍历字符串得到的是rune而非byte;i为字节起始位置,r为Unicode码点;str[0]取首字节非首字符;len(str)返回字节数非字符数。
Go 的 string 底层是 UTF-8 编码的字节序列,但 for range 会自动按 Unicode 码点(即 rune)拆分,而不是按字节。这意味着中文、emoji 或带重音的字母(如 "é")会被正确识别为单个字符,而非多个 byte。
常见错误是误以为 for range 返回的是下标和 byte,结果对中文做 str[i] 操作时 panic 或读到乱码。
for i, r := range "你好" 中,i 是该 rune 在字节串中的起始位置(0、3),r 是 rune 类型的 Unicode 码点(如 '你' 对应 20320)str[0] 取的是第一个字节(UTF-8 编码首字节),不是第一个字符[]byte(str),但注意这会丢失 Unicode 语义len("你好") 返回的是字节数(6),不是字符数(2)。用 for i := 0; i 配合 str[i] 会破坏 UTF-8 编码,导致非法字节序列或截断字符。
正确做法始终是用 for range,它内部已处理 UTF-8 解码逻辑:
str := "Hello 世界 ?"
for i, r := range str {
fmt.Printf("pos %d: %c (U+%04X)\n", i, r, r)
}
输出中 i 是字节偏移(0、5、8、12),r 是完整字符。如果真需要字符序号(第几个 Unicode 字符),可手动计数:
idx := 0 变量,在每次 range 迭代时递增i 当字符序号——它只是字节位置Go 中 string 是只读底层数组的引用,任何“修改”都必须新建字符串。试图在 for range 中赋值 str[i] = 'x' 会编译报错:cannot assign to str[i]。
常见替代方案:
[]rune:适合需要逐字符编辑(如大小写转换、过滤 emoji),再用 string(runes) 转回strings.Builder:适合拼接、条件跳过、插入等场景,性能更好s += newPart),会触发多次内存分配例如把每个汉字转成 ASCII 描述:
runes := []rune("Go编程")
var b strings.Builder
for _, r := range runes {
if unicode.Is(unicode.Han, r) {
b.WriteString(fmt.Sprintf("[U+%04X]", r))
} else {
b.WriteRune(r)
}
}
result := b.String() // "Go[U+7F16][U+7A0B]"
两者底层开销不同: for r 需要 UTF-8 解码,
for i 直接访问字节。单纯遍历字节确实更快,但代价是无法正确处理多字节字符。
真实项目中,绝大多数字符串操作(显示、解析、校验)都需要语义正确的字符边界。此时 for range 是唯一安全选择;刻意绕过它去优化微秒级差异,往往引入 bug 的成本远高于收益。
只有极少数情况可考虑字节遍历:
utf8.ValidString(s) 排除无效 UTF-8否则,老老实实用 for range —— 它不是语法糖,是 Go 对 Unicode 支持的核心设计。