通向Golang的捷径【5. 控制语句】

基于目前的学习阶段, 我们知道 Go 语言将从 main() 函数开始执行, 之后将执行该函数的语句, 但是我们希望在语句中, 增加一些判断条件, 因此 Go 语言提供了条件结构 (或分支结构).
在这里插入图片描述
或是在一个迭代或循环结构中, 重复执行一些代码:
在这里插入图片描述
同时还需要提供一些关键字, 如 break,continue, 可退出循环. 并且 return 关键字可退出一个程序块, 而 goto关键字可实现无条件的执行跳转.

Go 代码中, 省略了 if,switch,for 条件的圆括号, 因此这与 Java,C++,C# 语言有所不同.

5.1 if … else

if 语句可测试一个条件 (布尔型或逻辑型), 如果条件为真, 将执行花括号{ } 之间的语句, 如果条件为假, 将忽略花括号之间的代码.
在这里插入图片描述
else 关键字将为 if 语句, 提供一个备选代码块 ( 也需使用花括号封闭), 当 if 条件为假时, 可执行该语句块.
在这里插入图片描述
同时 if 语句也可实现级联, 如下的二重 if 语句:
在这里插入图片描述
if 语句的级联并无限制, 但是级联的层次过多, 会降低代码的可读性, 因此在使用 if 语句时, 应将频繁出现的情况, 放在第一位.

即使语句块只有一条语句, 花括号也必须提供 (强制要求), 这也与主流的软件工程理念相一致, 同时 { 应与 if和 else 在同一文本行, 而 } 应与后续的 else if 和 else 在同一文本行, 这些规定都是编译器的强制要求.
在这里插入图片描述
每个分支语句块应使用 4(或 8) 个空格或 1 个 tab 键进行缩排(缩进排版), 而 } 应实现垂直对齐, 这也是 gofmt的强制风格.

if 条件无须使用圆括号, 这可使代码更干净, 如果是一个复合条件, 比如使用了逻辑操作符&&,|,!|, 可使用圆括号建立优先级, 并增强可读性.

如果需要测试一个变量值, 并基于变量的不同值, 执行不同的语句块, 这应当选择 switch 语句, 参见 5.3 节, 因为该语句更适合.

例 5.1 booleans.go

在这里插入图片描述
在上述代码中, 条件无须写为 bool1 == true, 因为 bool1 已是一个布尔值. 如果条件为真, 也可使用! 实现反向测试, 比如 if !bool1 或 if !(条件), 在后一个条件中, 通常需要增加圆括号, 比如 if !(var1 == var2).

在 Go 代码中, 如果 if 语句使用 break,continue,goto,return 语句结尾, 将会忽略 else 分支, 如下:
在这里插入图片描述
如果在 if…else 语句的两个分支中, 都未给出 return(假定函数中, 只包含了一个 if…else 语句), 将无法编译, 并会出现错误:
在这里插入图片描述
这可能是一个编译器 bug 或是一个特性, 但它可使用户快速形成习惯用法.

一些示例

检测字符串 str 是否为空:
在这里插入图片描述
检测 Go 程序的运行环境 (即操作系统), 必须使用 runtime.GOOS:
在这里插入图片描述
上述检测应放置在 init() 函数, 以便用户输入可满足 OS 的要求, 如下:
在这里插入图片描述
Abs 函数可测试整型值的绝对值
在这里插入图片描述
isGreater 函数可检测两个整型的大小
在这里插入图片描述
如果 if 条件中包含了初始化语句 (为变量分配一个数值), 则必须在初始化语句之后, 强制增加; 符号:
在这里插入图片描述
为了保证最大的可读性, 在 if 语句中, 变量 val 将使用:= 进行初始化 ( 该变量的作用域将限制在{} 之间, 如果存在 else 分支, 其中也可使用 val 变量), 如果在 if 语句执行前, 已存在了 val 变量, 那么外部的 val 变量将在 if 语句执行时被隐藏, 因此一种最简单的解决方案, 即不使用:= 实现 if 语句的初始化语句.

