文章目录
- 0、唠唠叨叨
- 1、分支结构(if / else)
- 1.1、标准写法
- 1.2、特殊写法
- 2.1、标准循环
- 2.2、无限循环
- 2.3、for 中的初始语句(开始循环时执行的语句)
- 2.4、for 中的结束语句(循环结束时执行的语句)
- 2.5、for 中的条件表达式(控制是否循环的开关)
- 2.5.1、结束循环时带可执行语句的无限循环
- 2.5.2、无限循环
- 2.5.3、只有一个循环条件的循环
- 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.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
}
无效格式:
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)
}
代码输出如下:
对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。
3.5、遍历通道 channel(接收通道数据)
c := make(chan int)
go func() {
c <- 1
c <- 2
c <- 3
close(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)
}
代码输出如下:
再看一个匿名变量的例子:
for key, _ := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d \n", key)
}
代码输出如下:
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")
}
结果:
新编写的代码,不建议使用 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 OuterLoop
case 3:
fmt.Println(i, j)
break OuterLoop
}
}
}
}
结果:
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
}
}
}
}
结果:
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是否大于rightIndex
fmt.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 main
import (
"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 "":
continue
case "拜拜", "bye":
fmt.Println("下次再回!")
// 正常退出。
os.Exit(0)
default:
fmt.Println("对不起,我不清楚你说什么……")
}
}
}
结果: