《C语言程序设计现代方法》note-3 选择语句 循环语句
助记提要
- 关系运算符、判等运算符、逻辑运算符的优先级和结合性;
- 条件运算符;
- C语言中如何使用布尔值;
- switch语句的注意;
- for语句省略表达式;
- 逗号表达式;
- goto语句;
- 空语句;
不应该以聪明才智和逻辑分析能力来评判程序员,而要看其分析问题是否全面。
5章 选择语句
5.1 逻辑表达式
选择语句必选先测试表达式的值是真还是假。
构建逻辑表达式的运算符有9个。
逻辑表达式的值只会是1(真)或0(假)。
- 关系运算符
大于>
、小于<
、大于等于>=
、小于等于<=
4个关系运算符都是左结合的,优先级低于算术运算符。
注意 i < j < k
在C语言中是合法的,但是并不是检测j是否位于i和k之间。它先比较i<j
的值,然后用这个结果和k比较。
- 判等运算符
等于==
、不等于!=
判等运算符是左结合的,优先级低于关系运算符。
表达式i < j == j < k
,当i < j
和j < k
同为真或同为假时,表达式的值为真。
==
经常被误写为=
,有的时候编译器不会警告,导致程序出现错误。使用gcc编译C程序时可以指定-Wall
选项,强制编译器检查这类书写错误。程序员可以在if条件外面多加一层圆括号来禁用该警告:if ((i = j)) ...
- 逻辑运算符
非!
、与&&
、或||
!
是一元的右结合运算符,优先级和正负号相同。
&&
和||
是二元的左结合运算符,优先级低于判等运算符。
运算符&&
和||
都会进行短路计算:先计算出左操作数的值,然后计算右操作数。如果表达式的结果可以由左操作数推导出来,就不计算右操作数。
注意 由于&&
和||
的短路特性,有些运算不一定会发生,如:i > 0 && ++j > 0
,如果i不大于0,j的自增操作就不会发生。
5.2 if语句
语法
- 基本语法格式
if (表达式) 语句;
先计算表达式的值,表达式值非零,就执行圆括号后面的语句。
C语言中,非零值都为真。
- 复合语句
if (表达式){
语句1;
语句2;
...
}
花括号包起来的语句称为复合语句,它会强制编译器将一组语句做为一条语句来处理。
为语句添加花括号,可以使程序更易读。也可以更方便地向语句块中添加语句。
- else子句
if (表达式) 语句1; else 语句2;
if (表达式) {
语句11;
语句12
} else {
语句21;
语句22;
}
注意 else子句会归属于离它最近且还未和其他else匹配的if语句。
下面的这种写法,按缩进来理解,else应该属于外层的if语句,但是实际上它是属于内层的if语句的。这时最好把内层if语句使用花括号包起来。
if (y != 0)
if (x != 0)
result = x / y;
else
printf("Error: y == 0\n");
- 级联if语句
级联式if语句可以避免数量过多的内嵌if语句
if (表达式)
语句1;
else if (表达式)
语句2;
else
语句3;
级联式if语句不是新的语句类型,只是将另外一个if语句做为else字句。
条件运算符
条件运算符的作用类似if语句,它允许根据条件的结果,产生两个值中的一个。条件运算符构建的条件表达式语法如下:
表达式1 ? 表达式2 : 表达式3
如果表达式1成立,就计算表达式2的值做为条件表达式的结果,否则计算表达式3的值做为结果。
条件运算符是唯一的一个三元运算符。它的优先级低于所有的运算符。
条件表达式短小但是难以阅读,应该避免使用。但在少数地方使用会很方便:
// return语句使用
return i > j ? i : j
// printf函数使用
printf("%d\n", i > j ? i : j);
条件表达式i > 0 ? i : j
中,如果i是int
型,j是float
型,即使i > 0
为真,表达式的结果也是i转换后的float
型。
C语言中的布尔值
C89没有定义布尔类型,但是很多程序中需要变量存储真或假值。
程序员经常使用TRUE和FALSE这样的名字来定义宏:
#define TRUE 1
#define FALSE 0
使用时,可以通过这两个宏去赋值。判定条件时可以这样:
if (flag == TRUE) ...
// 如果flag为真
if (flag) ...
// 如果flag为假
if (!flag) ...
第二种方式更好,因为当flag的值不是0或1时程序也能正确运行。
或者定义一个可以用作类型的宏:
#define BOOL int
BOOL flag;
C99中提供了_Bool
类型,用来声明布尔变量。_Bool
变量实际上就是整型变量,但是只能赋值为0或1,存储非0值会导致变量被赋值为1。
_Bool flag;
C99还提供了一个新的头<stdbool.h>
,这个头提供了宏bool
,用来代表_Bool
;还提供了true
代表1,false
代表0。
引用这个头文件,可以很方便地使用布尔值:
bool flag;
flag = false;
flag = true;
5.3 switch语句
需要把表达式和一系列的值进行比较时,可以使用级联式的if语句或switch语句。
switch (表达式){
case 常量表达式1: 语句1;
...
case 常量表达式n: 语句n;
default : 语句;
}
switch后面的表达式必须是整型表达式,不能是浮点数和字符串。
case:常量表达式
称为分支标号。其中常量表达式也必须是整型表达式,不能包含变量和函数调用。
分支标号只能带一个常量表达式,但是分支标号后面可以跟任意数量的语句,并且不需要花括号括起来。每组语句的最后一句通常是break
语句。
分支标号不允许重复,但是没有顺序要求,default分支不是必须写在最后。多个分支标号可以放在同一组语句的前面。
default分支可以没有。
break
语句的作用
switch语句实际上是一种基于计算的跳转。控制表达式求值时,控制会跳转到与switch表达式的值相匹配的分支标号处。分支标号只是一个说明switch内部位置的标记,不影响程序控制流。执行完这个分支的最后一条语句后,程序会忽略分支标号,向下跳转到下一个分支的第一条语句上。
break
语句会让控制跳转到switch语句的下面。
switch的最后一个分支不需要break
语句,但是也会加一个,以防后续增加分支数目时忘记写break
语句。
6章 循环
6.1 while语句
表达式为真,就执行后面的语句。持续执行,直到表达式为假。
while (表达式) 语句;
表达式后面的语句称为循环体。循环体可以有多条语句,使用花括号括起来即可。
如果控制表达式始终为真,while语句会无限循环下去。需要的时候常常用非零常量来构造无限循环。
while (1) ...
6.2 do语句
do语句先执行循环体,然后计算控制表达式的值,如果表达式是非零的,就再次执行循环体,然后再次计算表达式的值。直到表达式的值为0,就终止do语句。
do 语句 while (表达式);
循环体可以是一条语句,也可以是花括号包起来的复合语句。
使用时最好给所有的do语句都加上花括号,因为没有花括号的do语句容易被误认为while语句。
6.3 for语句
for (声明或表达式1; 表达式2; 表达式3) 语句;
表达式1是循环开始前的初始化步骤,只执行1次;表达式2用来控制循环,表达式2的值不为0,循环就会一直执行下去;表达式3是每次循环中都会最后执行的操作。
由于表达式1和表达式3都是以语句的方式执行的,互不相关,它们经常被用做赋值表达式或自增/自减表达式。
for循环可以使用等价的while循环替换:
表达式1;
while (表达式2) {
语句;
表达式3;
}
for语句常用的形式
for语句很适合用在使用计数变量的循环中。for语句实现“向上加”或“向下减”n次的循环的一般格式:
// 向上加,从0加到n-1
for (i = 0; i < n; i++) ...
// 向上加,从1加到n
for (i = 1; i <= n; i++) ...
// 向下减,从n-1到0
for (i = n - 1; i >= 0; i--) ...
// 向下减,从n到1
for (i = n; i > 0; i--) ...
使用时要注意<
和<=
的使用效果,避免循环次数出错。
for循环省略表达式
C语言允许省略for循环的表达式。
省略表达式1,执行循环前没有初始化操作,相关操作需要放到循环前进行。
for (;表达式2;表达式3) ...
省略表达式3,则需要在循环体中确保表达式2最终会变为假。
for (表达式1;表达式2;) ...
同时省略表达式1和表达式3,产生的效果和while语句一样。这时使用while语句更好。
for (;表达式2;) ...
while (表达式2) ...
省略表达式2,默认判定为真,for语句进入无限循环:
for (;;) ...
声明用于循环的变量
C99中,表达式1可以替换为一个声明:
for (int i = 0; i < n; i++) ...
变量i不需要在for语句之前声明。
如果for语句之前已经声明了变量i,表达式1会创建一个新的i仅用于循环内。这样声明的变量不可以在循环外访问。
for语句可以声明多个同类型的变量。
逗号运算符
逗号运算符是为了在C语言要求只能有一个表达式的情况下,可以使用两个或更多表达式。
比如想在for语句中,编写多个初始化表达式,或者在每次循环中对多个变量执行自增操作。
表达式1, 表达式2
逗号表达式先计算表达式1的值,然后扔掉。接着计算表达式2的值,将这个值做为整个表达式的值。
表达式1应该始终都有副作用(更改变量值),否则就没有意义。
for (sum = 0, i = 1; i <= n; i++)
sum += i;
6.4 退出循环
- break
break
可以跳出while、do或for循环。
它适用于需要把退出点设置在循环体中间而不是之前或之后的情况。
break
可以把程序控制从包含它的最内层while、do、for或switch语句中转移出去。
这些语句嵌套出现时,break只能跳出一层嵌套。
-
continue
continue
可以跳过某次迭代的部分内容。
它只能用在循环中,并且把程序控制转移到循环体末尾之前,不跳出循环,直接进入下一次迭代。 -
goto
break
和continue
都是跳转语句,它们都有特定的跳转点。break
跳到包含它的循环结束之后的点,continue
跳到循环结束之前的点。
goto
语句可以跳转到函数中任何有标号的语句处。
标号是放在语句开始处的标识符。
标识符: 语句;
goto 标识符;
执行goto
语句时,控制会转移到标识符后面的语句上。该语句必须和goto
处于同一个函数。
goto语句在早期的编程语言中很常见,但是日常编程中很少用了。break、continue、return和exit函数可以代替需要使用goto的大多数情况。
goto语句既可以往前跳又可以往后跳,而且多个goto语句可以到达同一个标号,这会使代码难以理解和修改。
goto语句很有用的地方在于,能一次跳出多层嵌套的循环:
while (...) {
while (...){
...
// 这里使用break只能跳出一层
goto loop_done;
...
}
}
loop_done: ...;
6.5 空语句
空语句是除了末尾的分号外什么也没有的语句。
空语句可以用来编写空循环体的循环:
for (d = 2; d < n && n % d != 0; d++)
;
空语句最好单独放在一行,避免阅读程序时分不清后面的语句是否是循环体。可以使用其它写法凸显空循环体:
for (d = 2; d < n && n % d != 0; d++)
continue;
for (d = 2; d < n && n % d != 0; d++)
{}
循环转变为空循环体的循环后,通常不会提升效率。
注意 如果不小心在if
、while
或for
语句的圆括号后面加了分号,就会创建空语句,导致if
、while
或for
语句提前结束。
if
语句的圆括号后加分号,条件会失效,后面的语句总会被执行;
while
语句的圆括号后加分号,会造成无限循环。即使循环终止,也只执行一次循环体。
for
语句的圆括号后加分号,只执行一次循环体。