如何在Golang中使用for range遍历字符串_Golang字符遍历方法

for range遍历字符串得到的是rune而非byte;i为字节起始位置,r为Unicode码点;str[0]取首字节非首字符;len(str)返回字节数非字符数。

for range 遍历字符串得到的是 rune,不是 byte

Go 的 string 底层是 UTF-8 编码的字节序列,但 for range 会自动按 Unicode 码点(即 rune)拆分,而不是按字节。这意味着中文、emoji 或带重音的字母(如 "é")会被正确识别为单个字符,而非多个 byte

常见错误是误以为 for range 返回的是下标和 byte,结果对中文做 str[i] 操作时 panic 或读到乱码。

  • for i, r := range "你好" 中,i 是该 rune 在字节串中的起始位置(0、3),rrune 类型的 Unicode 码点(如 '你' 对应 20320
  • 直接用 str[0] 取的是第一个字节(UTF-8 编码首字节),不是第一个字符
  • 若需字节索引或操作原始字节,应先转 []byte(str),但注意这会丢失 Unicode 语义

想获取字符位置和长度?别依赖 len(str) 做循环边界

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 当字符序号——它只是字节位置

遍历时修改字符串内容?不行,st

ring 是不可变的

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 range 比 for i

两者底层开销不同:for range 需要 UTF-8 解码,for i 直接访问字节。单纯遍历字节确实更快,但代价是无法正确处理多字节字符。

真实项目中,绝大多数字符串操作(显示、解析、校验)都需要语义正确的字符边界。此时 for range 是唯一安全选择;刻意绕过它去优化微秒级差异,往往引入 bug 的成本远高于收益。

只有极少数情况可考虑字节遍历:

  • 确定字符串只含 ASCII(如 HTTP header 名、base64 数据)
  • 做底层协议解析(如自定义二进制协议封装在 string 中)
  • 且已通过 utf8.ValidString(s) 排除无效 UTF-8

否则,老老实实用 for range —— 它不是语法糖,是 Go 对 Unicode 支持的核心设计。