17. C Primer Plus 7.2.5、7.3以及7.4
7.2.5、7.3及7.4
7.2.5多层嵌套的if语句
前面介绍的if...else if...else序列是嵌套if的一种形式,从一系列选项中选 择一个执行。有时,选择一个特定选项后又引出其他选择,这种情况可以使 用另一种嵌套 if。例如:程序可以使用 if else选择男女,if else的每个分支里 又包含另一个if else来区分不同收入的群体。
我们把这种形式的嵌套if应用在下面的程序中:给定一个整数,显示所 有能整除它的约数。如果没有约数,则报告该数是一个素数。
在编写程序的代码之前要先规划好。首先,要总体设计一下程序。为方 便起见,程序应该使用一个循环让用户能连续输入待测试的数。这样,测试 一个新的数字时不必每次都要重新运行程序。
下列为我们为这种循环开发的一个模型(伪代码):
提示用户输入数字
当scanf()返回值为1
分析该数并报告结果
提示用户继续输入
在测试条件中使用scanf(),把读取数字和判断测试条件确定是 否结束循环合并在一起。
下一步,涉及如何找出约数,最直接的方法是:

该循环检查2~num之间的所有数字,测试它们是否能被num整除。但 是,这个方法有点浪费时间。
为了弄清其中的原理,我们分 析一下循环中得到的成对约数:2和72、2和48、4和36、6和24、8和18、9和 16、12和12、16和9、18和8,等等。在得到12和12这对约数后,又开始得到 已找到的相同约数(次序相反)。因此,不用循环到143,在达到12以后就 可以停止循环。
分析后发现,必须测试的数只要到num的平方根就可以了,不用到 num。对于9这样的数字,不会节约很多时间,但是对于10000这样的数,使 用哪一种方法求约数差别很大。不过,我们不用在程序中计算平方根,我们可以使用下列方法编写测试条件:

如果num是144,当div = 12时停止循环。如果num是145,当div = 13时停止循环。
注意:不使用平方根而用这样的测试条件,有两个原因。其一,整数乘法比求 平方根快。其二,我们还没有正式介绍平方根函数。
仍需解决的问题:1.如果待测试的数是一个完全平方数该怎么办?
2.如何知道一个数字是否为素数。
解决第一个问题的方法:可以使用嵌套if语句测试div是否等于num/div。如果是等于的话,程序只打印一个约数:

注意:从技术角度看,if else语句作为一条单独的语句,不必使用花括号。外 层if也是一条单独的语句,也不必使用花括号。但是,当语句太长时,使用花括号能提高代码的可读性,而且还可防止今后在if循环中添加其他语句时 忘记加花括号。**
解决第二个问题的方法:如果num是素数,程序流不会 进入if语句。要解决这个问题,可以在外层循环把一个变量设置为某个值 (如,1),然后在if语句中把该变量重新设置为0。循环完成后,检查该变 量是否是1,如果是,说明没有进入if语句,那么该数就是素数。这样的变量通常被称为标记(flag)。
一直以来,C都习惯用int作为标记的类型,其实新增的 _ Bool类型更合适。另外,如果在程序中包含了stdbool.h头文件,便可用bool代替_Bool类 型,用true和false分别代替1和0。
下列示例程序体现了以上程序分析的思路:


注意:该程序在for循环的测试表达式中使用了逗号运算符,这样每次输入新值时都可以把isPrime设置为true。
输出结果如下:


缺点:该程序会把1认为是素数,其实它不是。**逻辑运算符可以排除这种特殊情况。
总结:用if语句进行选择
关键字:if、else
一般注解:下面各形式中,statement可以是一条简单语句或复合语句。表达式为真 说明其值是非零值。
形式1:
if(expression)
statement
**如果expression为真,则执行statement部分。
形式2:**
if(expression)
statement1
else
statement2
如果expression为真,执行statement1部分;否则,执行statement2部分。
形式3:
if(expression)
statement1
else if(expression2)
statement2
else
statement3
如果expression1为真,执行statement1部分;如果expression2为真,执行 statement2部分;否则,执行statement3部分。
示例:

7.3逻辑运算符
if 语句和 while 语句通常使用关系表达式作为测试 条件。有时,把多个关系表达式组合起来会很有用。。例如,要编写一个程 序,计算输入的一行句子中除单引号和双引号以外其他字符的数量。这种情 况下可以使用逻辑运算符,并使用句点(.)标识句子的末尾。下列示例程序演示了逻辑运算符的用法:

输出结果如下:

