本节主要讨论Go
的控制语句,比如for
、select
、switch
等。
-
一些约定
🌵:表示「能知道最好」,如果不知道也不会导致错误。
🚩:表示「最起码要知道」,如果不知道很可能写出不好找的
bug
、性能问题。🈲:表示「这个就别做到了」,如果不知道就非常可能出问题。
只不过会不断的赋值
func main() {
arr := []string{"one", "two", "three"}
var wg sync.WaitGroup
for _, v := range arr {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(v) // 闭包
}()
}
wg.Wait()
}
// three
// three
// three
不要被代码中的v:= range arr
中的:=
给迷惑住,v
只会初始化一次!
下面是另一个例子。输出是什么?如果没有按照预想的结果输出,如何修改?
package main
import (
"fmt"
)
type Custom struct {
Id uint32 `json:"id"`
Name string `json:"name"`
}
func main() {
customs := []Custom{
{1, "one"},
{2, "two"},
{3, "three"},
}
var m = make(map[int]*Custom, len(customs))
for i, cm := range customs {
m[i] = &cm
fmt.Printf("%p\n", &cm)
}
for k, v := range m {
fmt.Printf("key:%d, val:%+v\n", k, v)
}
}
// 打印的val值是不确定的
for _, val := range values {
go func() {
fmt.Println(val)
}()
}
// 打印的val值是不确定的
for _, val := range values {
go func(val int) {
fmt.Println(val)
}(val)
}
Defers will grow your stack
下面的问题是什么?
fileNames := []string{}
for _, file := range {
f, err := read(file)
defer f.Close()
}
比如 for i:v := range sliceInt
sliceInt
就是一个表达式。当然表达式可以为字符串、切片、数组、channel
、map
等,但不论是什么表达式,都只会被求值一次,且将求值后的表达式复制给临时变量。
比如下面这个for range
会无限循环吗?
s := []int{0, 1, 2}
for range s {
s = append(s, 10)
}
实际的执行
s := []int{0, 1, 2}
copyS := s // 复制
for i := 0; i < len(copyS); i++ {
s = append(s, 10)
}
fmt.Println(s)
fmt.Println(copyS)
the range loop evaluates the provided expression only once, before the beginning of the loop, by doing a copy (regardless of the type).
为了比较学习,下面这个原始for
循环呢?会无限循环吗?
s := []int{0, 1, 2}
for i:=0;i<len(s); i++ {
s = append(s, 10)
}
the len(s) expression is evaluated during each iteration.
存在两种方式: 1、for range 会将后面的表达式的值复制一份
2、for i:=0 ... 通过下标遍历
回答:采用第二种通过下标的方式进行访问。为何?主要第一种方式代价大、还可能存在坑。
下面就来聊聊这个坑。
for range 的时候,会 copy 一份 range 后的 expression。
func main() {
var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
fmt.Println("original a =", a) // [1 2 3 4 5]
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("after for range loop, r =", r) // [1 2 3 4 5]
fmt.Println("after for range loop, a =", a) // [1 12 13 4 5]
}
The reason is that participating in the for range loop is a copy of the range expression. That is, in the above example, it is the copy of a that is actually participating in the loop, not the real a.
千万注意哈。
select {
case <-ch1:
// balabala
case <-ch2:
// balabala
case <-ch3:
// balabala
case <-ch4:
// balabala
case <-ch5:
// balabala
}
go
的select
,并不是依次判断ch1/ch2/ch3/ch4/ch5...
,而是随机的。
为什么是随机的呢? 因为怕饿死。
Bad | Good |
---|---|
t := i.(string) |
t, ok := i.(string)
if !ok {
// handle the error gracefully
} |
如果大于上面规定的层数,说明你应该写个函数了。
当遇到for
、switch
、select
时,要清楚使用break
产生的影响。
普通的break
只会跳出最里层的for
、select
、switch
结构
当你有疑惑的时候,一定是你嵌套太多层了 !
减少嵌套,生活更美好。
白明《Go语言精进之路》(📚)
[100 go mistakes](📚)
- Uber Go Style Guide
- Effective Go