likes
comments
collection
share

Go语言系列之编程指引与陷阱:2 控制结构

作者站长头像
站长
· 阅读数 8

前言

本节主要讨论Go的控制语句,比如forselectswitch等。

  • 一些约定

    🌵:表示「能知道最好」,如果不知道也不会导致错误。

    🚩:表示「最起码要知道」,如果不知道很可能写出不好找的bug、性能问题。

    🈲:表示「这个就别做到了」,如果不知道就非常可能出问题。

本系列文章的最新版本见

Github版

欢迎大家starfork、提PR

for

🚩1、迭代变量只会初始化一次

只不过会不断的赋值

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)
	}
}

🈲2、goroutine对迭代变量的引用(闭包)

// 打印的val值是不确定的
for _, val := range values {
	go func() {
		fmt.Println(val)
	}()
}

// 打印的val值是不确定的
for _, val := range values {
	go func(val int) {
		fmt.Println(val)
	}(val)
}

🈲3、不要在循环中使用defer

Defers will grow your stack

下面的问题是什么?

fileNames := []string{}

for _, file := range {
    f, err := read(file)
    defer f.Close()
}

🚩4、range 后接的表达式只会被求值一次

比如 for i:v := range sliceInt

sliceInt就是一个表达式。当然表达式可以为字符串、切片、数组、channelmap等,但不论是什么表达式,都只会被求值一次,且将求值后的表达式复制给临时变量。

比如下面这个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.

select

🈲1、不要假定和依赖select的执行顺序

select {
case <-ch1:
	// balabala
case <-ch2:
	// balabala
case <-ch3:
	// balabala
case <-ch4:
	// balabala
case <-ch5:
	// balabala
}

goselect,并不是依次判断ch1/ch2/ch3/ch4/ch5...,而是随机的。

为什么是随机的呢? 因为怕饿死。

其他控制结构

🚩1、必须处理类型断言

BadGood
t := i.(string)
t, ok := i.(string)
if !ok {
  // handle the error gracefully
}

🚩2、嵌套不要太深,最多3层?

如果大于上面规定的层数,说明你应该写个函数了。

🚩3、注意break的使用

当遇到forswitchselect时,要清楚使用break产生的影响。

普通的break只会跳出最里层的forselectswitch结构

当你有疑惑的时候,一定是你嵌套太多层了 !

减少嵌套,生活更美好。

参考