程序首先读入一个字符,并检查它是否是一个句点,因为句点标志一个 句子的结束。接下来,if语句的测试条件中使用了逻辑与运算符&&。该 if 语句翻译成文字是“如果待测试的字符不是双引号,并且它也不是单引号, 那么charcount递增1”。
逻辑运算符两侧的条件必须都为真,整个表达式才为真。逻辑运算符的 优先级比关系运算符低,所以不必在子表达式两侧加圆括号
下图是C语言中所有的逻辑运算符:

假设exp1和exp2是两个简单的关系表达式(如car > rat或debt == 1000),那么:
当且仅当exp1和exp2都为真时,exp1 && exp2才为真;
如果exp1或exp2为真,则exp1 || exp2为真;
如果exp1为假,则!exp1为真;如果exp1为真,则!exp1为假。
例如下面具体的示例:

如果不熟悉逻辑运算符或者觉得很别扭,请记住:(练习&&时间)== 完 美。**
7.3.1备选拼写:ISO646.h头文件
C 是在美国用标准美式键盘开发的语言。但是在世界各地,并非所有的 键盘都有和美式键盘一样的符号。因此,C99标准新增了可代替逻辑运算符 的拼写,它们被定义在ios646.h头文件中。如果在程序中包含该头文件,便 可用and代替&&、or代替||、not代替!。
例如下面的代码:

下图列出了逻辑运算符对应的拼写,很容易记:

由于C语言一直坚持尽量保持较少的关键字,所以C语言并没有直接使用and、or和not。并且有些逻辑运算符的备选拼写我们还没见过。
7.3.2优先级
!运算符的优先级很高,比乘法运算符还高,与递增运算符的优先级相 同,只比圆括号的优先级低。&&运算符的优先级比||运算符高,但是两者的 优先级都比关系运算符低,比赋值运算符高。
因此,表达式a >b && b > c || b > d相当于((a > b) && (b > c)) || (b > d)。也就是说,b介于a和c之间,或者b大于d。
在该例中没必要使用圆括号,但是许多程序员更喜欢使用带圆括号的第2种写法。这样做即使不记得逻辑运算符的优先级,表达式的含义也很清楚。
7.3.3求值顺序
除了两个运算符共享一个运算对象的情况外,C 通常不保证先对复杂表 达式中哪部分求值。例如下面的代码:

可以先对表达式5+3求值,也可以先对表达式9+6取值。
C 把先计算哪部分的决定权留给编译器的设计者,以便针对特定系统优 化设计。但是,对于逻辑运算符是个例外,C保证逻辑表达式的求值顺序是从左往右。
&&和||运算符都是序列点,所以程序在从一个运算对象执行到下 一个运算对象之前,所有的副作用都会生效。
而且,C 保证一旦发现某个元 素让整个表达式无效,便立即停止求值。正是由于有这些规定,才能写出这 样结构的代码:

读取字符直至遇到第1 个空格或换行符。第1 个子表达 式把读取的值赋给c,后面的子表达式会用到c的值。如果没有求值循序的保 证,编译器可能在给c赋值之前先对后面的表达式求值。
另外还有一种可能如下:

如果number的值是0,那么第1个子表达式为假,且不再对关系表达式求 值。这样避免了把0作为除数。
许多语言都没有这种特性,知道number为0 后,仍继续检查后面的条件。
最后,考虑下列例子:

实际上,&&是一个序列点,这保证了在对&&右侧的表达式求值之前, 已经递增了x。
总结:逻辑运算符与表达式
逻辑运算符:逻辑运算符的运算对象通常是关系表达式。!运算符只需要一个运算对象,其他两个逻辑运算符都需要两个运算对象,左侧一个,右侧一个。

逻辑表达式:
当且仅当expression1和expression2都为真,expression1 && expression2才 为真。如果 expression1 或 expression2 为真,expression1 || expression2 为 真。如果expression为假,!expression则为真,反之亦然。
求值顺序:
逻辑表达式的求值顺序是从左往右。一旦发现有使整个表达式为假的因素,立即停止求值。
示例:

以及x != 0 && (20 / x) < 5 只有当x不等于0时,才会对第2个表达式求值。
7.3.4范围
&&运算符可用于测试范围。例如,要测试score是否在90 ~100的范围内,可以这样写:

千万不要模仿数学上的写法:

千万不能这样写! !!
这样写的问题是代码有语义错误,而不是语法错误,所以编译器不会捕 获这样的问题(虽然可能会给出警告)。由于<=运算符的求值顺序是从左 往右,所以编译器把测试表达式解释为:(90<=range)<=100
子表达式90 <= range的值要么是1(为真),要么是0(为假)。这两个 值都小于100,所以不管range的值是多少,整个表达式都恒为真。因此,在范围测试中要使用&&。
许多代码都用范围测试来确定一个字符是否是小写字母。例如,假设ch 是char类型的变量:

