Skip to main content

控制流相关语法

if/else

基本

在 Go 中,你不需要在条件中使用括号。 else 子句可选。 但是,大括号仍然是必需的。 此外,为了减少行,Go 不支持三元 if 语句

下面是 if 语句的一个基本示例:

package main

import "fmt"

func main() {
x := 27
if x%2 == 0 {
fmt.Println(x, "is even")
}
}

复合 if 语句

Go 支持复合 if 语句。 可以使用 else if 语句对语句进行嵌套。 下面是一个示例:

package main

import "fmt"

func givemeanumber() int {
return -1
}

func main() {
if num := givemeanumber(); num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has only one digit")
} else {
fmt.Println(num, "has multiple digits")
}
}

switch

基本

像 if 语句一样,switch 条件不需要括号。 最简单形式的 switch 语句如下所示:

package main

import (
"fmt"
"math/rand"
"time"
)

func main() {
sec := time.Now().Unix()
rand.Seed(sec)
i := rand.Int31n(10)

switch i {
case 0:
fmt.Print("zero...")
case 1:
fmt.Print("one...")
case 2:
fmt.Print("two...")
}

fmt.Println("ok")
}

使用多个表达式

有时,多个表达式仅与一个 case 语句匹配。 在 Go 中,如果希望 case 语句包含多个表达式,请使用逗号 (,) 来分隔表达式。 此方法可避免代码重复。

以下代码示例演示了如何包含多个表达式。

package main

import "fmt"

func location(city string) (string, string) {
var region string
var continent string
switch city {
case "Delhi", "Hyderabad", "Mumbai", "Chennai", "Kochi":
region, continent = "India", "Asia"
case "Lafayette", "Louisville", "Boulder":
region, continent = "Colorado", "USA"
case "Irvine", "Los Angeles", "San Diego":
region, continent = "California", "USA"
default:
region, continent = "Unknown", "Unknown"
}
return region, continent
}
func main() {
region, continent := location("Irvine")
fmt.Printf("John works in %s, %s\n", region, continent)
}

调用函数

switch 还可以调用函数。 在该函数中,可以针对可能的返回值编写 case 语句。 例如,以下代码调用 time.Now() 函数。 它提供的输出取决于当前工作日。

package main

import (
"fmt"
"time"
)

func main() {
switch time.Now().Weekday().String() {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("It's time to learn some Go.")
default:
fmt.Println("It's the weekend, time to rest!")
}

fmt.Println(time.Now().Weekday().String())
}

从 switch 语句调用函数时,无需更改表达式即可修改其逻辑,因为你始终会验证函数返回的内容。

此外,还可以从 case 语句调用函数。 例如,使用此方法可以通过正则表达式来匹配特定模式。 下面是一个示例:

package main

import "fmt"

import "regexp"

func main() {
var email = regexp.MustCompile(`^[^@]+@[^@.]+\.[^@.]+`)
var phone = regexp.MustCompile(`^[(]?[0-9][0-9][0-9][). \-]*[0-9][0-9][0-9][.\-]?[0-9][0-9][0-9][0-9]`)

contact := "foo@bar.com"

switch {
case email.MatchString(contact):
fmt.Println(contact, "is an email")
case phone.MatchString(contact):
fmt.Println(contact, "is a phone number")
default:
fmt.Println(contact, "is not recognized")
}
}

请注意,switch 块没有任何验证表达式。 在 Go 中,可以在 switch 语句中省略条件,就像在 if 语句中那样。 此模式类似于比较 true 值,就像强制 switch 语句一直运行一样。

使逻辑进入到下一个 case

在某些编程语言中,你会在每个 case 语句末尾写一个 break 关键字。 但在 Go 中,当逻辑进入某个 case 时,它会退出 switch 块,除非你显式停止它。 若要使逻辑进入到下一个紧邻的 case,请使用 fallthrough 关键字。

若要更好地了解此模式,请查看以下代码示例。

package main

import (
"fmt"
)

func main() {
switch num := 15; {
case num < 50:
fmt.Printf("%d is less than 50\n", num)
fallthrough
case num > 100:
fmt.Printf("%d is greater than 100\n", num)
fallthrough
case num < 200:
fmt.Printf("%d is less than 200", num)
}
}

执行这段代码,结果是

15 is less than 50
15 is greater than 100
15 is less than 200

