- 作者:老汪软件技巧
- 发表时间:2023-12-28 22:00
- 浏览量:
文章目录
C现代方法笔记(&6) 第5章 选择语句 5.1 逻辑表达式
包括if语句在内的某些C语句必须测试表达式的值是“真”还是“假”。例如,if语句可能需要检测表达式i < j,若取得真值则说明i小于j。在许多编程语言中,类似i < j这样的表达式都具有特殊的“布尔”类型或“逻辑”类型。这样的类型只有两个值,即假和真。而在C语言中,i < j这样的比较运算会产生整数:0(假)或1(真)。先记住这一点,下面来看看用于构建逻辑表达式的运算符。
5.1.1 关系运算符
C语言的关系运算符( )跟数学上的、≤和≥运算符相对应,只不过用在C语言的表达式中时产生的结果是0(假)或1(真)。例如,表达式10 < 11的值为1,而表达式11 < 10的值为0。
关系运算符可以用于比较整数和浮点数,也允许比较混合类型的操作数。
从优先级上来看,关系运算符的优先级低于算术运算符。
请注意,表达式i < j < k在C语言中是合法的,但是由于 0 ? i: f)是哪一种类型的值?
答:如问题所述,当int型和float型的值混合在一个条件表达式中时,表达式的类型为float型。如果i > 0为真,那么变量i转换为float型后的值就是表达式的值。
问3:为什么_Bool这个名字就不会影响已有的程序呢?
答:C89标准指出,以下划线开头,后跟一个大写字母的名字是保留字,程序员不应该使用。
第6章 循环
循环(loop)是重复执行其他语句(循环体)的一种语句。在C 语言中,每个循环都有一个控制表达式( )。每次执行循环体(循环重复一次)时都要对控制表达式求值。如果表达式为真(即值不为零),那么继续执行循环。
C语言提供了3种重复语句,即while语句、do语句和for语句。while循环在循环体执行之前测试控制表达式,do循环在循环体执行之后测试控制表达式,for语句则非常适合那些递增或递减计数变量的循环。
本章最后两节致力于讨论与循环相关的C语言特性。break语句用来跳出循环并把程序控制传递到循环后的下一条语句,语句用来跳过本次循环的剩余部分,而goto语句则可以跳到函数内的任何语句上。
6.1 while语句
在C语言所有设置循环的方法中,while语句是最简单也是最基本的。while语句的格式如下所示:
while (表达式) 语句
圆括号内的表达式是控制表达式,圆括号后边的语句是循环体。
执行while语句时,首先计算控制表达式的值。如果值不为零(即真值),那么执行循环体,接着再次判定表达式。这个过程(先判定控制表达式,再执行循环体)持续进行,直到控制表达式的值变为零才停止。
请注意,虽然循环体必须是单独的一条语句,但这只是个技术问题;如果需要多条语句,那么只要用一对花括号构造成一条复合语句就可以了,比如:
while (i > 0) {
printf("flsdjfklsdjlkfjklsdj");
i--;
}
6.1.1 无限循环
如果控制表达式的值始终非零(为真),while语句将无法终止。事实上,C程序员有时故意用非零常量作为控制表达式来构造无限循环:
[惯用法] while (1) ...
除非循环体中含有跳出循环控制的语句(break、goto、)或者调用了导致程序终止的函数,否则上述形式的while语句将永远执行下去。
小知识:如何把输出整齐地排成两列?窍门是使用类似d这样的转换说明代替%d,并利用了函数在指定宽度内输出右对齐的特性。
下面是一个利用循环进行数列求和的例子:
/* Sums a series of numbers */
#include
int main(void)
{
int n, sum = 0;
printf("This program sums a series of integers.\n");
printf("Enter integers (0 to terminate): ");
scanf("%d", &n);
while (n != 0) {
sum += n;
scanf("%d", &n);
}
printf("The sum is: %d\n", sum);
return 0;
}
/*
This program sums a series of integers.
Enter integers (0 to terminate): 8 23 71 5 0
The sum is: 107
*/
注意,条件n != 0在数被读入后立即进行判断,这样可以尽快终止循环。此外,程序中用到了两个完全一样的scanf函数调用,在使用while循环时往往很难避免这种现象。
6.2 do语句
do语句和while语句关系紧密。事实上,do语句本质上就是while语句,只不过其控制表达式是在每次执行完循环体之后进行判定的。do语句的格式如下所示:
do 语句 while (表达式)
和处理while语句一样,do语句的循环体也必须是一条语句(当然可以用复合语句),并且控制表达式的外面也必须有圆括号。
执行do语句时,先执行循环体,再计算控制表达式的值。如果表达式的值是非零的,那么再次执行循环体,然后再次计算表达式的值。在循环体执行后,若控制表达式的值变为0,则终止do语句的执行。
do语句和while语句往往难以区别。两种语句的区别是,do语句的循环体至少要执行一次,而while语句在控制表达式初始值为0时会完全跳过循环体。
下面是一个利用do循环计算整数的位数的例子:
/* Calculates the number of digits in an integer */
#include
int main(void)
{
int digits = 0, n;
printf("Enter a nonnegative integer: ");
scanf("%d", &n);
do {
n /= 10;
digits++;
} while (n > 0);
printf("The number has %d digit(s).\n", digits);
return 0;
}
/*
Enter a nonnegative integer: 60
The number has 2 digit(s).
*/
6.3 for语句
for语句是C语言循环中最后一种循环,也是功能最强大的一种循环。不要因为for语句表面上的复杂性而灰心;实际上,它是编写许多循环的最佳方法。for语句非常适合应用在使用“计数”变量的循环中,当然它也可以灵活地用于许多其他类型的循环中。格式如下:
for (声明或者表达式1;表达式2;表达式3) 语句
其中表达式1、表达式2 和表达式3全都是表达式。下面是一个例子:
for (i = 10; i > 0; i--)
printf("T minus %d and counting\n",i);
/*
在执行for语句时,变量i先初始化为10,接着判定i是否大于0。因为判定的结果为真,所以打印信息T minus 10 and counting,然后变量i进行自减操作。随后再次对条件i > 0进行判定。循环体总共执行10次,在这一过程中变量i从10变化到1。
*/
for语句和while语句关系紧密。事实上,除了一些极少数的情况以外,for循环总可以用等价的while循环替换:
表达式1;
while (表达式2) {
语句
表达式3;
}
6.3.1 for语句的惯用法
对于“向上加”(变量自增)或“向下减”(变量自减)的循环来说,for语句通常是最好的选择。对于向上加或向下减共n次的情况,for语句经常会采用下列形式中的一种。
模仿上面的书写格式有助于避免C语言初学者常犯的下列错误:
6.3.2 在for语句中省略表达式
通常for语句用三个表达式控制循环,但是有一些for循环可能不需要这么多,因此C语言允许省略任意或全部的表达式。
注意,保留第一个表达式和第二个表达式之间的分号。即使省略掉某些表达式,控制表达式也必须始终有两个分号。
某些程序员用下列for语句建立无限循环:for (;;) ...
6.3.3 C99中的for语句
在C99中,for语句的第一个表达式可以替换为一个声明,这一特性使得程序员可以声明一个用于循环的变量:
for (int i = 0; i < n; i++)
变量i不需要在该语句前进行声明。事实上,如果变量i在之前已经进行了声明,这个语句将创建一个新的i且该值仅用于循环内。
for语句声明的变量不可以在循环外访问(在循环外不可见)。顺便提一下,for语句可以声明多个变量,只要它们类型相同。
for (int i = 0, j = 0; i < n; i++)
....
6.3.4 逗号运算符
有些时候,我们可能喜欢编写有两个(或更多个)初始表达式的for语句,或者希望在每次循环时一次对几个变量进行自增操作。使用逗号表达式(comma )作为for语句中第一个或第三个表达式可以实现这些想法。
逗号表达式的格式如下所示:
表达式1, 表达式2
这里的表达式1和表达式2是两个任意的表达式。逗号表达式的计算要通过两步来实现:
i = 1, j = 2, k = i + j
/*
因为逗号表达式的左操作数在右操作数之前求值,所以赋值运算i = 1、j = 2 和k = i + j是从左向右进行的。
*/
提供逗号运算符是为了在C语言要求只能有一个表达式的情况下,可以使用两个或多个表达式。换句话说,逗号运算符允许将两个表达式“粘贴”在一起构成一个表达式。(注意它与复合语句的相似之处,后者允许我们把一组语句当作一条语句来使用。)
需要把多个表达式粘在一起的情况不是很多。正如后面的某一章将介绍的那样,某些宏定义( 14.3 节)可以从逗号运算符中受益。除此之外,for语句是唯一可以发现逗号运算符的地方。
6.4 退出循环
然而,有些时候也需要在循环中间设置退出点,甚至可能需要对循环设置多个退出点。break语句可以用于有上述这些需求的循环中。
语句会跳过某次迭代的部分内容,但是不会跳出整个循环。goto语句允许程序从一条语句跳转到另一条语句。因为已经有了break和这样有效的语句,所以很少使用goto语句。
6.4.1 break语句
break语句把程序控制从语句中转移出来,还可以用于跳出while、do或for循环。
值得注意的是,break语句把程序控制从包含该语句的最内层while、do、for或语句中转移出来。因此,当这些语句出现嵌套时,break语句只能跳出一层嵌套。
while (...) {
switch (...) {
...
break;
...
}
}
/*
break语句可以把程序控制从switch语句中转移出来,
但是不能跳出while循环。
*/
6.4.2 语句
break语句刚好把程序控制转移到循环体末尾之后,而语句刚好把程序控制转移到循环体末尾之前。break语句会使程序控制跳出循环,而语句会把程序控制留在循环内。break语句和语句的另外一个区别是,break语句可以用于语句和循环(while、do和for),而语句只能用于循环。
6.4.3 goto语句
goto语句在早期编程语言中很常见,但在日常C语言编程中已经很少用到它了。
break、、语句(本质上都是受限制的goto语句)和exit函数( 9.5 节)足以应付在其他编程语言中需要goto语句的大多数情况。
goto语句则可以跳转到函数中任何有标号的语句处。[C99增加了一条限制:goto语句不可以用于绕过变长数组( 8.3 节)的声明。]
执行语句goto L;,控制会转移到标号L后面的语句上,而且该语句必须和goto语句在同一个函数中。
while (...) {
switch (...) {
...
goto loop_done; /* break won’t work here */
...
}
}
loop_done: ...
//goto语句对于嵌套循环的退出也是很有用的。
6.5 空语句
语句可以为空,也就是除了末尾处的分号以外什么符号也没有。下面是一个示例:
i = 0; ; j = 1;
这行含有三条语句:一条语句是给i赋值,一条是空语句,还有一条是给j赋值。
空语句主要有一个好处:编写空循环体的循环。(注意空语句单独放置在一行。)
for (d = 2; d < n && n % d != 0; d++)
/* empty loop body */;
C程序员习惯性地把空语句单独放置在一行。否则,有些人阅读程序时可能会搞不清for语句后边的语句是否是其循环体。
请注意!如果不小心在if、while或for语句的圆括号后放置分号,则会创建空语句,从而造成if、while或for语句提前结束。
问与答
问1:
while (i > 0)
printf("T minus %d and counting\n", i--);
为什么不删除“> 0”判定来进一步缩短循环呢?
while (i)
printf("T minus %d and counting\n", i--);
这种写法的循环会在i 达到0 值时停止,所以它应该和原始版本一样好。
答:新写法确实更加简洁,许多C程序员也这样写循环。但是,它也有缺点。
首先,新循环不像原始版本那样容易阅读。新循环可以清楚地显示出在i达到0值时循环终止,但是不能清楚地表示是向上计数还是向下计数。而在原始的循环中,根据控制表达式i > 0可以推断出这一信息。
其次,如果循环开始执行时i碰巧为负值,那么新循环的行为会不同于原始版本。原始循环会立刻终止,而新循环则不会。
问2:6.3节提到,大多数for循环可以利用标准模式转换成while循环。能给出一个反例吗?
答:当for循环体中含有语句时,6.3节给出的while模式将不再有效。
问3:哪个无限循环格式更可取,while(1)还是for(;;)?
答:C程序员传统上喜欢for(;;)的高效性。这是因为早期的编译器经常强制程序在每次执行while循环体时测试条件1。但是,对于现代编译器来说,在性能上两种无限循环应该没有差别。
问4:听说程序员应该永不使用语句。这种说法对吗?
答:语句的确很少使用。尽管如此,语句有时还是非常方便的。假设我们编写的循环要读入一些输入数据并测试其有效性,如果有效则以某种方法进行处理。如果有许多有效性测试,或者如果它们都很复杂,那么语句就非常有用了。循环将类似于下面这样:
for(; ;) {
读入数据;
if(数据的第一条测试失败)
continue;
if(数据的第二条测试失败)
continue; . . .
if(数据的最后一条测试失败)
continue;
处理数据;
}
问5:goto语句有什么不好?
答:goto语句不是天生的魔鬼,只是通常它有更好的替代方式。使用过多goto语句的程序会迅速退化成“垃圾代码”,因为控制可以随意地跳来跳去。垃圾代码是非常难于理解和修改的。
问6:除了说明循环体为空外,空语句还有其他用途吗?
答:非常少。空语句可以放在任何允许放语句的地方,所以有许多潜在的用途。但在实际中,空语句只有一种别的用途,而且极少使用。
问7:除了把空语句单独放置在一行以外,是否还有其他方法可以凸显出空循环体?
答:一些程序员使用虚设的语句:
for (d = 2; d < n && n % d != 0; d++)
continue;
还有一些人使用空的复合语句:
for (d = 2; d < n && n % d != 0; d++)
{}
写在最后
本文是作者阅读《C语言程序设计:现代方法(第2版·修订版)》时所做笔记,日后会持续更新后续章节笔记。欢迎各位大佬批评指正,希望对诸位有所帮助,Thank you very much!