深入学习Go语言
上QQ阅读APP看书,第一时间看更新

2.6 流程控制

2.6.1 switch语句

switch语句提供多路执行,将表达式或类型说明符与“switch”内的“case”进行比较,以确定要执行的分支。

switch有两种形式:表达式型switch和类型型switch。在表达式型switch中,包含与switch表达式的值进行比较的表达式。在类型型switch中,包含与switch表达式的类型进行比较的类型。注意:switch表达式在switch语句中只运行一次。

如果switch表达式求值为无类型常量,则首先将其转换为默认类型;如果是无类型的布尔值,则首先将其转换为bool类型。预先声明的无类型值nil不能用作开关表达式。

如果switch表达式是无类型的,则首先将其转换为switch表达式的类型。对于每个(可能已转换的)switch表达式x和switch表达式的值t,x和t必须可以进行有效的比较。

换句话说,switch表达式被视为用于声明和初始化没有显式类型的临时变量t,它是t的值,对每个switch表达式x进行相等性测试。

在switch或default子句中,最后一个非空语句可以是“fallthrough”语句,以指示应该从该子句的末尾流向下一个子句的第一个语句,无论下一个子句的条件是否满足。出现“fallthrough”语句后,它后面只能接下一个子句。

程序输出:

表达式型switch有三种使用方式。第一种,switch表达式可以执行一个简单语句完成运算从而得到表达式的值。

例如:

switch语句的第二种形式是不提供任何被判断的值(实际上默认为判断是否为true),然后在每个case分支中测试不同的条件。当任一分支的测试结果为true时,该分支的代码会被执行,此时语句(无表达式)相当于switch true。

例如:

switch语句的第三种形式是包含一个初始化语句:

例如:

val1和val2可以是同类型的任意值。类型不局限于常量或整数,但必须是相同的类型,或者最终结果为相同类型的表达式。前花括号{必须和switch关键字在同一行。

可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1,val2,val3。一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个switch代码块,也就是说,不需要特别使用break语句来表示结束。

如果在执行完每个分支的代码后,还希望继续执行后续分支的代码,可以使用fallthrough关键字来达到目的。

fallthrough强制执行后面的下一条分支代码。fallthrough不会判断下一条分支的表达式结果是否为真。例如:

程序输出:

上面代码中fallthrough直接进入case 2中,不会对条件判读。

类型型switch比较类型而不是值。它在其他方面类似于表达式型switch,只不过分支选择的是类型而不是值。它由一个特殊的switch表达式标记,该表达式使用类型断言的形式来进行动态类型判断。例如:

2.6.2 select语句

select是Go语言中的一个控制结构,类似于switch语句,主要用于处理异步通道操作,所有情况都会涉及通信操作。因此select会监听分支语句中通道的读写操作,当分支中的通道读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。select语句会选择一组可以发送或接收操作中的一个分支继续执行。select没有条件表达式,一直在等待case进入可运行状态。

select中的case语句必须是对通道的操作。

select中的default子句总是可运行的。

■ 如果有多个分支都可以运行,select会伪随机公平地选出一个执行,其他分支不会执行。

■ 如果没有可运行的分支,且有default语句,那么就会执行default的动作。

■ 如果没有可运行的分支,且没有default语句,select将阻塞,直到某个分支可以运行。

程序输出:

2.6.3 for语句

for语句是最简单的基于计数器的循环迭代,基本形式为:

这三部分组成循环的头部,相互之间使用分号隔开,但并不需要括号将它们括起来。

还可以在循环中同时使用多个计数器:

这得益于Go语言具有的平行赋值的特性。

for结构的第二种形式是没有头部的条件判断迭代(类似其他语言中的while循环),基本形式为:for{}。

也可以认为这是没有初始化语句和修饰语句的for结构,因此 ;; 便是多余的了。

即使是条件语句也可以省略,如i:=0;;i++或for{} 或for;;{}(;; 会在使用gofmt时移除),这些循环的本质就是无限循环。

第二种形式也可以改写为for true{},但一般情况下都会直接写for{}。

如果for循环的头部没有条件语句,那么就会认为条件永远为true(还记得前面Switch语句吗?没有表达式就认为是true。这好像是Go语言处理类似情况的常用法则)。因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。例如:

2.6.4 for-range结构

for-range结构是Go语言特有的一种迭代结构,它在许多情况下都非常有用。它可以迭代任何一个集合,包括数组(array)和字典(map),同时可以获得每次迭代所对应的索引和值。一般形式为:

如果只需要range里的索引值,可只写key。

要注意的是,val始终为集合中对应索引的值的副本,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(如果val为指针,则会产生指针的副本,依旧可以修改集合中的原值)。例如:

下面代码中,迭代变量v作为匿名goroutine的参数,它是值副本传递,就不会出现上面那种指针导致的值被修改的情况。例如:

一个字符串是Unicode编码的字符集合,因此也可以用for-range结构迭代字符串:

程序输出:

2.6.5 if语句

if语句由布尔表达式后紧跟一个或多个语句组成。注意布尔表达式不用(),根据布尔表达式的值指定两个分支的条件执行。如果表达式求值为true,则执行“if”分支,否则执行“else”分支。

由于if和switch都接受初始化语句,因此通常会看到用于设置局部变量的语句,而且该语句在计算表达式之前执行。例如:

在Go语言中,当if语句没有进入下一个语句,即正文以break、continue、goto或return结尾时,省略不必要的else。例如:

2.6.6 break语句

一个break的作用范围为该语句出现后的最内部的结构,它可以用于任何形式的for循环(计数器、条件判断等)。

但在switch或select语句中,break语句的作用是跳过整个代码块,执行switch或select外面后续的代码。

语句中如果有标签(见2.6.8节),则必须是包含“for”“switch”或“select”语句的标签,并且该标签是可以让执行终止的。例如:

程序输出:

上面的代码很好地验证了break的规则。首先break语句的作用范围为该语句出现后的最内部的结构,所以在j==0时,只是跳出最里层switch,还是在j循环迭代中。当执行break OuterLoop时,会跳到label标签的位置。注意这时的这个label标签只能是前面出现过的。

2.6.7 continue语句

关键字continue忽略剩余的循环体而直接进入下一次循环的过程,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件。

如果有一个标签,那么它必须是一个封闭的“for”语句,并且是当前执行进程的标签。例如:

2.6.8 标签

for、switch或select语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)结尾的单词(Gofmt会将后续代码自动移至下一行)。标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母。例如:

标签用于“break”“continue”和“goto”语句。定义从未使用过的标签是非法的,即不能编译成功。

2.6.9 goto语句

goto语句是跳转到具有相同函数内相应标签的语句。

Go语言不鼓励使用标签和goto语句,因为它们会导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。

块外的goto语句不能跳转到该块内的标签。如下面代码所示,由于L1在for里面,这很明显是错误的。

代码不能通过编译:

和break语句不一样,goto语句是可以跳到后面出现的标签的,前提是满足块外的goto语句不能跳转到该块内的标签。例如:

程序输出: