Control Flow
程序语言中的控制流语句用于控制各计算操作执行的次序。
1. 语句与程序块
表达式之后加上一个分号(;
),它们就变成了语句。
在C语言中,分号是语句结束符。
用一对花括号“{
”与“}
”把一组声明和语句括在一起就构成了一个复合语句(也叫作程序块),复合语句在语法上等价于单条语句。函数体中被花括号括起来的语句便是明显一例。if
、else
、while
与for
之后被花括号括住的多条语句也是类似的例子。右花括号用于结束程序块,其后不需要分号。
2. if-else
语句
if-else
语句用于条件判定。
1 | if (expression) |
其中else
部分是可选的。该语句执行时,先计算表达式的值,如果其值为真(即表达式的值为非0),则执行语句1;如果其值为假(即表达式的值为0),并且该语句包含else
部分,则执行语句2。
由于if
语句只是简单测试表达式的数值,因此可以对某些代码的编写进行简化。最明显的例子是用如下写法if (expression)
来代替if (expression != 0)
。某些情况下这种形式是自然清晰的,但也有某些情况下可能会含义不清。
因为if-else
语句的else
部分是可选的,所以在嵌套的if
语句中省略它的else
部分将导致歧义。解决的方法是将每个else
与最近的前一个没有else
配对的if
进行匹配。…如果这不符合我们的意图,则必须使用花括号强制实现正确的匹配关系。
…建议在有if
语句嵌套的情况下使用花括号。
…从语法上讲,跟在if
后面的应该是一条语句。
3. else-if
语句
1 | if (expression) |
这种if
语句序列是编写多路判定最常用的方法。其中的各表达式将被依次求值,一旦某个表达式结果为真,则执行与之相关的语句,并终止整个语句序列的执行。同样,其中各语句既可以是单条语句,也可以是用花括号括住的复合语句。
最后一个else
部分用于处理“上述条件均不成立”的情况或默认情况,也就是当上面各条件都不满足时的情形。有时候并不需要针对默认情况执行显式的操作,这种情况下,可以把该结构末尾的else
部分省略掉;该部分也可以用来检查错误,以捕获“不可能”的条件。
4. switch
语句
switch
语句是一种多路判定语句,它测试表达式是否与一些常量整数值中的某一个值匹配,并执行相应的分支动作。
1 | switch (expression) { |
每一个分支都由一个或多个整数值常量或常量表达式标记。如果某个分支与表达式的值匹配,则从该分支开始执行。各分支表达式必须互不相同。如果没有哪一分支能匹配表达式,则执行标记为default
的分支。default
分支是可选的。如果没有default
分支也没有其他分支与表达式的值匹配,则该switch
语句不执行任何动作。各分支及default
分支的排列次序是任意的。
break
语句将导致程序的执行立即从switch
语句中退出。在switch
语句中,case
的作用只是一个标号,因此,某个分支中的代码执行完后,程序将进入下一分支继续执行,除非在程序中显式地跳转。跳出switch
语句最常用的方法是使用break
语句与return
语句。
依次执行各分支的做法有优点也有缺点。好的一面是它可以把若干个分支组合在一起完成一个任务。但是,正常情况下为了防止直接进入下一个分支执行,每个分支后必须以一个break
语句结束。从一个分支直接进入下一个分支执行的做法并不健全,这样做在程序修改时很容易出错。除了一个计算需要多个标号的情况下,应尽量减少从一个分支直接进入下一个分支执行这种用法,在不得不使用的情况下应该加上适当的程序注释。
作为一种良好的程序设计风格,在switch
语句最后一个分支(即default
分支)的后面也加上一个break
语句。这样做在逻辑上没有必要,但当我们需要向该switch
语句后添加其他分支时,这种防范措施会降低犯错误的可能性。
5. while
循环与for
循环
在while
循环语句
1 | while (expression) |
中,首先求表达式的值。如果其值为真非0,则执行语句,并再次求该表达式的值。这一循环过程一直进行下去,直到该表达式的值为假(0)为止,随后继续执行语句后面的部分。
for
循环语句:
1 | for (expression1; expression2; expression3) |
它等价于下列while
语句:
1 | expression1; |
但当while
或for
循环语句中包含continue
语句时,上述二者之间就不一定等价了。
从语法角度看,for
循环语句的3个组成部分都是表达式。最常见的情况是,表达式1与表达式3是赋值表达式或函数调用,表达式2是关系表达式。这3个组成部分中的任何部分都可以省略,但分号必须保留。如果在for
语句中省略表达式1与表达式3,它就退化成了while
循环语句。如果省略测试条件,即表达式2,则认为其值永远是真值,因此,下列for
循环语句:
1 | for (;;) { |
是一个“无限”循环语句,这种语句需要借助其他手段(如break
语句或return
语句)才能终止循环。
在设计程序时到底选用while
循环语句还是for
循环语句,主要取决于程序设计人员的个人偏好。…因为其中没有初始化或重新初始化的操作,所以使用while
循环语句更自然一些。
如果语句中需要执行简单的初始化和变量递增,使用for
语句更合适一些,它将循环控制语句集中放在循环的开头,结构更紧凑、更清晰。通过下列语句可以很明显地看出这一点:
1 | for (i = 0; i < n; i++) |
这是C语言处理数组前n个元素的一种习惯性用法。…在C语言中,for
循环语句的循环变量和上限在循环体内可以修改,并且当循环因某种原因终止后循环变量i的值任然保留。因为for
语句的各组成部分可以是任何表达式,所以for
语句并不限于通过算术级数进行循环控制。尽管如此,牵强地把一些无关的计算放到for
语句的初始化和变量递增部分是一种不好的程序设计风格,该部分放置循环控制运算更合适。
把循环控制部分集中在一起,对于多重嵌套循环,优势更为明显。…注意,即使最外层for
循环的控制变量不是算术级数,for
语句的书写形式任然没有变,这就说明for
语句具有很强的通用性。
逗号运算符“,
”也是C语言优先级最低的运算符,在for
语句中经常会用到它。被逗号分隔的一对表达式将按照从左到右的顺序进行求值,各表达式右边的操作数的类型和值即为其结果的类型和值。这样,在for
循环语句中,可以将多个表达式放在各个语句成分中,比如同时处理两个循环控制变量。
某些情况下的逗号并不是逗号运算符,比如分隔函数参数的逗号,分隔声明中变量的逗号等,这些逗号并不保证各表达式按从左至右的顺序求值。
应该慎用逗号运算符。逗号运算符最适用于关系紧密的结构中,…对于需要在单个表达式中进行多步计算的宏来说也很适合。…这样,元素的交换过程便可以看成是一个单步操作。
6. do-while
循环
while
与for
这两种循环在循环体执行前对终止条件进行测试。与此相反,C语言中的第三种循环——do-while
循环则在循环体执行后测试终止条件,这样循环体至少被执行一次。
do-while
循环的语法形式如下:
1 | do |
在这一结构中,先执行循环体中的语句部分,然后再求表达式的值。如果表达式的值为真,则再次执行语句,依此类推。当表达式的值变为假,则循环终止。
经验表明,do-while
循环比while
循环和for
循环用得少得多。尽管如此,do-while
循环语句有时还是很有用的。
…其中的do-while
语句体中只有一条语句,尽管没有必要,但我们任然用花括号将该语句括起来了,这样做可以避免草率的读者将while
部分误认为是另一个while
循环的开始。
7. break
语句与continue
语句
不通过循环头部或尾部的条件测试而跳出循环,有时是很方便的。break
语句可用于从for
、while
与do-while
等循环中提前退出,就如同从swtich
语句中提前退出一样。break
语句能使程序从switch
语句或最内层循环中立即跳出。
continue
语句与break
语句是相关联的,但它没有break
语句常用。continue
语句用于使for
、while
或do-while
语句开始下一次循环的执行。在while
与do-while
语句中,continue
语句的执行意味着立即执行测试部分;在for
循环中,则意味着使控制转移到递增循环变量部分。continue
语句只用于循环语句,不用于switch
语句。某个循环包含的switch
语句中的continue
语句,将导致进入下一次循环。
当循环的后面部分比较复杂时,常常会用到continue
语句。这种情况下,如果不适用continue
语句,则可能需要把测试颠倒过来或者缩进另一层循环,这样做会使程序的嵌套更深。
8. goto
语句与标号
C语言提供了可随意滥用的goto
语句以及标记跳转位置的标号。从理论上讲,goto
语句是没有必要的,实践中不使用goto
语句也可以很容易地写出代码。
但是,在某些场合下goto
语句还是用得着的。最常见的用法是终止程序在某些深度嵌套的结构中的处理过程,例如一次跳出两层或多层循环。这种情况下使用break
语句是不能达到目的的,它只能从最内层循环退出到上一级的循环。下面是使用goto
语句的一个例子:
1 | for (...) |
在该例子中,如果错误处理代码很重要,并且错误可能出现在多个地方,使用goto
语句将会比较方便。
标号的命名同变量命名的形式相同,标号的后面要紧跟一个冒号。标号可以位于对应的goto
语句所在函数的任何语句的前面。标号的作用域是整个函数。
所有使用了goto
语句的程序代码都能改写成不带goto
语句的程序,但可能会增加一些额外的重复测试或变量。
大多数情况下,使用goto
语句的程序段比不适用goto
语句的程序段要难以理解和维护,少数情况除外。尽管该问题并不太严重,但我们还是建议尽可能少地使用goto
语句。