> 技术文档 > 第一章:Go语言基础入门之流程控制

第一章:Go语言基础入门之流程控制


Go 语言流程控制:深度掌握 if, for, switch 及跳转语句

Go 语言以其简洁、高效和并发特性而闻名,但其强大功能的基础之一,就是清晰且富有表现力的流程控制语句。掌握这些语句,如同掌握了程序执行的“大脑”,能够精确地引导代码的走向,实现复杂的业务逻辑。

本文将深入探讨 Go 语言的流程控制语句,包括条件判断 if-else,唯一的循环语句 for(及其多种形态),分支选择 switch(及其高级用法),以及跳转语句 breakcontinuegoto。通过详细的解释和丰富的代码示例,帮助您彻底理解并高效运用它们。

1. 条件判断:if-else

if-else 语句是程序中最基本的决策工具,用于根据条件执行不同的代码块。Go 语言的 if-else 语法简洁明了,并且有一些独特的特性。

基本语法:

if condition { // 当 condition 为 true 时执行} else { // 当 condition 为 false 时执行}
  • 特点:
    • 条件表达式 condition 不需要 用括号包围。
    • 代码块 {} 必须 存在,即使只有一行代码。这是 Go 语言强制的风格,旨在避免常见的错误。
    • else 语句是可选的。

示例 1:基本 if-else

package mainimport \"fmt\"func main() { score := 85 if score >= 60 { fmt.Println(\"恭喜,您通过了考试!\") } else { fmt.Println(\"很遗憾,您未能通过考试。\") } // 输出: 恭喜,您通过了考试!}

if 语句中的短声明 (Short Statement):

Go 语言允许在 if 关键字后放置一个可选的短声明(也称为初始化语句)。这个声明在条件判断之前执行,并且由它声明的变量的作用域仅限于 ifelse 块内部。这在处理函数返回值(特别是错误)时非常有用。

语法:

if statement; condition { // ...} else { // ...}

示例 2:if 带短声明 (常见于错误处理)

package mainimport (\"fmt\"\"strconv\")func main() { // 尝试将字符串转换为整数 if num, err := strconv.Atoi(\"123a\"); err != nil { // num 和 err 的作用域仅限于 if/else fmt.Printf(\"转换失败: %v\\n\", err) } else { fmt.Printf(\"转换成功,结果是: %d\\n\", num*2) } // 输出: 转换失败: strconv.Atoi: parsing \"123a\": invalid syntax // 另一个成功的例子 if num, err := strconv.Atoi(\"456\"); err != nil { fmt.Printf(\"转换失败: %v\\n\", err) } else { fmt.Printf(\"转换成功,结果是: %d\\n\", num*2) } // 输出: 转换成功,结果是: 912}

else if 链:

当有多个互斥的条件需要判断时,可以使用 else if 结构。

示例 3:else if 链

package mainimport \"fmt\"func main() { temperature := 28 if temperature < 0 { fmt.Println(\"非常冷,请注意保暖!\") } else if temperature >= 0 && temperature < 15 { fmt.Println(\"有点凉,请加件衣服。\") } else if temperature >= 15 && temperature < 25 { fmt.Println(\"温度适中,感觉舒适。\") } else { // temperature >= 25 fmt.Println(\"天气炎热,请注意防暑!\") } // 输出: 天气炎热,请注意防暑!}

2. 循环语句:for

Go 语言中只有 for 这一种循环语句。但它通过不同的形式,能够实现其他语言中 whiledo-while 以及 for-each 等循环的功能。

2.1. 经典 for 循环 (三段式)

这是最常见的 for 循环形式,与 C/C++/Java 中的 for 循环类似。

语法:

for initialization; condition; post { // 循环体}
  • initialization: 循环开始前执行一次,通常用于初始化循环变量。
  • condition: 每次迭代前判断,如果为 true 则继续循环,否则退出。
  • post: 每次迭代后执行,通常用于更新循环变量。

示例 1:经典 for 循环

package mainimport \"fmt\"func main() { fmt.Println(\"经典 for 循环:\") for i := 0; i < 5; i++ { fmt.Printf(\"当前数字是: %d\\n\", i) } // 输出: // 当前数字是: 0 // 当前数字是: 1 // 当前数字是: 2 // 当前数字是: 3 // 当前数字是: 4}

2.2. for 模拟 while 循环 (只有条件)

当只需要一个条件来控制循环时,可以省略 initializationpost 部分。

语法:

for condition { // 循环体}

示例 2:for 模拟 while 循环

package mainimport \"fmt\"func main() { fmt.Println(\"\\nfor 模拟 while 循环:\") count := 0 for count < 3 { fmt.Printf(\"当前计数是: %d\\n\", count) count++ } // 输出: // 当前计数是: 0 // 当前计数是: 1 // 当前计数是: 2}

2.3. 无限循环 (永真循环)

当所有部分都被省略时,for 循环会创建一个无限循环。通常需要配合 break 语句来跳出循环。

语法:

for { // 循环体}

示例 3:无限循环

package mainimport (\"fmt\"\"time\")func main() { fmt.Println(\"\\n无限循环 (带 break):\") i := 0 for { fmt.Printf(\"这是第 %d 次循环\\n\", i) time.Sleep(100 * time.Millisecond) // 暂停一小会儿 i++ if i >= 3 { break // 当 i 达到 3 时跳出循环 } } fmt.Println(\"循环结束。\") // 输出: // 这是第 0 次循环 // 这是第 1 次循环 // 这是第 2 次循环 // 循环结束。}

2.4. for-range 循环 (迭代器)

for-range 是 Go 语言中遍历数组 (array)、切片 (slice)、字符串 (string)、映射 (map) 和通道 (channel) 的强大且惯用的方式。

语法:

for index, value := range collection { // 使用 index 和 value}
  • index: 迭代到的元素的索引(对于数组/切片/字符串)或键(对于映射)。
  • value: 迭代到的元素的值。
  • 您可以省略 indexvalue,如果你不需要它们,但必须保留下划线 _ 来忽略不使用的变量。

示例 4:for-range 遍历切片

package mainimport \"fmt\"func main() { numbers := []int{10, 20, 30, 40, 50} fmt.Println(\"\\nfor-range 遍历切片:\") for i, num := range numbers { fmt.Printf(\"索引: %d, 值: %d\\n\", i, num) } // 输出: // 索引: 0, 值: 10 // 索引: 1, 值: 20 // 索引: 2, 值: 30 // 索引: 3, 值: 40 // 索引: 4, 值: 50}

示例 5:for-range 遍历映射 (Map)

package mainimport \"fmt\"func main() { fruits := map[string]string{ \"apple\": \"red\", \"banana\": \"yellow\", \"grape\": \"purple\", } fmt.Println(\"\\nfor-range 遍历映射:\") for fruit, color := range fruits { fmt.Printf(\"%s 是 %s 的\\n\", fruit, color) } // 输出 (顺序可能不固定): // grape 是 purple 的 // apple 是 red 的 // banana 是 yellow 的}

示例 6:for-range 遍历字符串

遍历字符串时,for-range 会按 Unicode 字符(rune)进行迭代,而不是字节。

package mainimport \"fmt\"func main() { greeting := \"你好 Go!\" // 包含中文和空格 fmt.Println(\"\\nfor-range 遍历字符串:\") for i, r := range greeting { fmt.Printf(\"索引: %d, Unicode 码点 (rune): %U, 字符: %c\\n\", i, r, r) } // 输出: // 索引: 0, Unicode 码点 (rune): U+4F60, 字符: 你 // 索引: 3, Unicode 码点 (rune): U+597D, 字符: 好 // 索引: 6, Unicode 码点 (rune): U+0020, 字符:  // 索引: 7, Unicode 码点 (rune): U+0047, 字符: G // 索引: 8, Unicode 码点 (rune): U+006F, 字符: o // 索引: 9, Unicode 码点 (rune): U+0021, 字符: !}

注意: 对于字符串,i 表示字符的起始字节索引,而不是字符计数。

3. 分支选择:switch

switch 语句提供了一种简洁的方式来根据一个表达式的值执行不同的代码块。Go 语言的 switch 比其他许多语言的 switch 更加灵活和强大。

3.1. 基本 switch 语句

语法:

switch expression {case value1: // 当 expression == value1 时执行case value2, value3: // 多个值 // 当 expression == value2 或 expression == value3 时执行default: // 当没有匹配的 case 时执行 (可选)}
  • 特点:
    • expression 是可选的,如果省略,switch 会根据 case 中的布尔表达式求值。
    • 每个 case 块的末尾不需要 break 语句,Go 默认情况下 case 不会 “fall through”(穿透)。
    • 可以为单个 case 指定多个值,用逗号分隔。
    • default 语句是可选的,当所有 case 都不匹配时执行。

示例 1:基本 switch 语句

package mainimport \"fmt\"func main() { day := \"Wednesday\" fmt.Println(\"基本 switch 语句:\") switch day { case \"Monday\": fmt.Println(\"今天是星期一,新的开始!\") case \"Tuesday\", \"Wednesday\", \"Thursday\": // 多个值 fmt.Println(\"今天是工作日,努力工作。\") case \"Friday\": fmt.Println(\"今天是星期五,周末快到了!\") case \"Saturday\", \"Sunday\": fmt.Println(\"今天是周末,尽情放松。\") default: fmt.Println(\"这不是一个有效的星期几。\") } // 输出: 今天是工作日,努力工作。}

3.2. switch 不带表达式 (Clean if-else if 替代品)

switch 后面不带任何表达式时,它会求值每个 case 语句本身作为一个布尔条件。第一个求值为 truecase 将被执行。这使得 switch 成为 if-else if 链的一种更清晰的替代方案。

语法:

switch { // 无表达式case condition1: // 当 condition1 为 true 时执行case condition2: // 当 condition2 为 true 时执行default: // ...}

示例 2:switch 不带表达式

package mainimport \"fmt\"func main() { age := 25 fmt.Println(\"\\nswitch 不带表达式:\") switch { case age < 18: fmt.Println(\"未成年人。\") case age >= 18 && age < 60: fmt.Println(\"成年人。\") default: // age >= 60 fmt.Println(\"老年人。\") } // 输出: 成年人。 // 也可以在 case 中使用短声明 fmt.Println(\"\\nswitch 不带表达式 (带短声明):\") length := 10 switch { case length > 0 && length < 5: fmt.Println(\"短文本。\") case length >= 5 && length < 15: fmt.Println(\"中等文本。\") default: fmt.Println(\"长文本。\") } // 输出: 中等文本。}

3.3. fallthrough 关键字 (穿透)

尽管 Go 的 switch 默认不穿透,但您可以使用 fallthrough 关键字强制执行下一个 case 块的代码,而不检查其条件。这在实践中很少使用,并且通常被视为不推荐的模式,因为它可能导致代码难以理解。

语法:

switch expression {case value1: // ... fallthrough // 执行下一个 casecase value2: // ...}

示例 3:fallthrough 关键字

package mainimport \"fmt\"func main() { grade := \"B\" fmt.Println(\"\\n使用 fallthrough:\") switch grade { case \"A\": fmt.Println(\"优秀!\") fallthrough // 会执行下一个 case case \"B\": fmt.Println(\"良好。\") // 这里没有 fallthrough,所以不会继续执行下一个 case case \"C\": fmt.Println(\"及格。\") fallthrough // 会执行下一个 case default: fmt.Println(\"需要努力。\") } // 输出: // 良好。 // 注意:如果 grade 是 \"A\",则会输出 \"优秀!\" 和 \"良好。\" // 如果 grade 是 \"C\",则会输出 \"及格。\" 和 \"需要努力。\"}

3.4. 类型 switch (Type Switch)

类型 switch 用于判断一个接口变量所持有的值的具体类型。这在处理多态性时非常有用。

语法:

switch variable.(type) {case TypeA: // 当 variable 的实际类型是 TypeA 时case TypeB: // 当 variable 的实际类型是 TypeB 时default: // ...}

示例 4:类型 switch

package mainimport \"fmt\"func describe(i interface{}) { fmt.Printf(\"参数类型是 %T,值是 \", i) switch v := i.(type) { // v 会是具体类型的值 case int: fmt.Printf(\"一个整数,值为 %d\\n\", v) case string: fmt.Printf(\"一个字符串,值为 \\\"%s\\\"\\n\", v) case bool: fmt.Printf(\"一个布尔值,值为 %t\\n\", v) case chan int: fmt.Printf(\"一个 int 通道\\n\") default: fmt.Printf(\"未知类型\\n\") }}func main() { fmt.Println(\"\\n类型 switch:\") describe(10) // 输出: 参数类型是 int,值是一个整数,值为 10 describe(\"Hello Go\") // 输出: 参数类型是 string,值是一个字符串,值为 \"Hello Go\" describe(true) // 输出: 参数类型是 bool,值是一个布尔值,值为 true describe(nil) // 输出: 参数类型是 ,值是 未知类型 describe([]float64{1.1, 2.2}) // 输出: 参数类型是 []float64,值是 未知类型}

4. 跳转语句:breakcontinue

breakcontinue 是控制循环和 switch 流程的关键字。

4.1. break 语句

break 语句用于提前终止 for 循环或 switch 语句的执行。

  • for 循环中,break 会立即退出最内层的循环。
  • switch 语句中,break 会立即退出 switch 块。

示例 1:breakfor 循环中

package mainimport \"fmt\"func main() { fmt.Println(\"break 在 for 循环中:\") for i := 0; i < 10; i++ { if i == 5 { fmt.Println(\"检测到 5,退出循环!\") break // 退出当前 for 循环 } fmt.Printf(\"当前数字: %d\\n\", i) } fmt.Println(\"循环后的代码。\") // 输出: // 当前数字: 0 // 当前数字: 1 // 当前数字: 2 // 当前数字: 3 // 当前数字: 4 // 检测到 5,退出循环! // 循环后的代码。}

示例 2:breakswitch 中 (尽管通常不需要)

Go 语言的 switch 默认不穿透,所以 breakswitch 中通常不是必需的,除非你想在执行某个 case 后强制退出 switch 之外的某个外层结构(如带标签的 switch)。

package mainimport \"fmt\"func main() { fmt.Println(\"\\nbreak 在 switch 中 (通常不需要):\") myValue := 2 switch myValue { case 1: fmt.Println(\"One\") // break // 即使有 break,行为也一样 case 2: fmt.Println(\"Two\") break // 这里的 break 是多余的,但不会报错 case 3: fmt.Println(\"Three\") default: fmt.Println(\"Other\") } // 输出: Two}

4.2. continue 语句

continue 语句用于跳过 for 循环当前迭代的剩余部分,并立即开始下一次迭代。

示例 3:continuefor 循环中

package mainimport \"fmt\"func main() { fmt.Println(\"\\ncontinue 在 for 循环中:\") for i := 0; i < 5; i++ { if i%2 == 0 { // 如果是偶数 fmt.Printf(\"跳过偶数: %d\\n\", i) continue // 跳过当前迭代的剩余部分,进入下一次迭代 } fmt.Printf(\"处理奇数: %d\\n\", i) } // 输出: // 跳过偶数: 0 // 处理奇数: 1 // 跳过偶数: 2 // 处理奇数: 3 // 跳过偶数: 4}

4.3. 标签 (Labels) 与 break/continue

Go 语言允许为 for 循环、switchselect 语句定义标签。这使得 breakcontinue 可以跳出或继续执行被标签标记的特定外部循环,而不是仅仅是最内层的循环。

语法:

LabelName: for ... { // ... if condition { break LabelName // 跳出 LabelName 标记的循环 // continue LabelName // 跳到 LabelName 标记的循环的下一次迭代 } }

示例 4:带标签的 break (跳出多层循环)

package mainimport \"fmt\"func main() { fmt.Println(\"\\n带标签的 break:\")OuterLoop: // 定义一个标签 for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { fmt.Printf(\"i: %d, j: %d\\n\", i, j) if i == 1 && j == 1 { fmt.Println(\"在 i=1, j=1 时跳出外层循环。\") break OuterLoop // 跳出 OuterLoop 标记的循环 } } } fmt.Println(\"所有循环结束。\") // 输出: // i: 0, j: 0 // i: 0, j: 1 // i: 0, j: 2 // i: 1, j: 0 // i: 1, j: 1 // 在 i=1, j=1 时跳出外层循环。 // 所有循环结束。}

示例 5:带标签的 continue (跳过外层循环的当前迭代)

package mainimport \"fmt\"func main() { fmt.Println(\"\\n带标签的 continue:\")OuterLoop2: for i := 0; i < 3; i++ { for j := 0; j < 3; j++ { if i == 1 && j == 1 { fmt.Println(\"在 i=1, j=1 时跳过外层循环的当前迭代。\") continue OuterLoop2 // 跳过 OuterLoop2 的当前迭代 (i=1),直接进入 i=2 的迭代 } fmt.Printf(\"i: %d, j: %d\\n\", i, j) } } fmt.Println(\"所有循环结束。\") // 输出: // i: 0, j: 0 // i: 0, j: 1 // i: 0, j: 2 // i: 1, j: 0 // 在 i=1, j=1 时跳过外层循环的当前迭代。 // i: 2, j: 0 // i: 2, j: 1 // i: 2, j: 2 // 所有循环结束。}

5. goto 语句

goto 语句是一个无条件跳转语句,它将程序的执行流直接转移到由标签(label)定义的位置。

语法:

goto LabelName// ...LabelName: // ...

特点与建议:

  • goto 语句在现代编程中普遍不推荐使用,因为它会使代码的逻辑变得复杂、难以阅读和维护,容易导致“意大利面条式代码”。
  • 在 Go 语言中,goto 只能跳转到函数内部的标签,不能跨函数跳转。
  • 不能跳过变量的声明。
  • 通常有更清晰、更结构化的替代方案(如使用 breakcontinuereturn 或良好的函数设计)。

示例:goto 的简单使用 (不推荐)

package mainimport \"fmt\"func main() { fmt.Println(\"\\ngoto 语句 (不推荐使用):\") i := 0Start: // 定义标签 fmt.Printf(\"当前 i 的值是: %d\\n\", i) i++ if i < 3 { goto Start // 跳转到 Start 标签 } fmt.Println(\"循环结束。\") // 输出: // 当前 i 的值是: 0 // 当前 i 的值是: 1 // 当前 i 的值是: 2 // 循环结束。}

在极少数情况下,goto 可能会用于简化深度嵌套的错误处理逻辑,但这通常可以通过 defer 语句或将代码重构为更小的函数来更好地实现。

总结

Go 语言的流程控制语句简洁而强大。

  • if-else 提供了清晰的条件判断,其短声明特性在错误处理中尤其方便。
  • for 是 Go 唯一的循环语句,但它通过灵活的语法涵盖了所有常见的循环模式,尤其是 for-range,极大地简化了集合的遍历。
  • switch 语句非常灵活,默认不穿透的设计减少了错误,无表达式的 switch 更是替代复杂 if-else if 链的优雅方式,而类型 switch 则增强了接口处理的能力。
  • breakcontinue 提供对循环内部流程的精细控制,配合标签可以处理多层嵌套的情况。
  • goto 应该尽可能避免,因为它会破坏代码的结构化和可读性(除非高手,否则不建议使用)。