跳到主要内容

流程控制

if else(分支结构)

if 判断格式:

if 条件 {
分支
} else if 条件 {
分支
} else {
分支
}

if 条件前还可以添加变量赋值, 在根据变量值判断

if score := 70; score > 90 {  // score 变量的作用域只在if判断内, if外无法调用
fmt.Println("A")
}else if score > 80 {
fmt.Println("B")
}else if score >= 60 {
fmt.Println("C")
}else {
fmt.Println("D")
}

switch case default

如果分支条件过多的话, 用 if-else 可能会增加阅读的困难, 此时可以通过 switch 语句来实现分支控制。

package main

import "fmt"

func main() {
tag := 2

switch tag { // switch 指定了分支控制的判断依据变量 tag 的值。
case 1: // 在以下分支中, 利用 tag 值分别后下列数值比较, 匹配成功就打印对应中文字符串。
fmt.Println("高")
case 2:
fmt.Println("中")
case 3:
fmt.Println("中")
default: // default是上述所有条件都不匹配时的默认操作。
fmt.Println("无")
}
}

每个 switch 语句只能有一个 default 分支。

一个分支可以有多个值, 通过逗号,分隔

switch n := 2; n {
case 1, 3, 5, 7:
fmt.Println("奇数")
case 2, 4, 6:
fmt.Println("偶数")
default:
fmt.Println("n")
}

分支也可以使用表达式, 这时 switch 后不需要判断变量了

n := 2
switch {
case n =< 1:
fmt.Println("n不大于1")
case n > 2:
fmt.Println("n大于2")
default:
fmt.Println("n等于2")
}

for(循环结构)

Go 语言中循环类型都由 for 来完成。

for 循环格式:

for 初始语句; 条件表达式; 结束语句{
循环体语句
}

for 循环中条件表达式返回 true 时循环体不停的进行循环, 直到表达式返回 false 时结束循环。

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

// 初始语句可以被忽略, 但分号需要 ;
i :=0
for ; i < 10; i++ {
fmt.Println(i)
}

// 初始和结束语句都可以被省略
i := 0
for i < 10 {
fmt.Println(i)
i++
}

无限循环

for 无限循环, 条件结果永远为 true。

for {
循环体语句
}

for 循环可以通过 break, goto, return, panic 语句强制退出循环。

利用 break 跳出无限循环

当判断条件不出现在 for 关键字后, 而是在循环体中。此时 for 循环不会出现次数限制, 而结束循环则需要使用 break 关键字。

total := 0
i := 1
for {
if i > 20 { // 当 i 大于 20 后, break 跳出循环
break
}
total += i
i++
}
fmt.Println(total) // 结果 210

for-range 循环

for range 循环可以用于遍历 array(数组), slice(切片), string(字符串), channel(通道), map 的循环。

for range 循环规则

数据结构循环获取的返回值
array(数组)/slice(切片)第一个值是索引位置, 第二个值是元素
string(字符串)第一个值是索引位置, 第二个值是字符的 rune 值
map第一个值是 key, 第二个值是 value
channel只有一个值, channel 中的元素
信息

Go1.22 版本开始支持 for range 整数

for i := range 10 {
fmt.Println(i)
}

对于 string 类型, for range 是循环获得每个字符的最简洁方式。

s := "Go语言是一门简洁的编程语言"
for _, n := range s {
fmt.Println(string(n)) // n 的类型为 rune, 需要通过 string() 强制转换成字符串, 以字符串形式输出
}

递归

在循环中, 每次都执行相同的代码块, 而递归也是在不断的执行同一代码块。

// 递归累加 1~20 的值
package main

import "fmt"

func cumulate(i, total int) int {
// 判断结束条件
if i > 20 {
return total
}

total = cumulate(i+1, total)
return total + i
}

func main() {
total := 0
i := 1
total = cumulate(i, total)
fmt.Println(total)
}

在 cumulate() 的函数体中, 如果将 cumulate(i+1, total) 看作下一次调用要处理的事务, 那么 total+i 则是本层调用要处理的事务。递归调用与 for 循环的本质区别在于: 递归函数的函数体中除了能够处理 下一层调用 外, 还能够处理自己 本层调用 的自有事务; 而循环体不同, 循环体中的每次操作之间都是完全独立、并列、没有任务关联的, 单次循环只能完成循环体内的自有事务。

goto

goto 语句通过标签进行代码间的无条件跳转。goto 可以在快速跳出多层循环、避免重复退出上有一定的帮助。

在 for 循环中, 虽然利用 break 可以跳出循环, 但 break 语句只能跳出当前循环, 对于多层循环逻辑有时会显得力不从心。

例如,有一个map类型的变量,其数据类型为 map[string]int,其中的key为字符串类型,value为整型切片。要查找是否存在值为100的value元素。

package main

import "fmt"

func main() {
var m = make(map[string][]int)
m["a"] = []int{1, 2, 5}
m["b"] = []int{10, 20, 50}
m["c"] = []int{100, 200, 500}

var result = false
// 第一层循环, 遍历map中的key-value, 以获取每个切片
for _, v := range m {
// 第二层循环, 遍历每个切片的value, 循环切片元素, 判断是否有100
for _, e := range v {
if e == 100 {
result = true
}
}
}
fmt.Println("100是否存在", result)
}

当我们第一次找到100时,最终结果一定为true,此时应该立即跳出所有循环。常规方案是利用break语句,但break只能跳出当前层级的循环,即如果只在第二层循环中利用break语句,则无法结束第一层循环操作。此时,利用goto可以直接跳出所有循环。

package main

import "fmt"

func main() {
var m = make(map[string][]int)
m["a"] = []int{1, 2, 5}
m["b"] = []int{10, 20, 50}
m["c"] = []int{100, 200, 500}

var result = false
// 第一层循环, 遍历map中的key-value, 以获取每个切片
for _, v := range m {
// 第二层循环, 遍历每个切片的value, 循环切片元素, 判断是否有100
for _, e := range v {
if e == 100 {
result = true
// 若找到100,直接跳到searchEnd标签处
goto searchEnd
}
}
}
searchEnd:
fmt.Println("100是否存在", result)
}

上面 searchEnd 仅是一个代码位置的标签, 并不会进行任何实质性操作。但这个标签可以定义在当前函数的任意位置, 而且几乎没有任何机制来验证标签位置的正确性。标签的位置几乎不受语法的约束,即使有人不小心移动了标签的位置,也很难被发现。这无疑为代码带来了更大的复杂度。