流程控制

条件语句if

基本形式:

1
2
3
4
5
6
7
8
if cond1{
...
}
else if cond2{
...
}else{
...
}
特点:

  • 条件必须是布尔值或者布尔表达式,不再支持整型0/1或者其他非零值(指针等);

  • 条件表达式不需要括号,代码块必须有花括号;

最大的特点是支持初始化语句,允许在if后定义局部变量供给条件语句块使用:

1
2
3
4
5
6
7
8
9
10
11
if x:=f(); x>0{
...
}else{
...
}

if n:="abc"; x>0{
fmt.Println(n[0])
}else{
fmt.Println(n[1])
}

条件语句switch

基本形式:

1
2
3
4
5
6
7
var flag int = 0
switch flag {
case 0: flag = 1
case 1: flag = 2
case 2: flag = 3
default: flag = 4
}
基本特点:

  • case语句自带break,每个case执行完自动退出;

  • 想要拒绝自动break实现多个case的穿透,在case的最后一条语句使用fallthrough关键字

    1
    2
    3
    4
    5
    6
    7
    8
    flag := 1
    switch flag {
    case 1:
    fmt.Println("Hello")
    fallthrough //也会执行case2
    case 2:
    fmt.Println("World")
    }

  • 同样支持在switch后写初始化公式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    switch flag := 1; flag {
    case 1:
    fmt.Println("Hello")
    fallthrough //也会执行case2
    case 2:
    fmt.Println("World")
    }

    //fmt.Println(flag) //不能传递到语句外使用

  • switch可以省略变量判断,golang默认处理为switch(true),适用于那种范围判断而不是相等判断,等效于if...else语句:

    1
    2
    3
    4
    5
    6
    7
    8
    switch {
    case score >= 90:
    fmt.Println("优秀")
    case score >= 60:
    fmt.Println("及格")
    default:
    fmt.Println("不及格")
    }

switch类型萃取

switch类型萃取,支持判别接口参数的类型,注意,必须是接口参数,而不是什么变量或者函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
//ok, interface type
var i interface{} //动态类型为nil,静态类型interface{}
switch t := i.(type) {
case int:
fmt.Println("int", t)
case float32:
fmt.Println("float32", t)
case string:
fmt.Println("string", t)
case nil:
fmt.Println("isNil", t) //isNil <nil>
}

1
2
3
4
5
6
7
8
9
10
11
12
//wrong, non-interface type use .(type) is not allowed
i := 10
switch t := i.(type) { //error: i (variable of type int) is not an interface
case int:
fmt.Println("int", t)
case float32:
fmt.Println("float32", t)
case string:
fmt.Println("string", t)
case nil:
fmt.Println("isNil", t) //isNil <nil>
}

关于golang的接口和动态类型讨论见下章,我们还可以通过any能够将一般值装载到接口值里,如打印泛型的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func printType[T any](value T) {
switch t := any(value).(type) {
case int:
fmt.Println("int", t)
case float32:
fmt.Println("float32", t)
default:
fmt.Println("other", t)
}
}

func main() {
i := 10
printType(i) //int 10
}

循环语句for

for语句有若干种形式,也都比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for i:=0; i<10; i++{
...
}

nums := []int{...}
for(i, value := range nums){
...
}

//类似while方法:
for a<b{
...
}

for true{
...
}

for range注意事项

对array、slice、map的遍历,第一个参数都是索引下标,第二个参数指向元素的拷贝:

1
2
3
for i,v := range slice1{
...
}
值得注意的是,range在遍历前已经确定了指向底层数组的指针以及要遍历的长度,因此如果遍历中篡改切片的长度也是不会影响遍历:
1
2
3
4
5
6
7
func main() {
s := []int{1, 2, 3, 4, 5}
for i, v := range s {
s = s[:3] //缩短切片
fmt.Println(i, v) //仍然能打印1到5
}
}

函数

普通函数基本规则

