文章目录
- 0、唠唠叨叨
- 1、分支结构(if / else)
- 1.1、标准写法
- 1.2、特殊写法
- 2、循环结构(for)
- 2.1、标准循环
- 2.2、无限循环
- 2.3、for 中的初始语句(开始循环时执行的语句)
- 2.4、for 中的结束语句(循环结束时执行的语句)
- 2.5、for 中的条件表达式(控制是否循环的开关)
- 2.5.1、结束循环时带可执行语句的无限循环
- 2.5.2、无限循环
- 2.5.3、只有一个循环条件的循环
- 3、键值循环结构(for range)
- 3.1、标准循环
- 3.2、遍历数组、切片(获得索引和值)
- 3.3、遍历字符串(获得字符)
- 3.4、遍历 map(获得 map 的键和值)
- 3.5、遍历通道 channel(接收通道数据)
- 3.6、在遍历中选择希望获得的变量
- 4、分支结构(switch / case)
- 4.1、基本写法
- 4.2、一分支多值
- 4.3、分支表达式
- 4.4、跨越 case 的 fallthrough(兼容C语言的 case 设计)
- 5、跳转结构(break / continue / goto)
- 5.1、跳出制定循环 break
- 5.2、继续下一次循环 continue
- 5.3、跳转到指定的标签 goto
- 5.3.1、使用 goto 退出多层循环
- 5.3.2、使用 goto 集中处理错误
- 6、示例
- 6.1、九九乘法表
- 6.2、冒泡排序
- 6.3、二分查找
- 6.4、简易聊天机器人
0、唠唠叨叨
流程控制可以说是一门语言的经脉了。Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。
本章主要介绍 Go 语言中的基本流程控制语句,包括分支语句(if 和 switch)、循环(for)和跳转(goto)语句。另外,还有循环控制语句(break 和 continue),break 的功能是中断循环或者跳出 switch 判断,continue 的功能是继续 for 的下一个循环。
1、分支结构(if / else)
1.1、标准写法
标准格式:
if condition1 {// do something
} else if condition2 {// do something else
}else {// catch-all or default
}
无效格式:
if x{
}
else { // 无效的
}
1.2、特殊写法
将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。
if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,例如:
if err := Connect(); err != nil {fmt.Println(err)return
}
Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。
在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。
2、循环结构(for)
Go语言中的循环语句只支持 for 关键字,而不支持
while 和 do-while 结构。
2.1、标准循环
sum := 0
for i := 0; i < 10; i++ {sum += i
}
2.2、无限循环
sum := 0
for {sum++if sum > 100 {break}
}
2.3、for 中的初始语句(开始循环时执行的语句)
初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其作用域将被局限在这个 for 的范围内。初始语句可以被忽略,但是初始语句之后的分号必须要写,例如:
step := 2
for ; step > 0; step-- {fmt.Println(step)
}
这段代码将 step 放在 for 的前面进行初始化,for 中没有初始语句,此时 step 的作用域就比在初始语句中声明 step 要大。
2.4、for 中的结束语句(循环结束时执行的语句)
在结束每次循环前执行的语句,如果循环被 break、goto、return、panic 等语句强制退出,结束语句不会被执行。
2.5、for 中的条件表达式(控制是否循环的开关)
2.5.1、结束循环时带可执行语句的无限循环
下面代码忽略条件表达式,但是保留结束语句,例如:
var i int
for ; ; i++ {if i > 10 {break}
}
2.5.2、无限循环
上面的代码还可以改写为更美观的写法。
var i int
for {if i > 10 {break}i++
}
无限循环在收发处理中较为常见,但需要无限循环有可控的退出方式来结束循环。
2.5.3、只有一个循环条件的循环
满足条件表达式时持续循环,否则结束循环。
var i int
for i <= 10 {i++
}
上述代码其实类似于其他编程语言中的 while,在 while 后添加一个条件表达式。
3、键值循环结构(for range)
3.1、标准循环
在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道(channel),for range 语法上类似于其它语言中的 foreach 语句,一般形式为:
for key, val := range coll { // for pos, char := range str {...
}
一个字符串是 Unicode 编码的字符(或称之为 rune )集合,因此也可以用它来迭代字符串。每个 rune 字符和索引在 for range 循环中是一一对应的,它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。
通过 for range 遍历的返回值有一定的规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- 通道(channel)只返回通道内的值。
3.2、遍历数组、切片(获得索引和值)
在遍历代码中,key 和 value 分别代表切片的下标及下标对应的值,数组也是类似的遍历方法:
for key, value := range []int{1, 2, 3, 4} {fmt.Printf("key:%d value:%d\n", key, value)
}
代码输出如下:
key:0 value:1
key:1 value:2
key:2 value:3
key:3 value:4
3.3、遍历字符串(获得字符)
var str = "hello 你好"
for key, value := range str {fmt.Printf("key:%d value:0x%x\n", key, value)
}
代码输出如下:
key:0 value:0x68
key:1 value:0x65
key:2 value:0x6c
key:3 value:0x6c
key:4 value:0x6f
key:5 value:0x20
key:6 value:0x4f60
key:9 value:0x597d
代码中的变量 value,实际类型是 rune 类型,以十六进制打印出来就是字符的编码。
3.4、遍历 map(获得 map 的键和值)
m := map[string]int{"hello": 100,"world": 200,
}
for key, value := range m {fmt.Println(key, value)
}
代码输出如下:
hello 100
world 200
对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。
3.5、遍历通道 channel(接收通道数据)
c := make(chan int)
go func() {c <- 1c <- 2c <- 3close(c)
}()
for v := range c {fmt.Println(v)
}
结果:
3.6、在遍历中选择希望获得的变量
m := map[string]int{"hello": 100,"world": 200,
}
for _, value := range m { // "_"可以理解为一种占位符,匿名变量本身不会进行空间分配,也不会占用一个变量的名字fmt.Println(value)
}
代码输出如下:
100
200
再看一个匿名变量的例子:
for key, _ := range []int{1, 2, 3, 4} {fmt.Printf("key:%d \n", key)
}
代码输出如下:
key:0
key:1
key:2
key:3
4、分支结构(switch / case)
Go语言的 switch 表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true
进行匹配,因此,可以将 if else-if else 改写成一个 switch。
4.1、基本写法
Go语言改进了 switch 的语法设计,case 与 case 之间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行。每个 switch 只能有一个 default 分支。例如:
var a = "hello"
switch a {
case "hello":fmt.Println(1)
case "world":fmt.Println(2)
default:fmt.Println(0)
}
4.2、一分支多值
不同的 case 表达式使用逗号分隔。
var a = "mum"
switch a {
case "mum", "daddy":fmt.Println("family")
}
4.3、分支表达式
var r int = 11
switch {
case r > 10 && r < 20:fmt.Println(r)
}
4.4、跨越 case 的 fallthrough(兼容C语言的 case 设计)
var s = "hello"
switch {
case s == "hello":fmt.Println("hello")fallthrough
case s != "world":fmt.Println("world")
}
结果:
hello
world
新编写的代码,不建议使用 fallthrough。
5、跳转结构(break / continue / goto)
5.1、跳出制定循环 break
package main
import "fmt"
func main() {
OuterLoop:for i := 0; i < 2; i++ {for j := 0; j < 5; j++ {switch j {case 2:fmt.Println(i, j)break OuterLoopcase 3:fmt.Println(i, j)break OuterLoop}}}
}
结果:
0 2
5.2、继续下一次循环 continue
package main
import "fmt"
func main() {
OuterLoop:for i := 0; i < 2; i++ {for j := 0; j < 5; j++ {switch j {case 2:fmt.Println(i, j)continue OuterLoop}}}
}
结果:
0 2
1 2
5.3、跳转到指定的标签 goto
goto 语句通过标签进行代码间的无条件跳转,同时 goto 语句在快速跳出循环、避免重复退出上也有一定的帮助,使用 goto 语句能简化一些代码的实现过程。
5.3.1、使用 goto 退出多层循环
原始代码:
package main
import "fmt"
func main() {var breakAgain bool// 外循环for x := 0; x < 10; x++ {// 内循环for y := 0; y < 10; y++ {// 满足某个条件时, 退出循环if y == 2 {// 设置退出标记breakAgain = true// 退出本次循环break}}// 根据标记, 还需要退出一次循环if breakAgain {break}}fmt.Println("done")
}
经过 goto 后优化:
package main
import "fmt"
func main() {for x := 0; x < 10; x++ {for y := 0; y < 10; y++ {if y == 2 {// 跳转到标签goto breakHere}}}// 手动返回, 避免执行进入标签return// 标签
breakHere:fmt.Println("done")
}
使用 goto 语句后,无须额外的变量就可以快速退出所有的循环。
5.3.2、使用 goto 集中处理错误
err := firstCheckError()
if err != nil {fmt.Println(err)exitProcess()return
}
err = secondCheckError()
if err != nil {fmt.Println(err)exitProcess()return
}
fmt.Println("done")
使用 goto 后:
err := firstCheckError()if err != nil {goto onExit}err = secondCheckError()if err != nil {goto onExit}fmt.Println("done")return
onExit:fmt.Println(err)exitProcess()
6、示例
6.1、九九乘法表
package main
import "fmt"var Info string = "沙师弟 -- 九九乘法表"func main() {fmt.Println(Info)fmt.Println()// 遍历, 决定处理第几行for y := 1; y <= 9; y++ {// 遍历, 决定这一行有多少列for x := 1; x <= y; x++ {fmt.Printf("%d*%d=%d ", x, y, x*y)}// 手动生成回车fmt.Println()}
}
结果:
6.2、冒泡排序
package main
import "fmt"var Info string = "沙师弟 -- 冒泡排序"func main() {arr := [...]int{11,12,42,83,34,34,47,54}var n = len(arr)fmt.Println(Info)fmt.Println()fmt.Println("--------没排序前--------\n",arr)for i := 0; i <= n-1; i++ {fmt.Println("--------第",i+1,"次冒泡--------")for j := i; j <= n-1; j++ {if arr[i] > arr[j] {t := arr[i]arr[i] = arr[j]arr[j] = t}fmt.Println(arr)}}fmt.Println("--------最终结果--------\n",arr)
}
结果:
沙师弟 -- 冒泡排序--------没排序前--------
[11 12 42 83 34 34 47 54]
--------第 1 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------第 2 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
--------第 3 次冒泡--------
[11 12 42 83 34 34 47 54]
[11 12 42 83 34 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
[11 12 34 83 42 34 47 54]
--------第 4 次冒泡--------
[11 12 34 83 42 34 47 54]
[11 12 34 42 83 34 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
[11 12 34 34 83 42 47 54]
--------第 5 次冒泡--------
[11 12 34 34 83 42 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 83 47 54]
--------第 6 次冒泡--------
[11 12 34 34 42 83 47 54]
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 83 54]
--------第 7 次冒泡--------
[11 12 34 34 42 47 83 54]
[11 12 34 34 42 47 54 83]
--------第 8 次冒泡--------
[11 12 34 34 42 47 54 83]
--------最终结果--------
[11 12 34 34 42 47 54 83]
6.3、二分查找
package main
import "fmt"var Info string = "沙师弟 -- 二分查找法"//二分查找函数 假设有序数组的顺序是从小到大(很关键,决定左右方向)
func BinaryFind(arr *[]int, leftIndex int, rightIndex int, findVal int) {if leftIndex > rightIndex { //判断 leftIndex是否大于rightIndexfmt.Println("找不到")return}middle := (leftIndex + rightIndex) / 2 //先找到 中间的下标if (*arr)[middle] > findVal {BinaryFind(arr, leftIndex, middle-1, findVal) //要查找的数,范围应该在 leftIndex 到 middle+1} else if (*arr)[middle] < findVal {BinaryFind(arr, middle+1, rightIndex, findVal) //要查找的数,范围应该在 middle+1 到 rightIndex} else {fmt.Printf("找到了,下标为:%v \n", middle)}
}
func main() {fmt.Println(Info)fmt.Println()//定义一个数组arr := []int{2, 3, 6, 8, 11, 22, 31, 36, 39, 54, 67, 76, 80, 81, 85, 91, 94, 98}BinaryFind(&arr, 0, len(arr) - 1, 36)fmt.Println("main arr :",arr)
}
结果:
6.4、简易聊天机器人
package mainimport ("bufio""fmt""os""strings"
)var Info string = "沙师弟 -- 简易聊天机器人"func main() {fmt.Println(Info)fmt.Println()// 准备从标准输入读取数据。inputReader := bufio.NewReader(os.Stdin)fmt.Println("请输入你的名字:")// 读取数据直到碰到 \n 为止。input, err := inputReader.ReadString('\n')if err != nil {fmt.Printf("异常退出: %s\n", err)// 异常退出。os.Exit(1)} else {// 用切片操作删除最后的 \n 。name := input[:len(input)-1]fmt.Printf("你好, %s! 我能为你做什么?\n", name)}for {input, err = inputReader.ReadString('\n')if err != nil {fmt.Printf("异常退出: %s\n", err)continue}input = input[:len(input)-1]// 全部转换为小写。input = strings.ToLower(input)switch input {case "":continuecase "拜拜", "bye":fmt.Println("下次再回!")// 正常退出。os.Exit(0)default:fmt.Println("对不起,我不清楚你说什么……")}}
}
结果: