控制结构
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、字符串、map、channel迭代循环如下: | 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
参考文章
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("继续执行") // 此处继续执行