znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第04章 - 流程控制与函数

本章讲解 Go 的流程控制语句(条件、循环、分支)以及函数的定义与高级用法,包括多返回值、可变参数、闭包和 defer 机制。

4.1 条件语句 if

Go 的 if 语句不需要括号包裹条件,但花括号是必须的:

score := 85
if score >= 90 {
    fmt.Println("优秀")
} else if score >= 60 {
    fmt.Println("及格")
} else {
    fmt.Println("不及格")
}

4.1.1 带初始化语句的 if

if 可以在条件前包含一个初始化语句,该语句声明的变量作用域仅限于 if-else 块内,这是 Go 非常地道的写法:

if value, err := strconv.Atoi("123"); err == nil {
    fmt.Println("转换成功:", value)
} else {
    fmt.Println("转换失败:", err)
}
// value 和 err 在这里已不可见

这种模式将变量作用域限制在最小范围,是处理错误的标准方式。

4.2 循环语句 for

Go 只有一种循环关键字 for,但它能表达所有循环形式。

4.2.1 经典三段式

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

4.2.2 类 while 循环

省略初始化和后置语句,for 就相当于 while

n := 1
for n < 100 {
    n *= 2
}

4.2.3 无限循环

for {
    // 永久循环,通常配合 break 或 return 退出
    if done {
        break
    }
}

4.2.4 range 遍历

range 用于遍历数组、切片、字符串、map 和 channel:

nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("索引 %d = %d\n", index, value)
}

// 只要索引
for index := range nums {
    fmt.Println(index)
}

// 只要值,用下划线忽略索引
for _, value := range nums {
    fmt.Println(value)
}

// 遍历 map
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
    fmt.Println(key, val)
}

4.3 跳转语句

  • break:跳出当前循环或 switch
  • continue:跳过本次循环,进入下一次迭代。
  • goto:跳转到指定标签(极少使用)。
  • 标签(label):配合 break/continue 可跳出多层嵌套循环:
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i*j > 2 {
            break outer // 直接跳出外层循环
        }
    }
}

4.4 分支语句 switch

Go 的 switch 比 C 更强大,且默认不需要 break——每个 case 执行完自动跳出。

switch day {
case "Sat", "Sun": // 一个 case 可匹配多个值
    fmt.Println("周末")
default:
    fmt.Println("工作日")
}

4.4.1 表达式 switch

switch 后可以不跟变量,此时相当于 if-else 链:

switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
default:
    fmt.Println("C")
}

4.4.2 fallthrough

若希望继续执行下一个 case(C 语言的默认行为),可使用 fallthrough

switch n {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two") // 当 n==1 时也会执行
}

4.4.3 类型 switch

用于判断接口的动态类型(详见第 6 章):

switch v := i.(type) {
case int:
    fmt.Println("整数:", v)
case string:
    fmt.Println("字符串:", v)
default:
    fmt.Println("未知类型")
}

4.5 函数定义

函数使用 func 关键字定义,基本语法为 func 函数名(参数列表) 返回值类型 { 函数体 }

func add(a int, b int) int {
    return a + b
}

// 相同类型的参数可合并书写
func add(a, b int) int {
    return a + b
}

4.5.1 多返回值

Go 函数可以返回多个值,这是其一大特色,广泛用于”返回结果 + 错误”的模式:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

result, err := divide(10, 2)
if err != nil {
    fmt.Println("错误:", err)
    return
}
fmt.Println("结果:", result)

4.5.2 命名返回值

返回值可以命名,相当于在函数开头声明了这些变量,return 时可不带参数(裸返回):

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // 裸返回,返回 x 和 y
}

命名返回值能提升可读性,但裸返回在长函数中会降低可读性,应谨慎使用。

4.5.3 可变参数

参数类型前加 ... 表示可变参数,函数内部将其视为切片:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3)         // 6
sum(1, 2, 3, 4, 5)   // 15

// 将切片展开传入
s := []int{1, 2, 3}
sum(s...)

4.6 函数是一等公民

在 Go 中,函数是一等公民(first-class citizen),可以赋值给变量、作为参数传递、作为返回值返回。

// 函数类型变量
var op func(int, int) int
op = add
fmt.Println(op(3, 4))

// 函数作为参数(回调)
func apply(a, b int, fn func(int, int) int) int {
    return fn(a, b)
}

4.6.1 匿名函数与闭包

匿名函数可以捕获外部变量,形成闭包:

func counter() func() int {
    count := 0
    return func() int {
        count++       // 捕获并修改外部变量
        return count
    }
}

c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3

每次调用 counter() 都会创建一个独立的 count 变量,闭包持有对它的引用。

4.7 defer 延迟执行

defer 用于延迟执行一个函数调用,被延迟的调用会在外层函数返回前执行,常用于资源释放、解锁、关闭文件等场景:

func readFile(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close() // 函数返回前自动关闭文件

    // ... 处理文件 ...
    return nil
}

4.7.1 defer 的执行顺序

多个 defer后进先出(LIFO)的栈顺序执行:

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}
// 输出: 2 1 0

4.7.2 defer 参数的求值时机

defer 语句的参数在 defer 出现时立即求值,而非执行时:

func example() {
    i := 0
    defer fmt.Println("defer 中的 i:", i) // 此时 i 为 0
    i = 100
    // 输出: defer 中的 i: 0
}

defer 还能配合命名返回值修改返回结果,这是实现 recover 错误恢复的关键(详见第 7 章)。

4.8 本章小结

本章学习了 Go 的流程控制(ifforswitch)和函数。Go 用单一的 for 表达所有循环,switch 默认自动 break 且支持类型判断。函数支持多返回值、可变参数、闭包,并通过 defer 优雅地管理资源释放。这些特性共同构成了 Go 简洁而强大的控制流。

下一章我们将深入复合数据类型:数组、切片、map 和结构体。