请注意,由于 num 为 15(小于 50),因此它与第一个 case 匹配。 但是,num 不大于 100。 由于第一个 case 语句包含 fallthrough 关键字,因此逻辑会立即转到下一个 case 语句,而不会对该 case 进行验证。 因此,在使用 fallthrough 关键字时必须谨慎。 该代码产生的行为可能不是你想要的。

for

基本

分号 (;) 分隔 for 循环的三个组件:

  • 在第一次迭代之前执行的初始语句(可选)。
  • 在每次迭代之前计算的条件表达式。 该条件为 false 时,循环会停止。
  • 在每次迭代结束时执行的后处理语句(可选)。 如你所见,Go 中的 for 循环类似于 C、Java 和 C# 之类的编程语言中的 for 循环。

Go中没有while关键字

在 Go 中,最简单形式的 for 循环如下所示:

func main() {
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println("sum of 1..100 is", sum)
}

使用 defer、panic 和 recover 函数进行控制

defer 函数

在 Go 中,defer 语句会推迟函数(包括任何参数)的运行,直到包含 defer 语句的函数完成。 通常情况下,当你想要避免忘记任务(例如关闭文件或运行清理进程)时,可以推迟某个函数的运行。

可以根据需要推迟任意多个函数。 defer 语句按逆序运行,先运行最后一个,最后运行第一个。

通过运行以下示例代码来查看此模式的工作原理:

package main

import "fmt"

func main() {
for i := 1; i <= 4; i++ {
defer fmt.Println("deferred", -i)
fmt.Println("regular", i)
}
}

输出:

regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1

defer 函数的一个典型用例是在使用完文件后将其关闭。 下面是一个示例:

package main

import (
"io"
"os"
"fmt"
)

func main() {
newfile, error := os.Create("learnGo.txt")
if error != nil {
fmt.Println("Error: Could not create file.")
return
}
defer newfile.Close()

if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
fmt.Println("Error: Could not write to file.")
return
}

newfile.Sync()
}

创建或打开某个文件后,可以推迟 .Close() 函数的执行,以免在你完成后忘记关闭该文件。

panic 函数

类似其他语言的try/catch

运行时错误会使 Go 程序崩溃,例如尝试通过使用超出范围的索引或取消引用 nil 指针来访问数组。 你也可以强制程序崩溃。

内置 panic() 函数可以停止 Go 程序中的正常控制流。 当你使用 panic 调用时,任何延迟的函数调用都将正常运行。 进程会在堆栈中继续,直到所有函数都返回。 然后,程序会崩溃并记录日志消息。 此消息包含错误信息和堆栈跟踪,有助于诊断问题的根本原因。

调用 panic() 函数时,可以添加任何值作为参数。 通常,你会发送一条错误消息,说明为什么会进入紧急状态。

例如,下面的代码将 panic 和 defer 函数组合在一起。 尝试运行此代码以了解控制流的中断。 请注意,清理过程仍会运行。

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(0x2, 0x3)
/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
/tmp/sandbox/prog.go:18 +0x298
main.main()
/tmp/sandbox/prog.go:6 +0x37

Program exited: status 2.

下面是运行代码时会发生的情况:

一切正常运行。 程序将输出传递到 highlow() 函数中的高值和低值。

如果 low 的值大于 high 的值,则程序会崩溃。 会显示“Panic!”消息。 此时,控制流中断,所有推迟的函数都开始输出“Deferred...”消息。

程序崩溃,并显示完整的堆栈跟踪。 不会显示“Program finished successfully!”消息。

在发生未预料到的严重错误时,系统通常会运行对 panic() 函数的调用。 若要避免程序崩溃,可以使用名为 recover() 的另一个函数。

recover 函数

有时,你可能想要避免程序崩溃,改为在内部报告错误。 或者,你可能想要先清理混乱情况,然后再让程序崩溃。 例如,你可能想要关闭与某个资源的连接,以免出现更多问题。

Go 提供内置 recover() 函数,让你可以在程序崩溃之后重新获得控制权。 你只会在你同时调用 defer 的函数中调用 recover。 如果调用 recover() 函数,则在正常运行的情况下,它会返回 nil,没有任何其他作用。

尝试修改前面的代码中的 main 函数,以添加对 recover() 的调用,如下所示:

func main() {
defer func() {
handler := recover()
if handler != nil {
fmt.Println("main(): recover", handler)
}
}()

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 )
main(): recover from panic highlow() low greater than high

Program exited.