例 5.2 ifelse.go

在这里插入图片描述
在这里插入图片描述
以下代码可在 if 语句块中, 获得 process() 函数的结果, 并在语句块中, 使用函数的返回值:
在这里插入图片描述

5.2 使用多个返回值, 测试函数的运行错误

Go 语言定义的函数, 可提供两个返回值, 如果函数执行成功, 一是返回值, 二是状态码 (true), 如果函数执行不成功, 一是 0(或 nil), 二是 false, 除了执行状态码之外, 还返回一个错误变量, 除了 nil 之外, 还返回其他的错误信息, 在 Go 语言中, 还可声明 error(错误) 类型的变量 (参见第 13 章), 因此也可在 if 语句中, 对函数的执行结果进行检测.

在 4.7 节中, 示例代码 string_conversion.go 使用 strconv.Atoi 函数, 将一个字符串转换成整型, 其中忽略了一个可能存在的错误条件:
在这里插入图片描述
如果 origStr 无法转换成整型, 将在 an 中返回 0, 而后续的 _ 将丢弃错误码, 而程序会继续执行. 这并不是一个最佳的处理方法, 可靠的程序应当对可能出现的所有错误进行检测, 否则至少应对函数返回的错误条件和数值, 进行必要的检查. 以下给出了 string_conversion.go 的第二个版本:

例 5.3 string_conversion2.go

在这里插入图片描述
如果错误变量 err 给出了一个错误码 (err != nil), 则需要进行检查, 并会打印出对应的错误消息, 或是给出
return 终止当前函数的执行. 当然也可返回错误变量, 比如 return err, 以便函数调用者对错误变量 err 进行检查.
在这里插入图片描述
在这里插入图片描述
如果上述代码在 main() 中执行, 那么应用程序将停止. 如果希望程序在某个错误出现时停止运行, 可使用 os包的 Exit 函数, 来替代 return 语句:
在这里插入图片描述
上述代码中, 为 Exit 提供了一个整型值 1, 该值可被外部脚本所检测.

如果既没有 else 分支, 也没有出现错误, 将会继续执行{} 之后的代码. 以下代码将使用 os.open 打开一个只读文件 name:
在这里插入图片描述
if 的初始化语句出现错误:
在这里插入图片描述
当 if 的初始化语句中, 给出一个多重赋值, 可在 if 条件中, 直接测试该赋值结果:
在这里插入图片描述
如果忘记函数可返回多个值, 以下代码将产生错误:
在这里插入图片描述
上述代码将出现错误:
在这里插入图片描述
因此代码应改为:
在这里插入图片描述
fmt 包的打印函数也可提供两个返回值:
在这里插入图片描述
当然控制台的输出操作无须检查, 如果是文件或网络连接的输出, 可需要对打印函数的错误码进行检查.

5.3 switch

相比于 C,Java, Go 语言的 switch 语句更具灵活性, 如下:
在这里插入图片描述
var1 是任意类型的一个变量, 而 val1,val2,… 为 var1 可能的数值, 当然这些数值无须是常量或整型, 但是必须具有相同的类型, 或是可得到同一类型的表达式, 另外{ 应与 switch 在同一文本行.

在一个 case 中, 可测试多个数值, 这些数值可逗号进行分隔,
在这里插入图片描述
同时每个 case 分支必须是唯一的, 并会从起始 case 开始数值匹配, 直到最后一个 case, 所以最有可能的数值,应尽量放置在起始 case 中.

如果首个 case 匹配成功且完成执行, 整个 switch 语句也将完成, 这里隐含了 C++, Java,C# 给出的 break 语句, 也是 Go 语言的改进之一.

在 Go 语言中, case 之间的顺序执行, 并不是默认行为, 如果希望实现顺序执行, 可在 case 分支的末尾处, 使用关键字 fallthrough, 如下:
在这里插入图片描述
顺序执行可实现 case 分支之间的层次化, 如果有默认动作需要执行时, 可在高层 case 分支中, 放入一些未被执行的特定代码.

如果在 case …: 之后, 存在多条语句, 也无须封闭在花括号{} 内, 但允许使用花括号, 如果 case 分支只有一条语句, 可放置在同一文本行 (case 关键字). case 分支的最后一条语句, 可以是 return 语句, 并可选择是否包含一个表达式. 当 case 分支使用了 return, 则等同于 switch 语句中} 符号之后的 return 语句.

