Golang 中的条件控制语句与 Clang 相似,但省去了许多不必要的附加符号,提供必要的精简方式。
条件控制语句
if-else 语句
- 与
Clang 不同的是 Golang 中的表达式都省去了 () 。
Usage 1:
1 2 3 4 5 6 7
| if <condition1> { <block1> } else if <condition2> { <block2> } else { <block0> }
|
Example:
1 2 3
| if returnValue == -1 { fmt.Println("Something Error.") }
|
1 2 3 4 5
| if weight < 240 { fmt.Println("Weight OK!") } else { fmt.Println("Too weight.") }
|
1 2 3 4 5 6 7 8 9 10 11
| if score < 60 { fmt.Println("F") } else if score < 70 { fmt.Println("P") } else if (score < 80) { fmt.Println("C") } else if (score < 90) { fmt.Println("B") } else { fmt.Println("A") }
|
Usage 2:
1 2 3
| if <init>; <conditional> { <block> }
|
Golang 没有三目运算符,因为官方认为其影响 Golang 的语法整洁性。
switch-case 语句
Usage 1:
1 2 3 4 5 6 7 8 9
| switch <variable> { case <value1>: <block1> case <value2>: <block2> ... default: <block0> }
|
Example:
1 2 3 4 5 6 7 8 9 10 11 12
| switch level { case 1: fmt.Println("P") case 2: fmt.Println("C") case 3: fmt.Println("B") case 4: fmt.Println("A") default: fmt.Println("F") }
|
Usage 2:
1 2 3 4 5 6 7 8 9
| switch { case <condition1>: <block1> case <condition2>: <block2> ... default: <block0> }
|
Example:
1 2 3 4 5 6 7 8 9 10 11 12
| switch { case score < 60: fmt.Println("F") case score < 70: fmt.Println("P") case score < 80: fmt.Println("C") case score < 90: fmt.Println("B") default: fmt.Println("A") }
|
select-case 语句
expression 必须是一个通信操作,如 x := <-c。
select 语句将随机抽取一个 case,如果通信成功将运行 block;如果通信失败并且存在 default 将运行 block0。
1 2 3 4 5 6 7 8
| select { case <expression1>: <block1> case <expression2>: <block2> default: <block0> }
|
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var c1 = make(chan string) var c2 = make(chan string)
func Thread1() { time.Sleep(time.Millisecond * 2100) c1 <- "Thread1 is ready." }
func Thread2() { time.Sleep(time.Millisecond * 2100) c2 <- "Thread2 is ready." }
func ThreadMain() { for i := 0; i < 10; i++ { select { case x := <-c1: fmt.Println("Get:", x) case x := <-c2: fmt.Println("Get:", x) default: fmt.Println("Get Nothing.") time.Sleep(time.Millisecond * 500) } } }
func main() { go Thread1() go Thread2() ThreadMain() }
|
1 2 3 4 5 6 7 8 9 10
| Get Nothing. Get Nothing. Get Nothing. Get Nothing. Get Nothing. Get: Thread1 is ready. Get: Thread2 is ready. Get Nothing. Get Nothing. Get Nothing.
|
- 注意在案例输出中,
Thread1 is ready. 与 Thread2 is ready. 先后是随机的,因为到第五次循环(2500ms)Thread1 与 Thread2 都准备就绪。
- 如果将
A1 处参数修改为 2000,则 Thread1 is ready. 在先概率更高,因为 Thread1 与 Thread2 理论上恰好同时在第四次循环(2000ms)准备就绪,而线程启动需要其他准备时间且 Thread1 先于 Thread2 启动。
循环控制语句
for 语句
| Golang |
Clang |
for <init>; <condition>; <post> { <block> } |
for (<init>; <condition>; <post>) { <block>; } |
for <condition> { <block> } |
while (<condition>) { <block>; } |
for { <block> } |
while (true) { <block>; } |
Golang 中的 for 语句 ; 间的语句也可以是空语句
Example:
1 2 3
| for i := 0; i < 10; i++ { fmt.Println(i) }
|
1 2 3 4 5 6 7 8 9
| var i int = 0
for i < 10 { fmt.Println(i) i++ }
|
1 2 3 4 5 6 7 8
|
for { fmt.Println("Running...") time.Sleep(time.Millisecond * 500) }
|
跳转语句
-
continue:跳转到下一个 for 循环。
1 2 3 4 5 6
| for i := 0; i < 5; i++ { if i == 3 { continue } fmt.Println(i) }
|
-
break:跳转到当前 for 循环外。
1 2 3 4 5 6
| for i := 0; i < 5; i++ { if i == 3 { break } fmt.Println(i) }
|
-
goto:跳转到指定标签
1 2 3 4 5 6 7 8 9
| for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { if i == 3 and j == 3 { goto tag } fmt.Println(i, j) } } tag:
|
for-range 语句
1 2 3 4
| slice := []int{1, 2, 3, 4, 5} for key, value := range slice { fmt.Println(key, value) }
|
- 数组、
string、slice、map、channel 等都可以使用 for-range 语句遍历。其中 channel 没有 key 且阻塞。
1 2 3 4
| channel := make(chan int, 10) for value := range channel { fmt.Println(value) }
|
特殊控制语句
Golang 从语法上支持并发,它有其特殊的控制语句。
defer 语句
defer 语句用法
defer 语句用于将操作延迟到函数结束执行,其操作甚至迟于 return 操作。
defer 语句将延迟操作压栈,在结束时逆序执行。
defer 语句必须使用函数。可以使用匿名函数。
1 2 3 4 5
| func Echo() { defer fmt.Println("Function Exited.") defer fmt.Println("Echoed.") fmt.Println("Hello World!") }
|
1 2 3
| Hello World! Echoed. Function Exited.
|
defer 语句怪用
1 2 3 4 5
| func Demo() (s string) { s = "Edit by Function" defer func() { s = "Edit by Defer" } return s }
|
- 循环延迟返回值相同。该案例中,
Demo1 中 defer 访问 for 循环定义的 i,在程序结束时访问其值为 3;Demo2 中 defer 访问每次循环体内定义的 i,可以认为是循环变量 i 的快照版本。
1 2 3 4 5
| func Demo1() { for i := 0; i < 3; i++ { defer fmt.Println(i) } }
|
1 2 3 4 5 6
| func Demo2() { for i := 0; i < 3; i++ { i := i defer fmt.Println(i) } }
|
go 语句
go 语句将创建一个 gorountine,可以简单认为是一个线程或协程。go 语句的对象是一个函数,将函数作为一个新的线程运行。
- 关于什么是线程,不作赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func thread() { for i := 0; i < 5; i++ { fmt.Println("Thread", i) time.Sleep(time.Microsecond * 50) } }
func main() { go thread() for i := 0; i < 5; i++ { fmt.Println("Main", i) time.Sleep(time.Microsecond * 50) } }
|
1 2 3 4 5 6 7 8 9 10
| Main 0 Thread 0 Thread 1 Main 1 Main 2 Thread 2 Main 3 Thread 3 Thread 4 Main 4
|
异常处理
error 接口
接口原型
1 2 3
| type error interface { Error() string }
|
构造异常
1 2
| err := errors.New("异常信息") err := fmt.Errorf("错误信息: %v", "异常信息")
|
捕捉异常
1 2 3 4 5 6 7 8 9 10 11 12 13
| func tryInt(s string) { i, err := strconv.ParseInt(s, 0, 64) if err == nil { fmt.Println("Value:", i) } else { fmt.Println("Error:", err) } }
func main() { tryInt("123123") tryInt("123a123") }
|
1 2
| Value: 123123 Error: strconv.ParseInt: parsing "123a123": invalid syntax
|
异常嵌套
从 go 1.13 开始支持异常嵌套,引入了 errors.Is、errors.As、errors.Unwrap、fmt.Errorf 中的 %w。
1 2 3 4 5 6 7 8 9 10 11 12
| func main() { err1 := errors.New("error 1") err2 := errors.New("error 2") err3 := errors.New("error 3") err4 := errors.New("error 1") fmt.Errorf("%w: %w", err3, err1) fmt.Println(errors.Is(err1)) fmt.Println(errors.Is(err2)) fmt.Println(errors.Is(err3)) fmt.Println(errors.Is(err4)) }
|
errors.Is 的本质是不断调用 errors.Unwrap 并检测错误信息指针是否一致,因此第 10 行为 false。
系统错误可以使用 fs.xxx 找到对应的系统错误。
panic() 与 recover()
Golang 中没有 try-catch 语句,而使用 panic-recover 进行异常控制,两者具有一定区别。
func panic(v any)(崩溃)将产生异常,如果异常不被捕捉,将抛出命令行错误并终止程序。
func recover() any(恢复)用于捕捉异常,只在 defer 函数中生效。
- 非不可挽回的错误,建议使用
error 而非 panic。
造成 panic 的场景
- 索引或指针无效或越界
- 向关闭的
channel 发送消息
- 类型断言(不获取
ok)
- 用户发送
panic
捕捉与处理异常
1 2 3 4 5 6 7 8 9
| defer func() { err := recover() if err != nil { fmt.Printf("捕捉异常: %T %v\n", err, err) } }()
panic("抛出异常")
|
- 由于
recover() 只在 defer 函数中生效,只能在函数结束处理异常。