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
函数中生效,只能在函数结束处理异常。