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) // A1
c1 <- "Thread1 is ready."
}

func Thread2() {
time.Sleep(time.Millisecond * 2100) // A1
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) // A2
}
}
}

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. 先后是随机的,因为到第五次循环(2500msThread1Thread2 都准备就绪。
  • 如果将 A1 处参数修改为 2000,则 Thread1 is ready. 在先概率更高,因为 Thread1Thread2 理论上恰好同时在第四次循环(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++
// }
for i < 10 {
fmt.Println(i)
i++
}
1
2
3
4
5
6
7
8
// for ;; {
// fmt.Println("Running...")
// time.Sleep(time.Millisecond * 500)
// }
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)
}
  • 数组、stringslicemapchannel 等都可以使用 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
defer <functionCall>
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 语句怪用

  • 谈及 defer 原理,defer 语句将延迟操作压栈,压栈数据包括:函数名、函数参数(临时)地址。后面的所有怪用都基于这一原理,defer 语句可以修改和访问本该已卸载的内存。defer 压栈后实际对地址又进行了一次引用,因此 Golang 的垃圾回收机制(GC)实际没有卸载这些内存。

  • 修改函数返回值。该案例中使用匿名返回值得不到相同效果,个人猜测是因为匿名返回值返回时进行了拷贝。

1
2
3
4
5
func Demo() (s string) {
s = "Edit by Function"
defer func() { s = "Edit by Defer" }
return s
}
1
Edit by Defer
  • 循环延迟返回值相同。该案例中,Demo1defer 访问 for 循环定义的 i,在程序结束时访问其值为 3Demo2defer 访问每次循环体内定义的 i,可以认为是循环变量 i 的快照版本。
1
2
3
4
5
func Demo1() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
1
2
3
3
3
3
1
2
3
4
5
6
func Demo2() {
for i := 0; i < 3; i++ {
i := i
defer fmt.Println(i)
}
}
1
2
3
2
1
0

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("异常信息")  // error: "异常信息"
err := fmt.Errorf("错误信息: %v", "异常信息") // errors.New(fmt.Sprintf(...))

捕捉异常

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.Iserrors.Aserrors.Unwrapfmt.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) // %w 称 Wrap, 将多种错误嵌套, 与 Unwrap 对应
fmt.Println(errors.Is(err1)) // true
fmt.Println(errors.Is(err2)) // false
fmt.Println(errors.Is(err3)) // true
fmt.Println(errors.Is(err4)) // false

}

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)
}
}()
// defer recover() 无效
// defer func() { recover() }() 有效
panic("抛出异常")
1
捕捉异常: string 抛出异常
  • 由于 recover() 只在 defer 函数中生效,只能在函数结束处理异常。