笔记
30.忽略了元素在 for range循环中被复制 (重要)
需要注意,range
循环中的值元素是一个复制的副本。因此,如果值是需要改变结构,只会更新副本,而不是元素本身,除非修改的值或字段是指针。在经典for循环或者for range循环中,通过索引访问元素来进行修改。
31.忽略了参数在for range循环中的计算方式 (重要)
type slice struct {
array unsafe.Pointer
len int
cap int
}
s := []int{0, 1, 2}
for range s {
// debug check slice array ptr (bp[0]),
// if append happen slicegrow, array ptr change -> new array
bp := (*[3]uintptr)(unsafe.Pointer(&s1))
fmt.Printf("0x%x\\n", bp[0])
s = append(s, 10)
}
notice: 这段代码在原文中分析过程是有误的,但不影响整体结果;range s 之后会发生值拷贝出现copy s, 在append之前,copy s 和 s的array ptr指向同一地址空间;当发生append之后,s 发生来扩容,s的ptr指向了新的地址空间,copy s还是指向原来的地址空间,两者的ptr指向的地址空间已经不同了;
对于ch chan 同理 , range ch 之后会发生值拷贝出现copy ch;对copy ch进行遍历;
对应数组arr […]int{1,2,3} 也是同理,range arr 之后会发生值拷贝出现copy arr; 对copy arr进行遍历, 如果想想改变对应arr的值,可以使用下标访问,或者 range &arr 之后 反生copy 数组引用&arr 指向同一地址空间;
32.忽略了在for range中使用指针元素的影响 (重要)
由于 for _, val := range &val的地址是一个常量,每次迭代指向不同的值,但是地址是同一个,所以在使用&val, 需要特别注意,如果存放&val值,循环迭代完之后,&val指向最后一个元素;如何解决呢, 两个主要的解决方案: 1. 值拷贝,然后赋予地址;2. 直接只用下标对应值的地址;
33.在map迭代期间做出错误的假设 (重要)
一种是错误的有序性假设, map是无序的,每次循环是随机获取key,就是说,插入顺序和读取的顺序不一致,每次循环读取的顺序都不同;应该清楚这些map无序行为,这样代码就不会基于错误的假设;
那么为什么 Go 有这样一种令人惊讶的方式来遍历map呢?这是语言设计者有意识的选择。他们想添加某种形式的随机性,以确保开发人员在使用map时永远不会依赖任何顺序假设(请参阅http://mng.bz/M2JW);
一种是迭代map时插入k/v,在 Go 中,允许在迭代期间更新map(插入或删除元素);它不会导致编译错误或运行时错误。但是,在迭代期间在map中添加条目时,应该考虑这种情况,以避免出现不确定的结果。出现的情况:
在Go中,如果在迭代过程中创建了map条目,则可以在迭代过程中产生或跳过。对于创建的每个条目以及从一个迭代到下一个迭代,选择可能会有所不同。
必须牢记这种行为,以确保代码不会产生不可预测的输出。如果想在迭代map的同时更新map并确保添加的条目不是迭代的一部分,一种解决方案是copy map,迭代map, 在用新的copy map中添加条目;
总而言之,当使用map时,不应该依赖以下内容:
- 按键排序的数据
- 保留插入顺序
- 确定性迭代顺序
- 在添加元素的同一迭代中生成的元素
记住这些行为应该有助于避免基于错误假设的常见错误。
34.忽略 break 语句是如何工作的
在Go中, 一个基本规则是break
语句终止的执行最里面的for
, switch
, 或select
语句。
如何编写代码来打破循环呢? 最惯用的方法是使用标签, 从对应的标签代码块中break; 或者对标签块代码封装成函数,直接return;
35.在循环中使用 defer
使用defer
时,必须记住它会在周围函数返回时安排函数调用。因此,defer
在循环内调用将堆叠所有调用:它们不会在每次迭代期间执行,例如,如果循环未终止,这可能会导致内存泄漏。解决这个问题最方便的方法是在每次迭代中引入另一个函数来调用。但是,如果性能至关重要,那么一个缺点就是函数调用增加的开销。如果有这样的情况并且想要防止这种开销,应该在循环之前摆脱defer
并手动处理延迟调用。
概括
- 循环中的值元素
range
是一个副本。因此要改变一个结构,例如,通过其索引或通过经典for
循环访问它(除非修改的元素或字段是指针)。 - 了解传递给运算符的表达式
range
在循环开始之前仅计算一次可以帮助避免常见错误,例如通道或切片迭代中的低效赋值。 - 使用局部变量或使用索引访问元素,可以防止在循环内复制指针时出错。
- 为确保在使用map时可预测输出,请记住map数据结构
- 不按键排序数据
- 不保留插入顺序
- 没有确定的迭代顺序
- 不保证在迭代期间添加的元素将在本次迭代中生成
break
语句终止的执行最里面的for
,switch
, 或select
语句,使用标签,可以从对应的标签代码块中break。- 提取函数内部的循环逻辑会导致
defer
在每次迭代结束时执行一条语句。