range仅支持slice、map、string、channel四类;自定义类型需转换;遍历切片时v是副本,修改无效,须用索引;map遍历顺序随机是设计使然;并发读写map会panic;字符串中range的i是字节偏移非字符序号。

range 不是万能迭代器,它只认四种类型:slice、map、string、channel;自定义类型必须转成这四者之一才能用,否则编译报错。
遍历切片时修改原值为什么没效果
因为 v 是每次迭代复制出来的副本,不是引用。写 v = v * 2 只改了临时变量,原切片元素完全不变。
- 要改原切片,必须用索引:
for i := range s { s[i] = s[i] * 2 }或for i, v := range s { s[i] = v * 2 } - 结构体切片更危险:
v.Field = x无效,除非v字段本身是指针或 slice - 只读场景(打印、计算)用
for _, v := range s完全没问题;但凡涉及“写”,就别碰v赋值
map 遍历顺序随机不是 bug 是设计
Go 明确规定 range 遍历 map 的起始哈希桶是随机的,所以键的输出顺序不固定——这是防你依赖顺序写的代码侥幸跑通。
- 测试里偶尔顺序一致?别信,生产环境大概率变
- 需要稳定顺序(比如日志输出、JSON 序列化),得先收集 key:
keys := make([]string, 0, len(m)),再for k := range m { keys = append(keys, k) },然后sort.Strings(keys),最后for _, k := range keys { fmt.Println(k, m[k]) } - 并发读写
map时用range会 panic:fatal error: concurrent map iteration and map write,必须加锁或换sync.Map
字符串遍历中 i 是字节偏移不是字符序号
for i, r := range s 中的 i 是该 rune 在字节序列里的起始位置,不是“第几个字符”。例如 "你好" 第二次迭代 i == 3,不是 1。
立即学习“go语言免费学习笔记(深入)”;
- 误把
i当字符序号,会导致跳过半个汉字、取错子串、s[i]拿到非法 UTF-8 字节 - 需要字符序号就自己计数:
idx := 0; for _, r := range s { idx++; if idx == 2 { /* 处理第二个字符 */ } } - 只查 ASCII 字符(如
'a')或做字节级操作,用for i := 0; i 更快,绕过 UTF-8 解码开销
Go 1.23+ 支持 range over func 但签名很严格
不是任意函数都能 range,必须是 func(yield func(T) bool) 类型,标准库提供 iter.Seq[T] 别名。
- 别写成
func() T或func() (T, bool)—— 这是 Go 1.22 旧模式,1.23 已弃用,会报cannot range over (type func() (int, bool)) - DB 分页迭代器必须在
yield内部触发查询,而不是函数开头一次查完;否则每次range都重拉整页,性能崩 - 组合多个迭代器时,别嵌套
for id := range UserIDs() { for detail := range GetUserDetail(id) { } },这是 N+1 查询;改用iter.Map
最常被忽略的点:range 对所有类型的值都是按需复制的,哪怕你只读不写,底层也可能触发内存拷贝;如果数据量大、结构体字段多,这个开销比你想的要实在。


