该方法仅对于像ASCII这样的字符编码有效,这些编码中相邻字母与相 邻数字一一对应。但是,对于像EBCDIC这样的代码就没用了。相应的可移植方法是,用ctype.h系列中的islower()函数:

无论使用哪种特定的字符编码,islower()函数都能正常运行(不过,一些早期的编译器没有ctype.h系列)。**
7.4一个统计单词的程序
我们可以编写一个统计单词数量的程序(即,该程序读取并报告 单词的数量)。该程序还可以计算字符数和行数。
首先,该程序要逐个字符读取输入,知道何时停止读取。然后,该程序 能识别并计算这些内容:字符、行数和单词。据此我们编写的伪代码如下:
读取一个字符
当有更多的输入时
递增字符计数
如果读完一行,递增行数计数
如果读完一个单词,递增单词计数
读取下一个字符
前面有一个输入循环的模型:
while((ch=getchar())! =STOP)
{
.....
}
这里,STOP表示能标识输入末尾的某个值。以前我们用过换行符和句 点标记输入的末尾,但是对于一个通用的统计单词程序,它们都不合适。我 们暂时选用一个文本中不常用的字符(如,|)作为输入的末尾标记。后面会有更好的方法以便程序既能处理文本文件,又能处理键盘输入。
现在,我们考虑循环体。因为该程序使用getchar()进行输入,所以每次 迭代都要通过递增计数器来计数。为了统计行数,程序要能检查换行字符。 如果输入的字符是一个换行符,该程序应该递增行数计数器。
注意:STOP字符位于一行中间的请况,我们可以作为特殊 行计数,即没有换行符的一行字符。可以通过记录之前读取的字符识别这种 情况,即如果读取时发现 STOP 字符的上一个字符不是换行符,那么这行就是特殊行。
最棘手的部分是识别单词。首先,必须定义什么是该程序识别的单词。 我们用一个相对简单的方法,把一个单词定义为一个不含空白(即,没有空 格、制表符或换行符)的字符序列。因此,“glymxck”和“r2d2”都算是一个单 词。程序读取的第 1 个非空白字符即是一个单词的开始,当读到空白字符时结束。
则判断非空白字符最直接的测试表达式是:

检测空白字符最直接的测试表达式是:

然而,使用ctype.h头文件中的函数isspace()更简单,如果该函数的参数 是空白字符,则返回真。所以,如果c是空白字符,isspace(c)为真;如果c不 是空白字符,!isspace(c)为真。
要查找一个单词里是否有某个字符,可以在程序读入单词的首字符时把 一个标记(名为 inword)设置为1。也可以在此时递增单词计数。然后,只 要inword为1(或true),后续的非空白字符都不记为单词的开始。下一个空 白字符,必须重置标记为0(或false),然后程序就准备好读取下一个单词。我们把上述分析写成伪代码即:
如果c不是空白字符,且inword为假
设置inword为真,并给单词计数
如果c是空白字符,且inword为真
设置inword为假,且不给单词计数
这种方法在读到每个单词的开头时把inword设置为1(真),在读到每 个单词的末尾时把inword设置为0(假)。只有在标记从0设置为1时,递增 单词计数。如果能使用_Bool类型,可以在程序中包含stdbool.h头文件,把 inword的类型设置为bool,其值用true和false表示。如果编译器不支持这种 用法,就把inword的类型设置为int,其值用1和0表示。
如果使用布尔类型的变量,通常习惯把变量自身作为测试条件。例如:
用if (inword)代替if (inword == true) 用if (!inword)代替if (inword == false)
可以这样做的原因是,如果 inword为true,则表达式 inword == true为 true;如果 inword为false,则表达式inword == true为false。所以,还不如直 接用inword作为测试条件。类似地,!inword的值与表达式inword == false的 值相同(非真即false,非假即true)。
下列程序演示了上述程序分析思路:


输出结果如下:

该程序使用逻辑运算符把伪代码翻译成C代码。例如,把下面的伪代码:
如果c不是空白字符,且inword为假
翻译成如下C语言代码:
if (!isspace(c) &&!inword)
注意:!inword 与 inword == false 等价。
上面的整个测试 条件比单独判断每个空白字符的可读性高:

上面的两种形式都表示“如果c不是空白字符,且如果c不在单词里”。如 果两个条件都满足,则一定是一个新单词的开头,所以要递增n_words。如 果位于单词中,满足第1个条件,但是inword为true,就不递增 n_word。当 读到下一个空白字符时,inword 被再次设置为 false。
问题:检查代码,查看一下 如果单词之间有多个空格时,程序是否能正常运行。
第8章讲解了如何解决修正这个问题,让该程序能统计文件中的单词量。

浙公网安备 33010602011771号