default(默认) 分支是可选的, 当变量 var1 的数值无法匹配时, 可执行 default 分支, 这与 if 语句的 else 分支很相似, 同时 default 分支可出现在 switch 的任意位置, 但最好能书写在末尾处.

例 5.4 switch1.go

在这里插入图片描述
在 12.1 节中, 将使用 switch 语句, 读取键盘的输入 (例 12.2 的 switch.go), 在以下的 switch 语句中, 并未给出测试变量 (这时 switch 将一直为真, 即 switch 1), 同时会对 case 给出的不同表达式进行测试, 如果表达式为真, 将执行该 case 分支, 这与 if-else 级联很相似, 只不过当包含大量分支时,switch 方式具有更好的可读性.
在这里插入图片描述
任意类型都支持相等比较符 (==), 比如 int, 字符串, 指针, 并能用于上述的 case 条件.

例 5.5 switch2.go

在这里插入图片描述
在这里插入图片描述
和 if 语句一样,switch 语句中, 也可包含初始化语句:
在这里插入图片描述
在以下代码中, a 和 b 可实现并发初始化,
在这里插入图片描述
同时 switch 用到的变量, 必须是同一类型, 该规则也可用于检查接口变量的类型, 参见 11.4 节.

5.4 for

for 可实现语句块的重复执行, 同时又比其他语言更加灵活, 重复执行的过程又称为迭代 (iteration). 在言中, 并未给出其他语言存在的匹配循环语句, 比如 do…while, 可能是这类语句的利用率不高.

5.4.1 计数迭代

for 语句的计数迭代的一般格式如下, 同时 for1.go 示例中使用了计数迭代.
在这里插入图片描述

例 5.6 for1.go

在这里插入图片描述
for 循环中花括号{} 封闭的程序块, 可重复执行, 而变量 ( 这里是 i) 的计数将决定执行次数,for 循环的起始处存在初始化语句 i := 0, 而变量 i 必须事先声明, 之后是判断条件 i < 5, 它将在每次迭代前进行计算, 结果为真, 将执行迭代, 结果为假,for 循环将停止, 再后是一个修改器 i++, 它将在每次迭代后进行计算, 当其计算完毕后, 将继续判断条件的检查, 当然修改器也可以是一个递减操作.

上述三类语句应使用逗号进行分隔, 但无须使用圆括号, 封闭这三类语句, 否则无效.
在这里插入图片描述
同时{应与 for 关键字在同一文本行, 并在} 之后 (for 循环完成), 计数变量将消失, 因此计数变量都采用简单名称, 如 i,j,z,ix. 从不修改 for 循环的计数变量, 将导致死循环, 这也是所有编程语言面临的一个难题.

5.4.2 条件迭代

第二类 for 循环, 即为条件迭代 (可等同于其他语言的 while 循环), 格式如下:
在这里插入图片描述
当然未包含初始化和修改器的 for 循环, 也存在一定的争议, 给出;; 则是多余的.

例 5.8 for2.go

在这里插入图片描述

5.4.3 死循环

如果出现以下语句,
在这里插入图片描述
将导致死循环, 而for true { } 语句等同于for { }. 如果 for 语句的判断条件丢失, 将导致判断条件一直为真,
这时循环的程序块将一直在执行. 也可在程序块中, 设定一个退出条件, 同时避免出现一个死循环, 这时可使用break 和 return 语句, 两者之间的区别在于,break 语句只能退出循环, 而 return 语句可从执行循环的函数中退出, 而死循环常见的用法是, 作为一个服务器, 并等待外部的连接请求.

