for 和 range
一、遍历切片或数组
1. 遍历数组的同时修改数组元素
1 | func main() { |
如上,结果表明,循环只遍历了原始切片中的三个元素,在遍历切片时追加的元素不会增加循环的执行次数,因此循环最终还是会停下来。
原理:Go语言源码中,对于所有的 range 循环,Go 语言都会在编译期将原切片或者数组赋值给一个新变量,在赋值过程中就发生了复制,并且又使用 len 关键字预先获取了切片的长度,所以在循环中追加新元素不会改变循环执行的次数。
2. range 返回变量的地址
1 | func main() { |
原理:同时遍历索引和元素的 range 循环,Go语言也会额外创建一个新的变量 v2 存储切片中的元素,循环中使用的这个变量 v2 会在每一次迭代被重新赋值而覆盖,赋值时也会触发复制。
因此我们想访问数组中元素的地址时,应该使用 &arr[index]
这种形式
二、遍历哈希表
遍历哈希表时,编译器会根据 range 返回值的数量在循环体中插入需要的赋值语句。
for k := range hash{}
,会插入k := *hit.key
for k, v := range hash{}
,会插入k := *hit.key, v := *hit.value
遍历前,会通过 runtime.fastrand
生成一个随机数随机选择一个遍历桶的起始位置。Go 语言不希望使用者依赖固定的遍历顺序,因此引入了随机数保证遍历的随机性。那么遍历过程中,通过随机数先选出一个正常桶开始遍历,随后遍历所有溢出桶,因为哈希表中正常桶和溢出桶在内存中是连续的,最后按照索引顺序遍历哈希表中其他的桶,直到遍历完所有桶。
三、遍历字符串
1 | func main() { |
如上,遍历字符串分为两种情况
- 使用下标访问字符串,那将会得到字符串中每个字节存储的元素,无法正常输出汉字
- 使用 range 访问字符串,就会转换成 rune 类型,rune 类型的定义:
type rune = int32
,rune 类型就是为了区分字符值和整数值。因此如果当前 rune 是 ASCII 的,那么只会占用 1 字节长度,每次循环体运行之后只需要将索引加一;但是如果当前 rune 占用多个字节,就会使用runtime.decoderune
函数解码。循环运行后将索引加 N 个字节。
四、遍历 channel
使用 for range 也可以遍历 channel。如 for v := range ch {}
语句,会循环从 channel 中取出待处理的值,并且会调用 runtime.chanrecv2
阻塞当前协程。