Go 学习笔记(3)流程控制
1. 通用流程控制
if/else 条件语句
if/else
语句的基本格式为:
if cond1 {
statement1
} else if cond2 {
statement2
} else {
statement3
}
在 Go 中,if/else
语句中条件不需要加小括号,而后面的执行语句需要加大括号。
if/else
后的条件可以有两种形式:
if cond {}
if define; cond {}
即在条件中可以可以执行其他语句或定义变量,但定义的变量只能在 if/else
语句体中使用:
if a := 1; a == 0 {
statement1
} else if b := 2; a < 0 {
fmt.Println(a)
statement2
} else {
fmt.Println(b)
statement3
}
// a, b 不能在 if/else 外部使用
fmt.Println(a) // error
fmt.Println(b) // error
此外,Go 不支持三元运算符 ? :
。
switch 条件语句
switch
语句的基本格式为:
switch x {
case cond1, cond2, cond3:
statement1
case cond4:
statement2
default:
statement3
}
每个 case
后可以有多个条件,用 ,
隔开,若满足其中任意一个条件,则命中该 case
。此外,case
中的条件不知可以为常量,也可以时表达式或函数的调用,只要结果类型相符即可。
如:
switch x {
case getX():
statement1
case a + b:
statement2
}
省略 switch 条件
还可以省略 switch
语句中的条件,但 case
中条件的类型需要为布尔值:
switch {
case cond1:
statement1
case cond2:
statement2
default:
statement3
}
该用法可以与 if/else
语句相互替换:
if cond1 {
statement1
} else if cond2 {
statement2
} else {
statement3
}
上述两种写法的效果完全相同,但 case
条件中不支持执行其他表达式,如下面语句无法编译:
switch {
case a := 1; a > 0: // error
statement1
}
switch
后支持执行其他表达式:
// 带条件
switch a := 1; a {
case 1:
statement1
case 2:
statement2
}
// 或省略条件
switch a := 1; {
case a <= 1:
statement1
case a > 1:
statement2
}
fallthrough
默认情况下,只要命中一个 case
,则整个 switch
语句会退出,可以通过添加 fallthrough
关键字继续执行下面的 case
,即使该 case
的条件不会被命中:
a := 1
switch a {
case a == 1:
fmt.Println("a == 1")
fallthrough
case a > 1:
fmt.Println("a > 1")
}
// 输出
// a == 1
// a > 1
for 循环语句
Go 中的 for
循环与 C 语言中的类似,只是可以省略小括号:
for expr1; cond; expr2 {
}
其中,expr1
为初始化语句,cond
为终止条件,expr2
为迭代后处理语句。
如:
sum := 0
for i := 1; i < 10; i++ {
sum += i
}
“while” 循环
Go 中没有 while
关键字,while
循环也是使用 for
实现的,只需省略初始化语句和迭代处理语句即可:
i := 0
for i < 5 {
fmt.Println(i)
i++
}
break
for
循环可以不跟任何条件,构成无限循环,通过 break
退出循环:
i := 0
for {
fmt.Println(i)
i++
if i == 5 {
break
}
}
continue
continue
用于跳过某次迭代,执行下次迭代:
i := 0
for i < 10 {
if i % 3 == 0 {
i++
continue
}
fmt.Println(i)
i++
}
2. 特殊流程控制
Go 中特有的流程控制语句:defer
,panic
,recover
。
defer 语句
defer
语句可以推迟函数的运行,知道包含该defer
的函数运行结束,才会执行 defer
指定的函数。
func main() {
for i := 1; i < 5; i++ {
defer fmt.Println(i)
}
fmt.Println("输出完成")
}
// 输出完成
// 4
// 3
// 2
// 1
保存 defer
延迟任务的是一个类似栈的后进先出的结构,因此后面 defer
的任务会先执行。
此外,从输出结果也可以看出来,defer
任务会在入栈时保存变量的副本,而不是实际函数调用时再捕获变量。
defer
一般用于关闭数据库及文件等资源。
panic 函数
panic
函数调用时会停止正常的程序流程,并执行被 defer
延迟的函数调用,随后输出错误日志,包括错误信息及对战追踪,最后程序停止运行。
// 来自 https://docs.microsoft.com/zh-cn/learn/modules/go-control-flow/4-use-defer-statement
package main
import "fmt"
func highlow(high int, low int) {
if high < low {
fmt.Println("Panic!")
panic("highlow() low greater than high")
}
defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
fmt.Println("Call: highlow(", high, ",", low, ")")
highlow(high, low + 1)
}
func main() {
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
程序输出:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high
goroutine 1 [running]:
main.highlow(0x4b1580, 0xc00000e018)
/tmp/sandbox543440378/prog.go:8 +0x285
main.highlow(0x2, 0x2)
/tmp/sandbox543440378/prog.go:13 +0x20f
main.highlow(0x2, 0x1)
/tmp/sandbox543440378/prog.go:13 +0x20f
main.highlow(0x2, 0x0)
/tmp/sandbox543440378/prog.go:13 +0x20f
main.main()
/tmp/sandbox543440378/prog.go:17 +0x25
可以看出,当 high=2, low=3
时,程序调用了panic()
,并在 panic
函数运行前执行了 defer
延迟的函数调用,最后输出 panic
的错误信息,随后程序便结束运行了,"Program finished successfully!"
并没有正常输出。
当发生数组越界或取消 nil
指针时,Go 程序也会自动的调用 panic
函数。
recover 函数
recover
函数用于避免程序的崩溃,可以从程序的崩溃中恢复,重新获得程序的控制权。
func main() {
defer func() {
handler := recover()
if handler != nil {
fmt.Println("main(): recover", handler)
}
}()
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
在 main
函数中使用 recover
函数,则在 highlow
中程序发生崩溃时,仍是首先执行 defer
延迟的函数调用,但程序不会退出,而是执行 recover
函数中的内容。程序正常情况下,handler
为 nil
,不会产生印象,只有程序异常的情况下,handler
才不为 nil
。
Go 中的 panic
与 recover
机制类似于其他语言中的 try/catch
,用于处理程序的异常。