golang的函数和C++还是有鲜明的区别,具体有以下特点:

  • 不支持函数重载,也不支持函数默认值,故同一个package内不能存在同名函数;

  • 无需声明,声明和定义一般同时给出;

  • 支持不定长参数(可与普通参数混用),也支持多返回值:

    1
    2
    3
    4
    5
    6
    7
    8
    func logf(level string, format string, args ...any) (string, string) {
    return "OK", fmt.Sprintf("[%s]"+format, append([]any{level}, args...)...) //切片可同时装入不同类型,他们都被转成any
    }

    func main() {
    notify, info := logf("INFO", "x=%d, y=%d", 2, 3)
    fmt.Println(notify, info) //OK [INFO]x=2, y=3
    }
    值得一提的是,可变参数列表可以被range遍历:
    1
    2
    3
    4
    5
    6
    7
    8
    func doAdd(args ...int) []int {
    res := make([]int, 0)
    for _, pos := range args {
    pos += 1
    res = append(res, pos)
    }
    return res //[2 3 4 5 6]
    }

  • 不支持嵌套,但支持匿名函数和闭包,嵌套是指在函数内部直接定义一个函数声明:

    1
    2
    3
    func once(){
    func twice(){...} //嵌套函数定义错误!可以使用匿名函数代替
    }

  • 支持命名返回的函数值,这个很特别,即golang允许你直接命名返回值,然后直接使用return即可,称裸返回:

    1
    2
    3
    4
    func getNum(a, b int)(sum int){
    sum = a+b
    return //sum被命名了,无需显示写return sum
    }
    但注意,如果是slice、map、chan等底层引用类型,要注意命名对象空间是否被分配和初始化;

    若返回值命名和局部变量重名,必须使用显式返回:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func getNum(a, b int) (z int) {
    z = 10
    { //独立作用域
    var z int = a + b
    return z
    }
    return
    }

    func main() {
    s := getNum(1, 2)
    fmt.Println(s) //3
    }

  • 函数的左花括号不能写到下一行,这是习惯使用C/C++分行format插件的人的噩梦:

    1
    2
    3
    4
    func once()
    { //错误!花括号不能写到这行

    }

  • slice、map、chan、指针、interface作为参数时默认均以底层指针的方式传递,修改参数会影响对象;

  • slice也能够适配可变参数,但是传参时要使用省略号展开:

    1
    2
    3
    4
    5
    6
    7
    8
    func push_front_elem[T any](dst []T, src ...T) []T {
    n := len(src)
    dst = append(dst, src...) //扩容
    copy(dst[n:], dst[:len(dst)-n])
    copy(dst[:n], src)
    return dst
    }
    s1 = push_front_elem(s1, []int{22, 33, 44}...) //OK

golang延迟defer闭包

golang允许在函数内部使用关键字defer定义延迟函数,该函数会在调用函数返回前或者panic前捕获变量值并且执行函数:

1
2
3
4
5
6
7
8
9
10
11
12
func getNum(a, b int) (z int) {
z = a + b
defer func() {
z += 10
}()

fmt.Println("z value before return:", z)
return z
}
func main() {
fmt.Println("z=", getNum(1, 2)) //
}
输出:
1
2
z value before return: 3
z= 13

这种特性可以用于对返回值的处理、资源的收尾、异常的处理或者计时的结束打印,如打印执行时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"time"
)

func getDuratiom() (z int) {
start := time.Now()
for i := range 10000 {
z += i
}
time.Sleep(1000 * time.Millisecond)

defer func() {
fmt.Printf("duration = %s, res = %d", time.Since(start), z)
}()
return
}

func main() {
getDuratiom() //duration = 1.0004287s, res = 49995000
}

注意,如果被defer修饰的闭包或者普通函数存在实参,这个实参是defer定义时就捕获的,而不是被捕获变量那样,直到执行(函数返回)时才捕获; 而且这个实参被捕获到栈中,遵循LIFO(后进先出)原则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"

func getDuratiom() {
for i := 0; i < 3; i++ {
defer fmt.Printf("%d\t", i) //普通函数捕获实参
}

for i := 3; i < 6; i++ {
defer func(v int) {
fmt.Printf("%d\t", v) //形参v
}(i) //闭包捕获实参i
}
fmt.Println("exit")
}

func main() {
getDuratiom() //duration = 1.0004287s, res = 49995000
}

输出:

1
2
exit
5 4 3 2 1 0

匿名函数

golang的匿名函数非常强大,其完全可以被认为是一个普通变量,除了C++中那种作局部变量被调用、立刻执行等用法,还可以充当函数参数、返回值、结构体参数等,见下。

基本形式

  1. 定义成匿名函数变量:

    1
    2
    3
    4
    5
    6
    7
    func test() {
    doAdd := func(a, b int) int {
    return a + b
    }

    doAdd(1, 2)
    }

  2. 立刻定义和调用, 获取执行结果:

    1
    2
    3
    4
    5
    6
    func test() {
    res := func(a, b int) int {
    return a + b
    }(1, 2)
    fmt.Println(res) //3
    }

  3. 匿名函数作为函数参数传入(类似std::function):

    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
    package main

    import "fmt"

    func test(nums []int, f func(int) int) []int {
    res := make([]int, len(nums))
    for i, pos := range nums {
    res[i] = f(pos)
    }
    return res
    }

    func main() {
    s := []int{
    1, 2, 3, 4, 5,
    }
    s = test(s, func(i int) int {
    if i%2 == 0 { //偶数翻倍,奇数清零
    i *= 2
    } else {
    i = 0
    }
    return i
    })
    fmt.Println(s) //[0 4 0 8 0]
    }

  4. 匿名函数作为返回值返回:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //返回int(int)函数类型
    func makeCounter(start int) func(end int) int {
    n := start
    return func(end int) int {
    if n < end {
    n++
    }
    return n
    }
    }

    func main() {
    counter := makeCounter(0)
    for i := 0; i < 100; i++ {
    fmt.Printf("%d ", counter(5)) //1,2,3,4,5,5,5,5,5,...
    }
    }