Go系列:控制结构

283 阅读3分钟

控制结构

Go语言里有很多流程控制语句:if-else、for-range、switch、select、goto、defer等

if-else

使用形式

  • if condition
  • if init;condition init初始化语句,一般为赋值表达式,可以是函数调用 condition关系表达式或逻辑表达式,控制条件。
// 条件表达式
if age > 18 {
    fmt.Println("已经成年了")
}
// 赋值表达式 + 条件表达式
if age := 20;age > 18 {
    fmt.Println("已经成年了")
}

注意点:

  • 条件表达式是可以带括号的,但不建议;
  • 初始化语句不一定是赋值表达式,还可以是函数调用等其他语句;
  • 表达式后面的括号必须在表达式尾部,不可以换行。

使用场景

1、函数调用时监测函数错误

if val,err := pack.Fun();err != nil { 
    return err 
}

2、通过defer使程序从panic中恢复

defer func() { 
    if err := recover();err != nil { 
        fmt.Println(err) 
    } 
}()

3、检测map中是否存在一个 k-v对

if val,ok := map[k]; ok { 
    return val
}

4、类型断言,检测类型变量 varInt 是否包含T类型

if val,ok := varInt.(T); ok { 
    return val
}

5、检测通道是否关闭

if input,open := <-ch; !open { 
    break //通道是关闭的
}

for

使用形式

  • for
  • for condition
  • for init;condition;post

使用场景

// 1、无条件的 for 和只有条件的 for 相当于其他语言的 while(true)
// 2、带初始化语句的 for

// 翻转slice内元素
s := []int{1,2,3,4}
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
    s[i], s[j] = s[j], s[i]
}

for-range

使用形式

  • for k, v := range value range 可以对数组slice字符串mapchannel迭代循环如下: | value | k值 | v值 | 说明| | --- | --- | --- |--- | |string | index | str[index]| | |silce | index | s[index]| | |数组 | index | arr[index]| 值拷贝 | |map | key | m[key]| | |chan | element | |

使用场景

1、作用于数组

a := [2]int{1, 2}
for i, v := range a {
     fmt.Printf("index:%d value:%d", i, v)
}
// output
index:0 value:1
index:1 value:2

2、作用于slice

3、作用于string

4、作用于map

5、作用于chan

参考文章

for range使用中的注意点

switch

使用形式

golang switch 非常灵活,分支表达式不限于整数或常量,可以是任意类型,区别于其他语言的地方是,每个switch中每个case语句都带有break,匹配成功后不会自动向下执行其他 case,而是跳出整个switch,可以使用fallthrough 穿透 case 执行后面 case 的代码。

使用场景

1、普通分支判断

var index int = 0
switch index {
case 0:
    fmt.Printf("index:%d", 0)
// 等价于
// case 1:
// case 2:
case 1,2:
    fmt.Printf("index:%s", "is not 0")
}

2、替换多重if-else

var index int = 1
switch {
case index > -10 && index < 10:
    fmt.Println("-10~10")
case index > -5 && index < 5:
    fmt.Println("-5~5")
}

3、类型判断

switch 语句还可以被用于type-switch来判断某个 interface 变量中实际存储的变量类型。

var x interface{}
switch i := x.(type) {
// 带初始化语句 
case int,int8: 
    fmt.Printf(" x 的类型 :%T\r\n", i) 
default: 
    fmt.Printf("未知型") }

select

select是Go中的控制结构,类似于switch,用于处理异步IO操作。select会监听case语句中的channel的读写操作,当case中的读写操作为非阻塞状态(即能读或写)时,将会触发相应的动作。

  • 如有多个可运行的case,select会随机公平的选出一个执行,其他的不会执行;
  • 如果没有可运行的case,且有default语句,那么就会执行default的动作;
  • 如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行。

使用形式

select {
    // case分支一定是chan的读写操作
    case <-ch:
        //…
    default:
}

使用场景

1、永久阻塞

// 当启动协程处理任务时,不希望main函数退出,此时可以让main永久阻塞
func main() {
    // 协程逻辑
    select{} //永久阻塞主线程
}

2、限时等待

// 使用全局ch来接受response,如果时间超过3S,ch,则第二条case将执行
var ch = make(chan int)
// do request
func request() {
    select {
    case data := <-ch:
        do(data)
    case <-time.After(time.Second * 3):
        fmt.Println("request time out")
    }
}

func do(data int) {
    //...
}

3、快速检错

// 有时我们使用管道来传输错误,此时就可以用 select 语句快速检测管道中是否有错误,避免陷入循环
ch := make(chan error, active)
// 其他逻辑 传入 ch 用于记录错误
select {
    case mangerErr <- ch:
        // 错误检测、避免陷入循环
        if mangerErr != nil {
            break;
        }
}

跳出for-switch/for-select循环体

跳出 for-switch、for-select的方式有case内return、带标签的break、带标签的goto语句,具体规则如下:

  • 使用 case 内的 return 语句,直接跳出函数,后面的代码不再执行;
  • 带标签的 break 语句,标签只能在循环体之前,仅跳出循环体,后面的代码继续执行;
  • 带标签的 goto 语句,如标签在循环体之前,则会再次进入循环导致死循环,标签在循环体之后,可跳出循环体,后面的代码继续执行。 代码示例:
loop:
    for {
        switch {
        case true:
            fmt.Println("执行")
            break loop
            // return // 跳出函数
            // break  // 跳出 switch/select
        }
    }
fmt.Println("继续执行") // 此处继续执行