2

Go tips-笔记: 控制语句 30-35 mistakes

 1 year ago
source link: https://weedge.github.io/post/notions/go-tips/go-tips-04-control-structures/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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在每次迭代结束时执行一条语句。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK