for range 是唯一推荐的字符级遍历方式,i 为字节偏移而非字符序号;len(str) 返回字节数;strings.map 适用于无状态单字符变换。

for range 是唯一推荐的字符级遍历方式,其他方法要么乱码、要么语义错误、要么徒增开销。
用 for range 遍历字符串时,i 是字节偏移,不是字符序号
例如 "你好" 中,for i, r := range str 第一次迭代 i == 0(“你”的起始字节位置),第二次 i == 3(“好”的起始字节位置)。如果你误把 i 当作“第几个字符”,后续逻辑会全错。
常见错误现象:
- 用
i做条件跳过前 N 个字符,结果跳过了 1 个汉字 + 1 个字节 - 想取“第 2 个字符”却写
str[1],实际拿到的是“你”的第二个 UTF-8 字节,打印为
正确做法:需要字符序号就自己计数:for _, r := range str { idx++; if idx == 2 { ... } }
len(str) 返回字节数,不是字符数
这是和几乎所有其他语言最根本的差异点。写 for i := 0; i 看似自然,但遇到中文就崩——因为 <code>len("北京") == 6,而它只有 2 个字符。
容易踩的坑:
- 循环体里直接用
str[i]取值,对多字节字符必然截断 - 用
len(str)判断是否为空字符串?没问题;但用来做“字符长度比较”就错
获取真实字符数请用:utf8.RuneCountInString(str) 或 len([]rune(str))(后者会分配内存)
什么时候必须转成 []rune?
只在两种场景下才需要显式转换:
- 需要随机访问第 N 个字符,比如实现光标移动、substring 提取、反转字符串
- 需要修改字符顺序或内容,比如过滤重复字符、大小写批量转换
性能影响很实在:[]rune(str) 是 O(n) 操作,且每个 rune 占 4 字节,原字符串若为 1MB UTF-8 文本,转完可能占 4MB 内存。高频循环中反复转换要避免。
示例:提取第 5 个汉字
rs := []rune(str)
if len(rs) >= 5 && unicode.Is(unicode.Han, rs[4]) {
ch := rs[4]
}
strings.Map 适合统一字符变换,不是通用遍历工具
如果你的目标是“对每个字符做相同操作”,比如全转大写、删除控制符、替换 emoji,strings.Map 比手写 for range 更安全简洁。
函数签名:func Map(mapping func(rune) rune, s string) string
关键细节:
- 映射函数返回
-1表示删除该字符(不是跳过) - 返回新字符串,原串不变;内部已处理 UTF-8 边界,你不用管字节
- 不适合需要上下文判断的逻辑(比如“删掉和前一个相同的字符”),因为它是无状态单字符映射
示例:把所有 ASCII 字母转大写,其余字符保留
result := strings.Map(func(r rune) rune {
if r >= 'a' && r <p>真正难的不是语法,而是每次读写字符串时,都得下意识问一句:“我现在要的是字节位置,还是字符序号?”——<code>range</code> 给你的是前者(字节起始偏移),<code>[]rune</code> 给你的是后者(索引即序号),选错就等于在内存里走错路。</p>golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