例 12.17(xml.go) 给出了一个 for 循环的应用:

在这里插入图片描述

5.4.4 for … range

在 Go 语言的迭代结构中, 还包含了一些有价值的内容, 可以优雅地实现强大的功能, 能基于一个集合中的元素进行循环 (比如数组和 map, 参见第 7 和 8 章), 这与其他语言的 foreach 相似, 此循环在每次迭代中, 都会包含一个索引值, 如下:
在这里插入图片描述
注意,val 只是集合中索引值的副本, 因此它是只读变量, 所以通过 val, 无法对集合中的索引值进行修改, 而Unicode 字符 (或 rune) 的集合将是一个字符串, 该 for 循环也能用于字符串, 如果 str 是一个字符串, 可使用以下循环:
在这里插入图片描述
每个 rune 字符和索引位置, 都可在循环中进行处理, 同时 Unicode 字符将解析成 UTF-8 编码, 错误的编码将浪费一个字节的内存单元, 同时可生成 U+FFFD 的显示格式.

例 5.9 range_string.go

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
比较例子 5.7(for_string.go) 的输出, 可知英文字符只需一个字节, 而中文字符需要三个字节.

5.5 break/continue

在 for2.go 代码的 for 循环中, 使用了 break 语句, 以下将重写这一部分,

例 5.10 for3.go

在这里插入图片描述
在每次迭代中, 条件 (i < 0) 都将检查, 以确定循环是否可以终止, 如果该条件为真, 将执行 break 语句, 退出for 循环.同时 break 语句可退出最内层的结构, 它可用于不同类型的 for 循环,switch,select(参见第 13 章), 当 break 语句执行后, 将跳转到结构体的末尾处 (通常是}), 继续执行. 在以下的嵌套循环中, break 语句可退出内层循环(但外层循环并未退出):

例 5.11 for4.go

在这里插入图片描述
关键字 continue 可跳过剩余的循环程序块, 并直接检查循环条件, 以及开始下一次循环迭代,

例 5.12 for5.go

在这里插入图片描述
注意,continue 关键字只能用于 for 循环.

5.6 break,continue 和 goto 使用的标记 (label)

for,switch,select 语句的起始代码行, 都可给出一个标记 (label), 该标记需使用冒号 (😃 结尾, 并会放置在代码之前 (gofmt 工具会将标记, 放置在前一行), 如下:

例 5.13 for6.go

在这里插入图片描述
标记对大小写敏感, 通常会选择大写标记, 以增强代码的可读性.

当 continue LABEL1 语句执行时, 将跳转到标记 LABEL1, 开始执行. 在上述代码中, 当 j=(4,5) 时, 将不会产生输出, 并会跳转到外层循环, 从 i 的新值开始执行, 将会引发内层循环变量 j 复位为 0,break LABEL 也可提供相同的操作方式, 它不仅能用于 for 循环, 还可用于 switch, 而 goto 语句则可实现相似的循环处理, 如下:

例 5.14 goto.go

在这里插入图片描述
在这里插入图片描述
有些论调觉得 goto 是一个搅局者, 应当禁用该语句, 当然我们需要一种更理性的声音, 以下观点值得商榷:标记和 goto 语句的使用只能带来失望, 它们会快速搅乱程序的设计, 不使用它们, 代码的可读性将会提高.

来自 15.1 节的示例 simple_tcp_server.go 给出 goto 的用法, 当网络连接中出现读取错误时, 可使用 goto 语句跳出该读取死循环, 并关闭与客户端的连接.

如果声明了一个标记, 却没有使用, 编译器将产生一个编译错误:
在这里插入图片描述
如果真的需要使用 goto 语句, 应保证提供正向标记, 即标记应在 goto 语句之前出现, 同时保证标记与 goto 之间的距离 (不要过长), 同时在标记和 goto 之间, 不要声明新变量, 因为这可能引入错误和不可预知的结果, 如下:

例 5.15 goto2.go(无法编译)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值