Go第四篇之流程控制
流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。
Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。
本章主要介绍了 Go 语言中的基本流程控制语句,包括分支语句(if 和 switch)、循环(for)和跳转(goto)语句。另外,还有循环控制语句(break 和 continue),前者的功能是中断循环或者跳出 switch 判断,后者的功能是继续 for 的下一个循环。
Go语言分支结构
在 Go 语言中,可以通过 if 关键字进行条件判断,格式如下:
- if 表达式1 {
- 分支1
- } else if 表达式2 {
- 分支2
- } else{
- 分支3
- }
当表达式 1 的结果为 true 时,执行分支 1,否则判断表达式 2,如果满足则执行分支 2,都不满足时,则执行分支 3。表达式 2、分支 2 和分支 3 都是可选的,可以根据实际需要进行选择。
Go 语言规定与 if 匹配的左括号{
必须与 if 和表达式放在同一行,如果尝试将{
放在其他位置,将会触发编译错误。
同理,与 else 匹配的{
也必须与 else 在同一行,else 也必须与上一个 if 或 else if 的右边的大括号在一行。
举例
通过下面的例子来了解 if 的写法:
- var ten int = 11
- if ten > 10 {
- fmt.Println(">10")
- } else {
- fmt.Println("<=10")
- }
代码输出如下:
>10
代码说明如下:
- 第 1 行,声明整型变量并赋值 11。
- 第 2 行,判断当 ten 的值大于 10 时执行第 3 行,否则执行第 4 行。
- 第 3 和第 5 行,分别打印大于 10 和小于等于 10 时的输出。
特殊写法
if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:
- if err := Connect(); err != nil {
- fmt.Println(err)
- return
- }
Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。
err!=nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。
这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。
提示
在编程中,变量在其实现了变量的功能后,作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。
循环结构
Go 语言中的所有循环类型均可以使用 for 关键字来完成。
基于语句和表达式的基本 for 循环格式如下:
- for 初始语句;条件表达式;结束语句{
- 循环体代码
- }
循环体不停地进行循环,直到条件表达式返回 false 时自动退出循环,执行 for 的}
之后的语句。
for 循环可以通过 break、goto、return、panic 语句强制退出循环。for 的初始语句、条件表达式、结束语句的详细介绍如下。
for 中的初始语句
初始语句是在第一次循环前执行的语句,一般使用初始语句执行变量初始化,如果变量在此处被声明,其作用域将被局限在这个 for 的范畴内。
初始语句可以被忽略,但是初始语句之后的分号必须要写,代码如下:
- step := 2
- for ; step > 0; step-- {
- fmt.Println(step)
- }
这段代码将 step 放在 for 的前面进行初始化,for 中没有初始语句,此时 step 的作用域就比在初始语句中声明 step 要大。
for 中的条件表达式——控制是否循环的开关
对每次循环开始前计算的表达式,如果表达式为 true,则循环继续,否则结束循环。条件表达式可以被忽略,被忽略条件的表达式默认形成无限循环。
1) 结束循环时带可执行语句的无限循环
下面代码忽略条件表达式,但是保留结束语句,代码如下:
- var i int
- for ; ; i++ {
- if i > 10 {
- break
- }
- }
代码说明如下:
- 第 3 行,无须设置 i 的初始值,因此忽略 for 的初始语句。两个分号之间是条件表达式,也被忽略,此时循环会一直持续下去;for 的结束语句为 i++,每次结束循环前都会调用。
- 第 5 行,判断 i 大于 10 时,通过 break 语句跳出 for 循环到第 9 行。
2) 无限循环
上面的代码还可以改写为更美观的写法,代码如下:
- var i int
- for {
- if i > 10 {
- break
- }
- i++
- }
代码说明如下:
- 第 3 行,忽略 for 的所有语句,此时 for 执行无限循环。
- 第 9 行,将 i++ 从 for 的结束语句放置到函数体的末尾是等效的,这样编写的代码更具有可读性。
无限循环在收发处理中较为常见,但需要无限循环有可控的退出方式来结束循环。
3) 只有一个循环条件的循环
在上面代码的基础上进一步简化代码,将 if 判断整合到 for 中,变为下面的代码:
- var i int
- for i <= 10 {
- i++
- }
在代码第 3 行中,将之前使用 if i>10{} 判断的表达式进行取反,变为判断 i 小于等于 10 时持续进行循环。
上面这段代码其实类似于其他编程语言中的 while,在 while 后添加一个条件表达式,满足条件表达式时持续循环,否则结束循环。
for 中的结束语句——每次循环结束时执行的语句
在结束每次循环前执行的语句,如果循环被 break、goto、return、panic 等语句强制退出,结束语句不会被执行。
输出九九乘法表
熟悉了 Go 语言的基本循环格式后,让我们用一个例子来温习一遍吧。
输出九九乘法表:
- package main
- import "fmt"
- func main() {
- // 遍历, 决定处理第几行
- for y := 1; y <= 9; y++ {
- // 遍历, 决定这一行有多少列
- for x := 1; x <= y; x++ {
- fmt.Printf("%d*%d=%d ", x, y, x*y)
- }
- // 手动生成回车
- fmt.Println()
- }
- }
结果输出如下:
- 1*1=1
- 1*2=2 2*2=4
- 1*3=3 2*3=6 3*3=9
- 1*4=4 2*4=8 3*4=12 4*4=16
- 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
- 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
- 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
- 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
- 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
代码说明如下:
- 第 8 行,生成 1~9 的数字,对应乘法表的每一行,也就是被乘数。
- 第 11 行,乘法表每一行中的列数随着行数的增加而增加,这一行的 x 表示该行有多少列。
- 第 12 行,打印一个空行,实际作用就是换行。
这段程序按行优先打印,打印完一行,换行(第12行),接着执行下一行乘法表直到整个数值循环完毕。
键值循环
Go 语言可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。通过 for range 遍历的返回值有一定的规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- 通道(channel)只返回通道内的值。
遍历数组、切片——获得索引和元素
在遍历代码中,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
遍历字符串——获得字符
Go 语言和其他语言类似,可以通过 for range 的组合,对字符串进行遍历,遍历时,key 和 value 分别代表字符串的索引(base0)和字符串中的每一个字符。
下面这段代码展示了如何遍历字符串:
- 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
代码中的 v 变量,实际类型是 rune,实际上就是 int32,以十六进制打印出来就是字符的编码。
遍历map——获得map的键和值
对于 map 类型来说,for range 遍历时,key 和 value 分别代表 map 的索引键 key 和索引对应的值,一般被称为 map 的键值对,因为它们总是一对一对的出现。下面的代码演示了如何遍历 map。
- m := map[string]int{
- "hello": 100,
- "world": 200,
- }
- for key, value := range m {
- fmt.Println(key, value)
- }
代码输出如下:
hello 100
world 200
注意
对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。
遍历通道(channel)——接收通道数据
for range 可以遍历通道(channel),但是通道在遍历时,只输出一个值,即管道内的类型对应的数据。
下面代码为我们展示了通道的遍历:
- c := make(chan int)
- go func() {
- c <- 1
- c <- 2
- c <- 3
- close(c)
- }()
- for v := range c {
- fmt.Println(v)
- }
代码说明如下:
- 第 1 行创建了一个整型类型的通道。
- 第 3 行启动了一个 goroutine,其逻辑的实现体现在第 5~8 行,实现功能是往通道中推送数据 1、2、3,然后结束并关闭通道。
- 这段 goroutine 在声明结束后,在第 9 行马上被并行执行。
- 从第 11 行开始,使用 for range 对通道 c 进行遍历,其实就是不断地从通道中取数据,直到通道被关闭。
在遍历中选择希望获得的变量
在使用 for range 循环遍历某个对象时,一般不会同时需要 key 或者 value,这个时候可以采用一些技巧,让代码变得更简单。下面将前面的例子修改一下,参考下面的代码示例:
- m := map[string]int{
- "hello": 100,
- "world": 200,
- }
- for _, value := range m {
- fmt.Println(value)
- }
代码输出如下:
100
200
在例子中将 key 变成了下画线,那么这里的下画线就是匿名变量。什么是匿名变量?
- 可以理解为一种占位符。
- 本身这种变量不会进行空间分配,也不会占用一个变量的名字。
- 在 for range 可以对 key 使用匿名变量,也可以对 value 使用匿名变量。
再看一个匿名变量的例子:
- for key, _ := range []int{1, 2, 3, 4} {
- fmt.Printf("key:%d \n", key)
- }
代码输出如下:
key:0
key:1
key:2
key:3
在该例子中,value 被设置为匿名变量,只使用 key,而 key 本身就是切片的索引,所以例子输出索引。
我们总结一下for的功能:
- Go 语言的 for 包含初始化语句、条件表达式、结束语句,这 3 个部分均可缺省。
- for range 支持对数组、切片、字符串、map、通道进行遍历操作。
- 在需要时,可以使用匿名变量对 for range 的变量进行选取。
switch语句
分支选择可以理解为一种批量的if语句,使用 switch 语句可方便地对大量的值进行判断。
在 Go 语言中的 switch,不仅可以基于常量进行判断,还可以基于表达式进行判断。
提示
C/C++ 语言中的 switch 语句只能支持数值常量,不能对字符串、表达式等复杂情况进行处理,这么设计的主要原因是性能。C/C++ 的 switch 可以根据 case 的值作为偏移量直接跳转代码,在性能敏感代码处,这样做显然是有好处的。
到了 Go 语言的时代,语言的运行效率并不能直接决定最终的效率,I/O 效率现在是最主要的问题。因此,Go 语言中的 switch 语法设计尽量以使用方便为主。
基本写法
Go 语言改进了 switch 的语法设计,避免人为造成失误。Go 语言的 switch 中的每一个 case 与 case 间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行。示例代码如下:
- var a = "hello"
- switch a {
- case "hello":
- fmt.Println(1)
- case "world":
- fmt.Println(2)
- default:
- fmt.Println(0)
- }
代码输出如下:
1
上面例子中,每一个 case 均是字符串格式,且使用了 default 分支,Go 语言规定每个 switch 只能有一个 default 分支。
1) 一分支多值
当出现多个 case 要放在一起的时候,可以像下面代码这样写:
- var a = "mum"
- switch a {
- case "mum", "daddy":
- fmt.Println("family")
- }
不同的 case 表达式使用逗号分隔。
2) 分支表达式
case 后不仅仅只是常量,还可以和 if 一样添加表达式,代码如下:
- var r int = 11
- switch {
- case r > 10 && r < 20:
- fmt.Println(r)
- }
注意,这种情况的 switch 后面不再跟判断变量,连判断的目标都没有了。
跨越case的fallthrough——兼容C语言的case设计
在 Go 语言中 case 是一个独立的代码块,执行完毕后不会像C语言那样紧接着下一个 case 执行。但是为了兼容一些移植代码,依然加入了 fallthrough 关键字来实现这一功能,代码如下:
- var s = "hello"
- switch {
- case s == "hello":
- fmt.Println("hello")
- fallthrough
- case s != "world":
- fmt.Println("world")
- }
代码输出如下:
hello
world
新编写的代码,不建议使用 fallthrough。
goto语句
goto 语句通过标签进行代码间的无条件跳转。goto 语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go 语言中使用 goto 语句能简化一些代码的实现过程。
使用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")
- }
代码说明如下:
- 第 10 行,构建外循环。
- 第 13 行,构建内循环。
- 第 16 行,当 y==2 时需要退出所有的 for 循环。
- 第 19 行,默认情况下循环只能一层一层退出,为此就需要设置一个状态变量 isbreak,需要退出时,设置这个变量为 true。
- 第 22 行,使用 break 退出当前循环,执行后,代码调转到第 28 行。
- 第 28 行,退出一层循环后,根据 isbreak 变量判断是否需要再次退出外层循环。
- 第 34 行,退出所有循环后,打印 done。
使用 goto 集中处理错误
将上面的代码使用 Go 语言的 goto 语句进行优化。
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")
- }
代码说明如下:
- 第 13 行,使用 goto 语句跳转到指明的标签处,标签在第 23 行定义。
- 第 20 行,标签只能被 goto 使用,但不影响代码执行流程,此处如果不手动返回,在不满足条件时,也会执行第 24 行代码。
- 第 23 行,定义 breakHere 标签。
使用 goto 语句后,无须额外的变量就可以快速退出所有的循环。
统一错误处理
多处错误处理存在代码重复时是非常棘手的,例如
- err := firstCheckError()
- if err != nil {
- fmt.Println(err)
- exitProcess()
- return
- }
- err = secondCheckError()
- if err != nil {
- fmt.Println(err)
- exitProcess()
- return
- }
- fmt.Println("done")
代码说明如下:
- 第 1 行,执行某逻辑,返回错误。
- 第 2~6 行,如果发生错误,打印错误退出进程。
- 第 8 行,执行某逻辑,返回错误。
- 第 10~14 行,发生错误后退出流程。
- 第 16 行,没有任何错误,打印完成。
在上面代码中,加粗部分都是重复的错误处理代码。后期陆续在这些代码中如果添加更多的判断,就需要在每一块雷同代码中依次修改,极易造成疏忽和错误。
如果使用 goto 语句来实现同样的逻辑
- err := firstCheckError()
- if err != nil {
- goto onExit
- }
- err = secondCheckError()
- if err != nil {
- goto onExit
- }
- fmt.Println("done")
- return
- onExit:
- fmt.Println(err)
- exitProcess()
代码说明如下:
- 第 3 行和第 9 行,发生错误时,跳转错误标签 onExit。
- 第 17 行和第 18 行,汇总所有流程进行错误打印并退出进程。
break(跳出循环)
break 语句可以结束 for、switch 和 select 的代码块。break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上。
跳出指定循环:
- 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
- }
- }
- }
- }
- 代码输出如下:
- 0 2
代码说明如下:
- 第 7 行,外层循环的标签。
- 第 8 行和第 9 行,双层循环。
- 第 10 行,使用 switch 进行数值分支判断。
- 第 13 和第 16 行,退出 OuterLoop 对应的循环之外,也就是跳转到第 20 行。
continue
continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用。在 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
代码说明:第 14 行将结束当前循环,开启下一次的外层循环,而不是第 10 行的循环。
Go第四篇之流程控制的更多相关文章
- shell之路 shell核心语法【第四篇】流程控制
if语句 if ... fi 语句: if ... else ... fi 语句: if ... elif ... else ... fi 语句. 注意: expression 和方括号([ ])之间 ...
- 李洪强iOS开发Swift篇—06_流程控制
李洪强iOS开发Swift篇—06_流程控制 一.swift中的流程控制 Swift支持的流程结构如下: 循环结构:for.for-in.while.do-while 选择结构:if.switch 注 ...
- python 学习之 基础篇三 流程控制
前言: 一. python中有严格的格式缩进,因为其在语法中摒弃了“{}”来包含代码块,使用严格的缩进来体现代码层次所以在编写代码的时候项目组要严格的统一器缩进语法,一个tab按键设置为四个空格来缩进 ...
- Java核心基础第3篇-Java流程控制
Java流程控制 本章一起来探讨下Java的流程控制语句.主要从以下几个方面展开: Java分支语句 Java循环语句 Java其实和其他任何的开发语言一样,分支语句和循环语句是必不可少的,有个这两个 ...
- [Java入门笔记] Java语言基础(四):流程控制
流程控制指的是在程序运行的过程中控制程序运行走向的方式.主要分为以下几种: 顺序结构 顺序结构,顾名思义,是指程序从上往下逐步顺序执行.中间没有任何的判断和跳转. 分支结构 Java提供两种分支结构: ...
- shell重温---基础篇(流程控制&if判断&for&while&循环操作)
和Java.PHP等语言不一样,sh的流程控制不可为空,如(以下为PHP流程控制写法): <?php if (isset($_GET["q"])) { search( ...
- C#基础篇三流程控制2
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace P01R ...
- Shell学习心得(四):流程控制
一.if else 1. if if 语句语法格式: if condition then command1 command2 ... commandN fi 写成一行(适用于终端命令提示符): ]; ...
- Go语言入门篇-基本流程控制
一.if语句 二.switch语句 三.for语句 四.select语句
随机推荐
- PHP之文件上传
1.$_FILES['myFile']['name'] 上传文件的原始名称 2.$_FILES['myFIle']['type'] 上传文件的mime-type 3.$_FILES['myFile'] ...
- ES6 ruanyifeng, shim polyfill
http://www.cnblogs.com/upup2015/p/7927485.html 一个等号是赋值操作,==先转换类型再比较,===先判断类型,如果不是同一类型直接为false npm in ...
- Python requests 301/302/303重定向(跨域/本域)cookie、Location问题
今天使用request的get方法获取一个网站的登录页信息,结果使用charles抓包的时候发现该网站登录页303(重定向的问题),网上查了很多资料,原因如下: 一.cookie 原因:利用reque ...
- mysql 内置功能 视图 使用
#语法:CREATE VIEW 视图名称 AS SQL语句 增加了一张表 mysql> create view course2teacher as select * from course in ...
- vue:Group XSwitch Actionsheet,Toast控件使用
<template> <div> <div class="vux-demo"> <img class="logo" s ...
- 3.cassandra遇到内存占用过高的问题
目前cssandra的内存分配如下: https://docs.datastax.com/en/cassandra/2.1/cassandra/operations/ops_tune_jvm_c.ht ...
- [py]python中的特殊类class type和类的两面性图解
生活中的模具 生活中 编程 万物都从无到有, 起于烟尘 () 生产原料,铁 object 车床-生产各类模具 元类即metaclass,对应python的class type 模具-生产各类实在的物品 ...
- numpy中arange()和linspace()区别
arange()类似于内置函数range(),通过指定开始值.终值和步长创建表示等差数列的一维数组,注意得到的结果数组不包含终值. linspace()通过指定开始值.终值和元素个数创建表示等差数列的 ...
- 深入浅出TCP之listen
原文:http://blog.chinaunix.net/uid-29075379-id-3858844.html int listen(int fd, int backlog); 有几个概念需要在开 ...
- Winter Storm Warning
2019-01-02 11:22:33 ...WINTER STORM WARNING REMAINS IN EFFECT UNTIL 6 AM AKST TUESDAY... * WHAT...He ...