Go基础|第5章:流程控制语句
Synopsis: 在 Go 中只有很少的几个控制结构,没有 do 或者 while 循环,只有 for 循环。有 if 语句和灵活的 switch 语句,还有延迟调用 defer 语句,后续学习并发 channel 章节时,我们还会学习 select 语句
1. for
1.1 基本格式
Go 语言只有一种循环结构:for
循环。基本的 for 循环由三部分组成,它们用分号隔开:
- 初始化语句
init statement
: 在第一次迭代前执行一次 - 条件表达式
condition
:在每次迭代前求值 - 后置语句
post statement
:在每次迭代的结尾执行
初始化语句通常为 简短变量声明语句 :=
,该变量声明仅在 for 语句的作用域中可见。一旦条件表达式的布尔值为 false,循环迭代就会终止。for 语句后面的三个构成部分外没有小括号,大括号 { } 则是必须的
package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) // 45 }
1.2 类 while 循环
初始化语句和后置语句都是可选的:
此时你可以去掉分号,就类似于 C 语言中的 while 语句:
package main import "fmt" func main() { n := 1 for n < 5 { // 省略初始化语句、后置语句以及后面的分号 n *= 2 } fmt.Println(n) // 8 }
1.3 无限循环
如果连循环条件也省略掉的话,那么该循环就永远不会结束(无限循环
)
1.4 for-range 遍历
可以使用 for := range
循环 string / array / slice / map / channel 中的每个元素:
package main import "fmt" func main() { strings := []string{"hello", "world"} for i, s := range strings { fmt.Println(i, s) } } /* Output: 0 hello 1 world */
注意:
range expression
只会在 for 语句开始执行时被求值一次,无论后边会有多少次迭代- range expression 的求值结果会被复制,也就是说,被迭代的对象是 range expression 结果值的副本而不是原值
1.5 continue 和 break
continue
和 break
关键字跟 C 语言中的作用一样,表示立即进入下一次循环、跳出最内层循环
sum := 0 for i := 1; i < 5; i++ { if i%2 != 0 { // skip odd numbers continue } sum += i } fmt.Println(sum) // 6
1.6 类 do-while 语句
实现类似 C 中的 do-while 语句,先执行循环中的语句,然后再判断表达式是否为真,如果为真则继续循环;如果为假,则终止循环:
方式一:
package main import "fmt" func main() { i := 0 for ok := true; ok; ok = i < 3 { fmt.Println(i) i++ } } /* Output: 0 1 2 */
方式二:
package main import "fmt" func main() { i := 0 for { fmt.Println(i) i++ if !(i < 3) { break } } } /* Output: 0 1 2 */
1.7 类 until-do 语句
实现类似 Bash 中的 until-do 语句,只要不符合判断条件,就不断循环执行指定的语句;一旦符合判断条件,就退出循环:
方式一:
package main import "fmt" func main() { i := 0 for ok := true; ok; ok = !(i >= 3) { fmt.Println(i) i++ } } /* Output: 0 1 2 */
方式二:
package main import "fmt" func main() { i := 0 for { fmt.Println(i) i++ if i >= 3 { break } } } /* Output: 0 1 2 */
2. if
2.1 基本格式
条件表达式的值为 true 时,执行 if 后面的语句。与 for 循环类似,表达式外无需小括号,而大括号 { } 则是必须的
package main import ( "fmt" "math" ) func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) } func main() { fmt.Println(sqrt(2), sqrt(-4)) } /* Output: 1.4142135623730951 2i */
2.2 带初始化语句
同 for 一样,if 语句可以在条件表达式前执行一个简单的初始化语句,该语句声明的变量作用域仅在 if 之内
package main import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } // 报错 undefined: v // fmt.Printf("v = %v", v) return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) } /* Output: 9 20 */
2.3 带 else if / else 语句
在 if 的简短变量声明语句中声明的变量,可以在对应的 else 块中使用
func area(x, y int) { if i := x * y; i > 200 { fmt.Printf("Big house") } else if i > 100 { fmt.Printf("Good house") } else { fmt.Printf("Just ok") } }
Go 语言没有三元条件运算符 res = expr ? x : y
,只能写成:
或者创建一个函数:
3. switch
switch
是编写一连串 if-else 语句的简便方法,从上往下
依次判断各 case 分支的值是否与 switch 后面的条件表达式值相等,一旦找到满足条件的 case 分支就运行它里面的语句,并退出(忽略后续的 case 分支,除非主动在 case 分支中以 fallthrough
语句结束)。如果所有 case 分支都不匹配,并且提供了 default
分支时,将执行 default 分支里的语句
3.1 基本格式
package main import ( "fmt" "runtime" ) func main() { fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.\n", os) } } /* Output: Go runs on windows. */
switch 的 case 分支条件无需为常量,且取值不必为整数:
package main import ( "fmt" "time" ) func main() { fmt.Println("When's Saturday?") today := time.Now().Weekday() switch time.Saturday { case today + 0: fmt.Println("Today.") case today + 1: fmt.Println("Tomorrow.") case today + 2: fmt.Println("In two days.") default: fmt.Println("Too far away.") } } /* Output: When's Saturday? Too far away. */
3.2 省略条件表达式
没有条件的 switch 同 switch true {...}
一样,这种形式能将一长串 if-then-else 写得更加清晰
package main import ( "fmt" "time" ) func main() { t := time.Now() switch { // missing expression means "true" case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } } /* Output: Good afternoon. */
3.3 case 分支包含多个条件表达式
package main import ( "fmt" ) func WhiteSpace(c rune) bool { switch c { case ' ', '\t', '\n', '\f', '\r': return true } return false } func main() { c := '\t' if ok := WhiteSpace(c); ok { fmt.Printf("%q is whitespace", c) } } /* Output: '\t' is whitespace */
3.4 fallthrough
如果 fallthrough
语句是 case 分支的 最后的语句
,那么执行完匹配的指定分支后不会退出 switch,而是把控制权交给下一个 case 分支
package main import ( "fmt" ) func main() { switch 2 { case 1: fmt.Println("1") fallthrough case 2: fmt.Println("2") fallthrough case 3: fmt.Println("3") } } /* Output: 2 3 */
3.5 break 退出 switch
break
语句终止 最内层 的 for
、switch
或 select
语句的执行,而 switch 的机制本来就是执行完匹配的 case 分支后就自动退出 switch 了:
或:
如果 switch 在 for 循环里面,那么可以在 for 循环的外面设置一个标签,在 switch 中使用 break 可以跳出外层的 for 循环,直达标签位置:
package main import ( "fmt" ) func main() { Loop: for _, ch := range "a b\nc" { switch ch { case ' ': // skip space break case '\n': // break at newline break Loop default: fmt.Printf("%c\n", ch) } } } /* Output: a b */
执行顺序:
- 首先执行 switch expression
- 然后从左至右、从上至下执行 case 分支中的各个 case expressions
- 第一个匹配 switch expression 的 case expression 立即运行 case 分支下的语句
- 此分支的其它 case expressions 被忽略
package main import ( "fmt" ) // Foo prints and returns n. func Foo(n int) int { fmt.Println(n) return n } func main() { switch Foo(2) { case Foo(1), Foo(2), Foo(3): // Foo(3) 不会被执行 fmt.Println("First case") fallthrough case Foo(4): // fallthrough 直接进入分支下面的语句,不执行 Foo(4) fmt.Println("Second case") } } /* Output: 2 1 2 First case Second case */
4. defer
4.1 基本格式
defer
会将跟在它后面的函数,推迟到外层函数返回时执行:
package main import "fmt" func main() { defer fmt.Println("World") fmt.Println("Hello") } /* Output: Hello World */
无论外层函数是正常结束还是引发 panic 错误,defer 语句都会被执行:
package main import "fmt" func main() { defer fmt.Println("World") panic("Stop") fmt.Println("Hello") } /* Output: World panic: Stop goroutine 1 [running]: main.main() D:/MyCode/go-in-action/tour/test.go:7 +0xc0 Process finished with exit code 2 */
如果调用了 os.Exit() 方法来退出程序,则不会执行 defer 语句:
package main import ( "fmt" "os" ) func main() { defer fmt.Println("after os.Exit") // never be called. os.Exit(2) } /* Output: exit status 2 */
4.2 defer stack
推迟的函数调用会被压入一个栈中!当外层函数返回时,被推迟的函数会按照 后进先出
的顺序调用
注意:推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
package main import "fmt" func main() { fmt.Println("Hello") for i := 1; i <= 3; i++ { defer fmt.Println(i) // 推迟调用的函数的参数 i 会立即求值,但直到外层函数返回前该函数都不会被调用 } fmt.Println("World") } /* Output: Hello World 3 2 1 */
4.3 可修改外层函数的命名返回值
推迟调用的 匿名函数
可以访问和修改外层函数的 命名返回值
package main import "fmt" func foo() (result string) { defer func() { result = "Change World" // change value at the very last moment }() return "Hello World" } func main() { fmt.Printf("Result of foo(): %q", foo()) } /* Output: Result of foo(): "Change World" */
4.4 常见用途
(1) 释放资源
defer
语句经常被用于处理成对的操作,比如打开/关闭、连接/断开连接、加锁/释放锁等。通
过 defer 机制,可以保证不论外层函数逻辑多复杂、在任何执行顺序下,打开的资源都能被释放(释放资源的
defer 语句应该直接跟在请求资源的语句后面)
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
(2) 处理 panic 错误
recover
函数仅在延迟调用的函数内部时才有效,用于捕获 panic 错误
package main import "fmt" func foo() int { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() m := 1 panic("foo: fail") m = 2 return m } func main() { n := foo() // Since the panic occurred before foo returned a value, n still has its initial zero value. fmt.Println("main received", n) } /* Output: foo: fail main received 0 */
使用 defer 和 recover 修改外层函数的命名返回值:
package main import ( "errors" "fmt" ) func foo() error { var err error defer func() { if r := recover(); r != nil { err = errors.New("recoverd in foo") } }() panic("foo: fail") return err } func bar() (err error) { // 命名返回值 defer func() { if r := recover(); r != nil { err = errors.New("recoverd in bar()") // 可以修改外层函数的命名返回值 } }() panic("bar: fail") return } func main() { fmt.Println(foo()) fmt.Println(bar()) } /* Output: <nil> recoverd in bar() */
0 条评论
评论者的用户名
评论时间暂时还没有评论.