C Primer Plus(第五版)7

第 7 章 C 控制语句:分支和跳转

在本章中你将学习下列内容:

· 关键字:if(如果),else(否则),switch(切换),continue(继续),break(中断),
case(情况),default(缺省),goto(转到)

· 运算符: &&(逻辑与), ||(逻辑或), ?: (条件运算)

· 函数: getchar(), putchar()以及 ctype.h 系列。

· 怎样使用 if 和 if else 语句以及如何嵌套使用它们。

· 使用逻辑运算符将关系表达式组合为更加复杂的判断表达式。

· C 的条件运算符。

· switch 语句 。

· break,continue,和 goto 跳转。

· 使用 C 的字符 I/O 函数:getchar()和 putchar()。

· 由 ctype.h 头文件提供的字符分析函数系列。

对 C 语言掌握到一定程序以后,你或许想要处理一些更复杂的任务。这时候就需要一些手段来控制和组织这些项目,C 有方法来满足这些需要。前面已经学习了使用循环来处理重复的任务。在本章中,你将学习如何使用分支结构,如 if 和 switch, 以便允许程序按照所检查的条件执行相应的动作。另外,还要介绍 C 的逻辑运算符,它们能使你对 while 或 if 条件中的多个关系进行判断。此外,我们还将学习跳转语句,它将程序流程切换到程序的其他部分。学完本章,你将获得设计按你希望的方式运行的程序所需的全部基本知识。

7.1 if 语句

让我们以程序清单 7.1 展示的一个 if 语句的简单例子开始。这个程序读入一系列每日的最低温度(摄氏度),并报告输入的总数,以及最低温度在零度以下的天数的百分率。在一个循环里使用 scanf()读入数值,在每个循环中增加计数器的值来统计输入数值的个数。if 语句检测低于零度以下的温度并单独统计这些天的数目。

程序清单 7.1 colddays.c 程序
--------------------------------------------------------------------
/* colddays.c ---- 求出温度低于零度的天数的百分率 */
#include <stdio.h>
int main (void)
{
const int FREEZING = 0;
float temperature;
int cold_days = 0;
int all_days = 0;

printf ("Enter the list of daily low temperatures . \n");
printf ("Use Celsius, and enter q to quit .\n");
while (scanf ("%f",&temperature) == 1)
{
all_days++;
if (temperature < FREEZING)
cold_days++;
}
if (all_days != 0)
printf ("%d days total; %.1f%% were below freezing .\n",
all_days,100.0 * (float)cold_days / all_days);
if (all_days == 0)
printf ("NO data entered !\n");
system("PAUSE");
return 0;
}

下面是一个运行示例:
Enter the list of daily low temperatures .
Use Celsius, and enter q to quit .
12 5 -2.5 0 6 8 -3 -10 5 10 q
10 days total; 30.0% were below freezing .

while 循环的判断条件利用 scanf()的返回值在 scanf()遇到非数字输入的时候终止循环。用 float 而不是 int 来声明 temperature ,这样程序就既能接受像 8 那样的输入,也能接受 -2.5 这样的输入。

while 代码中的新语句如下:

if (temperature < FREEZING)
cold_days++;

该 if 语句指示计算机,如果刚读入的数值 (temperature)小于 -0 就将 cold_days 加 1 。如果 temperature 值不小于 0 将是什么情形呢?那样的话就会跳过 cold_days++;语句,while 循环继续读取下一个温度值。

该程序使用两次 if 语句来控制输出。如果有数据的话,程序就会打印出结果,如果没有数据,那么程序就会报告该事实(稍后你将看到可以用一种更好的方法来处理程序的这个部分)。

为了避免整数除法,示例程序在计算百分率时使用了类型转换 float。并不是真的需要类型转换,因为在表达式 100.0 * cold_days / all_days 中,先求子表达式 100.0 * cold_days 的值并通过自动类型转换规则强制转换为浮点型数值。然后使用类型转换可以表明你的意图,并保护程序免受不完善编译器的影响。

if 语句被称为分支语句(branching statement)或选择语句(selection statement),因为它提供了一个交汇点,在此处程序需要选择两条分支的一条前进。一般的形式如下:

if (expression)
statement

如果 expression 求得的值为真(非零),就执行 statement;否则,跳过该语句。和 while 循环一样, statement 既可以是单个语句也可以是一个代码块(术语上称为复合语句)。这种结构和 while 语句很相似。主要的区别在于在 if 语句中,判断和执行(如果可能的话)仅有一次,而在 while 循环中,判断和执行可以重复多次。

通常,expression 是一个关系表达式。也就是说,它比较两个量的大小,像表达式 x>y 和 c== 6 那样。如果 expression 的值为真 (x大于y,或者c等于6);就执行语句;否则,将忽略语句。更一般地,可以使用任何表达式,表达式的值为 0 就被视为假。语句部分可以是一个简单语句,就像例子中的那样;也可以是一个由花括号标出的复合语句(代码块):

if (score > big )
printf ("Jackpot \n"); // 简单语句

if (joe > ron ) // 复合语句
{
jackpot++
printf ("you lose,Ron \n");
}

7.2 在 if 语句中添加 else 关键字

简单形式的 if 语句使你可以选择执行一条语句(可能是复合语句)或忽略它。C 还可以通过使用 if else 形式来在两个语句间做出选择。让我们使用 if else 形式侯程序清单 7.1 中略显笨拙的程序段。

if (all_days != 0)
printf ("%d days total: %.1f%% were below freezing .\n",
all_days, 100.0 * (float)cold_days / all_days);
if (all_days == 0)
printf ("No data entered \n");

如果程序发现 all_days 不等于 0 的,那么它应该知道 all_days 肯定等于 0,这样就不必等闲判断。使用 if else ,你可以将这段程序改写为如下形式;

if (all_days != 0)
printf ("%d days total: %.1f%% were below freezing .\n",
all_days, 100.0 * (float)cold_days / all_days);
else
printf ("No data entered \n");

仅仅进行了一次判断。如果 if 的判断表达式为真,则打印温度数据;如果某值为假,则打印警告消息。

注意,if else 语句的通用形式为:

if (expression)
statement1
else
statement2

如果 expression 为真(非零),就执行 statement1;如果 expression 为假或零,则执行跟在 else 后的那一条语句 (statement2)。语句可以是简单的或复合的。C 不要求缩排,但这是标准的风格。缩排使语句依赖于判断而执行这一事实显得一目了然。

如果希望在 if 和 else 之间有多条语句,必须使用花括号创建一个代码块。下面的结构了 C 语法,因为编译器期望 if 和 else 之间只有一条语句(单条的或复合的):

if (x > 0)
printf ("Incrementing x :\n");
x++;
else // 将产生一个错误
printf ("x <= 0 \n");

编译器会把 printf () 语句看作 if 语句的部分,将 x++;语句看作一条单独的语句,而不把它作为 if 语句的一部分。然后会认为 else 没有所属的 if ,这是个错误。应该使用这种形式:

if (x > 0)
{
printf ("Incrementing x :\n");
x++;
}
else
printf ("x <= 0 \n");

if 语句使你能够选择是否执行某个动作。if else 语句使你可以在两个动作之间进行选择。

7.2.1 另一个例子:介绍 getchar()和 putchar()

前面的多数程序所输入的内容都是数字。为了练习使用其他的形式,让我们来看一个面向字符的例子。你已经知道了怎样使用 scanf()和 printf()来以 %c 说明符读写字符,现在我们将接触专门为面向字符 I/O 而设计的一对 C 函数: getchar()和 putchar()。

getchar()函数没有参数,它返回来自输入设备的下一个字符。例如,下面的语句读取下一个输入字符并将它的值赋给变量 ch:

ch = getcahr();

该语句与下面的语句有同样的效果:

scanf("%c",&ch);

putchar()函数打印它的参数。例如,下面的语句将先前赋给 ch 的值作为字符打印聘为:

putchar(ch);

该语句与下面的语句有同样的效果:

printf ("%c",ch);

因为这些函数仅仅处理字符,所以它们比更通用的 scanf()和 printf()函数更快而且更简洁。同样,注意到它们不需要格式说明符,因为它们只对字符起作用。这两个函数通常都在 stdio.h 文件中定义(而且,它们通常只是预处理器宏(macro),而不是真正的函数;我们将在第 16 章“C 预处理器和 C 库”中讨论类似函数的宏)。

下面将通过一个程序来说明这些函数是怎么工作的。这个程序再现输入的一行,但将每个非空格字符替换为该字符在 ASCII 码序列中的后一个字符。空格还是作为空格生机。可以将希望程序做出的响应表述为“如果字符是空格,打印之;否则,打印它在 ASCII 序列中的下一个字符”。

C 的代码看起来很像该陈述,这一点你可以在程序清单 7.2 中看到。

程序清单 7.2 cypher1.c 程序
----------------------------------------------------------------
/* cypher1.c --- 改变输入,只保留其中的空格 */
#include <stdio.h>
#define SPACE " " /* SPACE 相当于 “引号-空格-引号” */
int main (void)
{
char ch;

ch = getchar(); /* 读入一个字符 */
while (ch != ‘\n‘) /* 当一行末结束时 */
{
if (ch == SPACE) /* 空格不变 */
putchar(ch); /* 不改变这个字符 */
else
putchar(ch + 1); /* 改变其他字符 */
ch = getchar(); /* 获取下一个字符 */
}
putchar(ch); /* 打印换行字符 */
system("PAUSE");
return 0;
}

下面是一个运行示例;

CALL MB HAL.
DBMM NC IBM/

把这个循环和程序清单 7.1 中的循环进行比较。程序清单 7.1 中用 scanf()返回的状态而不是输入元素的值来决定何时终止循环。而在程序清单 7.2 中,则是由输入元素的值本身决定何时终止循环,这种不同导致这个循环结构与前面的略有不同。这里,一个“读”语句在循环之前,另一个“读”语句在每次循环的结尾。然而,C 灵活的语法使你可以通过将读取和判断合并为单个表达式来仿效程序清单 7.1 。那也就是说,你可以把这种形式的循环:

ch = getchar(); // 读入一个字符
while (ch != ‘\n‘) //当一行末结束时
{
...
ch = getchar(); // 获取下一个字符
}

替换为下面的形式:

while ((ch = getchar()) != ‘\n‘)
{
... // 处理字符
}

关键的一行是:

while ((ch = getchar()) != ‘\n‘)

这体现了典型的 C 编程风格:将两个动作合并为一个表达式。C 自由的格式可以使这行语句中的每个部分更清晰:

while (
(ch = getchar()) // 为 ch 赋一个值
!= ‘\n‘) // 把 ch 与 \n 相比较

该动作将一个值赋给 ch 并将这个值与换行符进行比较。圆括号括住 ch = getchar()使之成为“!=”运算符的左操作数。为了求得该表达式的值,计算机首先必须调用 getchar()函数,然后将它的返回值赋给 ch 。因为赋值表达式的值就等于表达式左侧数的值,所以 ch = getchar()的值就等于 ch 的新值。因此,读取 ch 之后,判断条件缩减为 ch != ‘\n‘ ,也就是说,ch 不等于换行符。

这种独特的习惯用法在 C 编程中看常见,所以你应该熟悉它。也一定要记住适当地使用圆括号来组合子表达式。

所有的圆括号都是必需的。假如错误地这样使用;

while (ch = getchar() != ‘\n‘)

!= 运算符的优先级比 = 要高,所以首先被计算的表达式是 getchar() != ‘\n‘ 。因为这是一个关系表达式,因此其值是 0 或 1 (真或假)。然后这个值被赋给 ch 。遗漏了圆括号就意味着 ch 的值被赋为 0 或 1,而不是 getchar()的返回的值,显然这不是我们所希望的。

下面的语句:

putchar (ch + 1); // 改变其他字符

再次表明了字符实际上是作为整数被存储的。在表达式 ch + 1 中,ch 被扩展为 int 型以用于计算,然后 int 型的计算结果被传给 putchar()。这个函数接受一个 int 型的参数,但仅仅使用最后一个字节来决定显示哪个字符。

7.2.2 ctype.h 系列字符函数

注意到程序清单 7.2 的输出显示出句号被转换为斜杠;这是因为斜杠字符对应的 ASCII 码与句号的 ASCII 码大 1 。但是如果程序指明只转换字母,将所有的非字母字符(而不只是空格符)保留下来,将会更好。本章稍后讨论的逻辑运算提供一种方法以判断字符是否不是空格,逗号,如此等等,但是罗列所有的可能性太麻烦。幸好, ANSI C 有一系列标准的函数可以用来分析字符;ctype.h 头文件包含了这些函数的原型。这些函数接受一个字符作为参数,如果该字符属于某特定的种类则返回非零值(真),否则返回零(假)。例如,如果 isalpha()函数的参数是个字母,则它返回一个非零值。程序清单 7.3 通过使用该函数扩展了程序清单 7.2,它也加入了刚才讨论过的精简后的循环结构。

程序清单 7.3 cypher2.c 程序
--------------------------------------------------------------
// cypher2.c -- 改变输入,只留非字母字符

#include <stdio.h>
#include <ctype.h> // 为 isalpha()提供原型
int main (void)
{
char ch;

while ((ch = getchar()) != ‘\n‘)
{
if (isalpha(ch)) //如果是一个字母
putchar (ch+1); // 则改变它
else //否则
putchar (ch); // 原样打印它
}
putchar(ch); // 打印换行字符
system("PAUSE");
return 0;
}

下面是一个运行示例;注意大写字母和小写字母被译码,而空格和标点符号则没有;

Look! It‘s a programmer!
Mppl! Ju‘t b qsphsbnnfs!

表 7.1 和表 7.2 列出了 ctype.h 头文件所包含的一些函数。有些函数提到了本地化,这里指的是难免指定一个本地以修改或扩展 C 的基本用法的 C 工具(例如,许多国家在书写十进制小数的时候,使用逗号来替代小数点,于是,特定的本地化工具就能够指定逗号在浮点小数的输出中起到小数点一样的作用。因此,就显示为 123,45)。注意,映射函数并不改变原始的参数,它们只返回改变后的值。也就是说,下列语句不改变 ch 的值:

tolower(ch) // 对 ch 没有影响

没有改变 ch ,若要改变 ch,可以这样做:

ch = tolower(ch); // 把 ch 转换为小写 注: CBC++ 不一定可行

表 7.1 ctype.h 的字符判断函数
---------------------------------------------------------------------------
函数名 为如下参数时,返回值为真
----------------------------------------------------------------------------
isalnum() 字母数字(字母或数字)
----------------------------------------------------------------------------
isalpha() 字母
----------------------------------------------------------------------------
isblank() 一个标准的空白字符(空格水平制表或换行)或任何其他本地化指定为空白字符
--------------------------------------------------------------------------------------
iscntrl() 控制符,例如 Ctrl + B
---------------------------------------------------------------------------------
isdigit() 阿拉伯数字
-------------------------------------------------------------------------------
isgraph() 除空格之外的所有可打印字符
--------------------------------------------------------------------------------
islower() 小字字母
--------------------------------------------------------------------------------
isprint() 可打印字符
-------------------------------------------------------------------------------
ispunct() 标点符号(除空格和字母数字外的可打印字符)
--------------------------------------------------------------------------------
isspace() 空白字符:空格,换行,走纸,回车,制表符,或其他本地化定义字符
--------------------------------------------------------------------------------
isupper() 大写字母
---------------------------------------------------------------------------------
isxdigit() 十六进制数字字符
----------------------------------------------------------------------------------

表 7.2 ctype.h 的字符映射函数
-----------------------------------------------------------------------------------
函数名 动作
-----------------------------------------------------------------------------------
tolower() 如果参数是大写字符,返回相应的小写字符;否则,返回原始参数
-----------------------------------------------------------------------------------
toupper() 如果参数是小写字符,返回相应的大写字符;否则,返回原始参数
-------------------------------------------------------------------------------------

7.2.3 多重选择 else if

日常生活经常会给我们提供两个以上的选择。可以用 else if 扩展 if else 结构来适应这种情况。我们来看一个例子。公用事业公司通常使计费依赖于客户使用能量的总数。这里有某个公司电力计费的费率,单位是千瓦时(kWh):

-------------------------------------------------------------------
第一个 360 kWh: 每 kWh $0.12589
-------------------------------------------------------------------
下一个 320 kWh: 每 kWh $0.17901
--------------------------------------------------------------------
超过 680 kWh; 每 kWh $0.20971
----------------------------------------------------------------------

如果你比较在意用电管理,可以编写程序来计算你的用电费用。程序清单 7.4 所示程序就是完成这一任务的第一步。

程序清单 7.4 electric.c 程序
----------------------------------------------------------------------
/* electric.c -- 计算用电帐目 */
#include <stdio.h>
#define RATE1 0.12589 // 第一个 360 kWh: 每 kWh $0.12589
#define RATE2 0.17901 // 下一个 320 kWh: 每 kWh $0.17901
#define RATE3 0.20971 // 超过 680 kWh; 每 kWh $0.20971
#define BREAK1 360.0 // 费率的第一个分界点
#define BREAK2 680.0 // 费率的第二个分界点
#define BASE1 (RATE1 * BREAK1) // 用电 360 kWh 的费用
#define BASE2 (BASE1 + (RATE2 * (BREAK2 - BREAK1))) // 用电 680 kWh 的费用

int main (void)
{
double kwh; // 用电的千瓦小时数
double bill; // 费用

printf ("Please enter the kwh used .\n");
scanf ("%lf",&kwh); // %lf 是 double 类型的说明符
if (kwh <= BREAK1)
bill = RATE1 * kwh;
else if (kwh <= BREAK2) // 用电量在 360 kWh 和 680 kWh 之间时
bill = BASE1 + (RATE2 * (kwh - BREAK1));
else
bill = BASE2 + (RATE3 * (kwh - BREAK2)); // 用电超过 680 kWh时

printf ("The charge for %.1f kwh is $%1.2f \n",kwh,bill);
system("PAUSE");
return 0;
}

下面是一个输出示例:

Please enter the kwh used .
580
The charge for 580.0 kwh is $84.70

程序清单 7.4 用符号常量表示费率,以便这些常量可以很方便地放置在一起。这样一旦电力公司改变费率(这是很可能的),就很容易更新这些被放在一起的费率。该清单也用符号表示了费率的分界点。它们也有可能改变。BASE1 和 BASE2 根据费率和分界点来表示。这样,如果费率或分界点改变了,它们也会自动地更新。你可能回想起预处理器是不做计算的。程序中 BASE1 出现的地方将用 0.12589 * 360.0 代替。不用担心;编译器会求得表达式的数值 (45.3204)以便最终的程序代码使用 45.3204而不是一个计算符。

程序的流程简单明了的。该程序按照 kwh 的数值选择三个公式中的一个。应该特别注意的是仅当 kwh 大于 360 时程序才到达第一个 else 。所以,像程序注释所注明的那样,else if (kwh <= BREAK2) 行实际上相当于要求 kwh 在 360 和 680 之间。同样,仅当 kwh 超过 680, 才能够到达最后一个 else 。 最后请注意, BASE1 和 BASE2 分别代表前 360 和 680千瓦时的总费用。因此,当用电量超过这些值时,仅需要加上额外的费用。

实际上,else if 是你已经学过的形式的一种变化。例如,该程序的核心部分只不过是下面语句的另一种写法;

if (kwh <= BREAK1)
bill = RATE1 * kwh;
else
if (kwh <= BREAK2)
bill = BASE1 + RATE2 * (kwh - BREAK1);
else
bill = BASE2 + RATE3 * (kwh - BREAK2);

也就是说,程序所包含的一个 if else 语句是另一个 if else 语句的 else 语句部分。我们称第二个 if else 语句被嵌套(nested)在第一个里面。回忆一下,整个 if else 结构作为一条语句,这就是为什么不必将被嵌套的 if else 放在花括号中。然而使用花括号可以更清楚地表明这种特殊格式的含义。

两种形式完全等价。唯一的区别在于空格和换行的使用位置,而编译器会忽略这些差别。尽管如此,第一种形式还是更好些,因为它更清晰地展示出你做出了三种选择。这种格式更易于快速浏览程序时看清楚各个选择。只在必要的时候使用缩排的嵌套格式,比如在必须判断两个单独的量时。这种情形的一个例子是仅在夏季对超过 680 千瓦的用电量加收 10%的额外费用。

如下段所展示的,可以把多个(当然,需要在编译器的限制范围内)所需的 else if 语句连成一串使用。

if (score < 1000)
bonus = 0;
else if (score < 1500)
bonus = 1;
else if (score < 2000)
bonus = 2;
else if (score < 2500)
bonus = 4;
else
bonus = 6;

(这很可能是某个游戏程序的一部分,bonus 描述你为下一轮游戏所获得的炸弹或食品的多少。)

说到编译器的限制,C99 标准要求编译器最少支持 127 层嵌套。

7.2.4 把 else 与 if 配对

当有众多 if 和 else 的时候,计算机是怎样判断哪个 if 对应 哪个 else 的?例如,考虑下面的程序段:

if (number > 6)
if (number < 12)
printf (" You‘re close !\n");
else
printf (" Sorry,you lose a turn \n");

什么时候打印“Sorry,you lose a turn ”?是在 number 小于等于 6 时,还是在大于等于 12 的时候?换句话说,这个 else 对应第一个还是第二个 if 呢?答案是,else 对应第二个 if 。 也就是说,你可能得到下列响应

数字 响应
----------------------------------------
5 没有任何响应
----------------------------------------
10 You‘re close
-----------------------------------------
15 Sorry,you lose a turn
----------------------------------------

规则是如果没有花括号指明, else 与和它最接近的一个 if 相匹配。

例1 :

if (condition)
do this;
if (condition)
do this;
else
do this; else 与和它最接近的一个 if 相匹配。(即例中的最后一个 if)

例2:

if (condition)
{
do this;
if (condtion)
do this;
}
else
do this; 由于内部的 if 语句包含在花括号中,所以 else 与第一个 if 相匹配。

第一个例子的缩排使得 else 那会儿是与第一个 if 匹配的。但要记住,编译是忽略缩排的。如果真的希望 else 与第一个 if 匹配,可以这样写:

if (number > 6)
{
if (number < 12)
printf (" You‘re close !\n");
}
else
printf (" Sorry,you lose a turn \n");

你可能会得到下面的响应;

数字 响应
----------------------------------------
5 You‘re close
----------------------------------------
10 You‘re close
-----------------------------------------
15 Sorry,you lose a turn
----------------------------------------

7.2.5 多层嵌套的 if

前面所看到的 if...else if...else 序列是嵌套 if 的一种形式,这是从一系列的二选一中进行选择的。当进行了特定的选择后又导致了额外的选择时将使用另一种嵌套 if 。例如,一个程序可能用 if else 来区别不同收入的群体。

我们试着用这种形式的嵌套 if 来解决下面的问题:给定一个整数,显示所有能带除它的约数;如果没有,则报告该数是个素数。

该问题在编写代码之前需要预先计划好。首先,需要对程序进行总体设计。为了方便,程序需要用一个循环来使能输入被测试的数。这样,在希望检验一个新数的时候就无须每次都重新运行程序。我们已经为这种循环研究出一个模式:

prompt user // 提示用户
while the scanf()return value is 1 //如果输入的返回值为 1
analyze the number and report results //分析输入的数值并检查循环是否该终止
prompt user // 提示用户

回忆一下,通过在循环判断条件中使用 scanf(),程序试图读入一个数值并检查循环是否应该终止。

下一步,需要计划怎么来找到除数。或许最显而易见的方法是这样的:

for (div = 2; div < num ; div++)
if (num % div == 0)
printf ("%d is divisible by %d\n",num,div);

该循环检查界于 2 到 num 之间的所有数,看它们是否可以整除 num。不幸的是,这种方法浪费了计算机的时间。我们可以做得更好。例如,考虑搜索 144 的约数。可以发现 144 % 2为 0,这意味着 144 能被 2 整除。如果明确地拿 144 除以 2 ,可以得到 72 ,这也是个约数,因为一次成功的
num % div 测试可能得到两个约数而不是一个。然而真正的性能指标在于循环测试的改变。为了弄清这是怎样工作的,看一下循环中所得到的成对的约数: 2 和 72,3 和 48, 4 和 36,8 和 18, 9 和 16, 12 和12, 19 和 9, 18 和 8,如此等等。哦,在得到 12 和 12 这对数后,又开始得到与已找到的相同的约数(以相反的次序)。无须循环到 143,在达到 12 后就可以停止。这就节省了大量的循环周期!

归纳以后,可以确定必须测试的数只要到 num 的平方根就可以了,而不必到 num 。对于像 9 这样的数,这并不会节省很多时间,但对于像 10 000 这样的数,差别就很大了。然而,我们不必在程序中计算平方根,而是像下面这样描述判断条件;

for (div = 2 ;(div * div) <= num; div++)
if (num % div == 0)
printf ("%d is divisble by %d and %d \n",num,div,num / div);

如果 num 为 144, 循环到 div = 12 终止。如果 num 为 145,循环运行到 div = 13 终止。

利用这种方法判胜于利用平方根判断有两个原因。第一,整数乘法比求平方根更快;第二,平方根的函数还没正式介绍。

我们还需要提出两个问题,然后才能准备开始编程。第一,如果测试的数是一个完全平方根怎么办?报告 144 可被 12 和 12 整除显然有些愚蠢,但可以用嵌套 if 语句来判断 div 是否等于 num / div 。如果是,程序将只打印一个约数而不是两个。

for (div = 2; (div * div)<= num; div++)
{
if (num % div == 0)
{
if (div * div != num)
printf ("%d is divisible by %d and %d \n",num,div,num/div);
else
printf ("%d is bivisible by %d \n",num,div);
}
}

--------------------------------------------------------

PS : 花括号

从技术角度讲,if else 语句作为单个语句,所以不需要括上花括号。外部 if 也是单个语句,所以不需要花括号。然而,当语句很长时,花括号使人更容易清楚发生了什么,并且当后来将其他语句加到 if 或循环中时它们提供了保护。

----------------------------------------------------------

第二,怎样知道一个数是素数呢?如果 num 是素数,程序流程永远也进不了 if 语句。为了解决这个问题,可以在循环外设置一个变量为某一值,比方说 1 ,在 if 语句中将这个变量为 0 。那么,循环完成后,可以检查该变量是否仍然是 1 。如果是,则从没进入过 if 语句,这个数是素数。这样的变量通常被称为(flag)。

传统上, C 习惯用 int 类型作为标志,但新型的 _Bool型变量极佳地符合了这种需求。而且,通过包含 stdbool.h 文件,可以用 bool 代替关键字 _Bool 表示这种类型,并用标识符 true 和 false 代替 1 和 0 。

程序清单 7.5 续保了所有这些想法。为了扩大满园,程序用类型 long 代替 类型 int (如果系统不支持 _Bool 类型,可以让 isPrime 使用 int 类型并用 1 和 0 代替 true 和 false 。

程序清单 7.5 divisors.c 程序
----------------------------------------------------------
/* divisors.c -- 使用嵌套 if 显示一个数的约数 */
#include <stdio.h>
#include <stdbool.h>

int main ( void)
{
typedef int bool;
unsigned long num; // 要检查的数
unsigned long div; // 可能的约数
bool isPrime; // 素数的标志

printf ("please enter an integer for analysis : ");
printf ("enter q to quit \n");

while (scanf ("%lu",&num) == 1)
{
for (div = 2, isPrime = true; (div * div)<= num; div++)
{
if ( num % div == 0)
{
if ((div * div) != num)
printf ("%lu is divisible by %lu and %lu \n",num,div,num/div);
else
printf ("%lu is divisible by %lu ",num,div);
isPrime = false;
}
}
if (isPrime)
printf ("%lu is prime \n",num);
printf ("please enter an integer for analysis : ");
printf (" enter q to quit \n");
}
printf ("Bye \n");
return 0;
}

注意,该程序在 for 循环控制表达式中使用了逗号运算符,以针对每个新输入的数将isPrime 初始化为 true 。

下面是一个运行示例:

please enter an integer for analysis : enter q to quit
36
36 is divisible by 2 and 18
36 is divisible by 3 and 12
36 is divisible by 4 and 9
36 is divisible by 6 please enter an integer for analysis : enter q to quit
149
149 is prime
please enter an integer for analysis : enter q to quit
3077
3077 is divisible by 17 and 181
please enter an integer for analysis : enter q to quit
q

这个程序将会把 1 判断为素数,而实际上, 1 不是素数。下一部分将要介绍的逻辑运算符将使你能够把 1 排除在素数之外。

PS: 总结: 使用 if 语句进行选择

关键字: if,else

总体注解:

下列每种形式中,语句部分可以是一个简单语句或者一个复合语句。一个真表达式着它具有非零值。

-----------------------------------------------------------

形式1;

if (exprssion)
statement

如果 expression 为真 则执行 statement 。

-----------------------------------------------------------

形式2:

if (exprssion)
statement1
else
statement2

如果 exprssion 为真 执行 statement1;否则执行 statement2 。

------------------------------------------------------------

形式3:

if (exprssion1)
statement1
else if(exprssion2)
statement2
else
statement3

如果 exprssion1 为真 执行 statement1;如果 exprssion1 为假而 exprssion2 为真,则执行
statement2 ;否则,如果两个表达式都为假,执行 statement3 。

例如;

if (legs == 4)
printf ("It might be a horse \n");
else if (legs > 4)
printf ("It is not a horse \n");
else /* case of legs < 4 */
{
legs++;
printf ("Now it has one more leg\n");

--------------------------------------------------------------

7.3 获得逻辑性

对于 if 和 while 语句通常怎样使用关系表达式作为判断条件你已经了。有时你会觉得将两个或多个表达式组合起来很有用处。例如,假设需要编写一个程序,用来计算一个输入的句子中除单引号和双引号以外的字符出现了多少次。可以用逻辑运算符来实际该目的,可以用(英文的)句号(.)来标识一个兔子的结束,程序清单 7.6 用一个简短的程序阐明了这种方法。

程序清单 7.6 chcount.c 程序
----------------------------------------------------------------------------
/* chcount.c -- 使用逻辑与运算符 */
#include <stdio.h>
#include <stdlib.h> /* 为了使用 system("PAUSE"); 功能 */
#define PERIOD ‘.‘
int main (void)
{
int ch;
int charcount = 0;

while ((ch = getchar()) != PERIOD)
{
if (ch != ‘"‘ && ch!= ‘\‘‘)
charcount++;
}
printf ("Ther are %d non-quote characters \n",charcount);
system("PAUSE");
return 0;
}

下面是一个运行实例:

I didn‘t raad the "I‘m a programdding Fool" best seller.
Ther are 51 non-quote characters

首先,程序读入一个字符并检查它是否是一个句号,因为句号标志着一个句子的结束。接下来的语句中,使用了逻辑与运算符 && ,这是我们以前没有遇到的。可以将该 if 语句翻译为:“如果字符不是双引号并且它不是单引号,那么 charcount 增加 1 。”

要使整个表达式为真,则两个条件必须都为真。逻辑运算符的优先级低于关系运算符,所以不必使用圆括号组合子表达式。

C 有三个逻辑运算符,请参见表 7.3

表 7.3 C 的逻辑运算符
------------------------------------------------------
运算符 含义
-----------------------------------------------------
&& 与
----------------------------------------------------
|| 或
----------------------------------------------------
! 非
----------------------------------------------------

假如 exp1 和 exp2 是两个简单的关系表达式,例如 cat > rat 且 debt == 1000 。那么可以声明如下:

· 仅当 exp1 和 exp2 都为真的时候, exp1 && exp2 才为真。

· 如果 exp1 或 exp2 为真或二者都为真, exp1 || exp2 为真。

· 如果 exp1 为假,则 !exp1 为真;并且如果 exp1 为真,则 !exp1 为假 。

下面是一个具体的例子:

· 5 > 2 && 4 > 7为假,因为只有一个子表达式为真。

· 5 > 2 || 4 > 7 为真,因为至少有一个子表达式为真。

· !(4 > 7) 为真,因为 4 不大于 7.

顺便说说,最后一个不成功与下面的表达式等价:

4 <= 7

如果你与逻辑运算符不熟悉或感到很别扭,请记住;

(练习 && 时间) == 完美

7.3.1 改变拼写法: iso646.h 头文件

C 是在美国使用标准美工键盘的系统上发展而来。但在世界各地,并不是所有的键盘都有与美式键盘相同的符号。因此,C99 标准为逻辑运算符增加了可供选择的拼写法。它们在 iso646.h 头文件里定义。如果包含了这个头文件,就可以用 and 代替 &&, 用 or 代替 ||,用 not 代替 ! 。例如,下列语句:

if (ch != ‘"‘ && ch!= ‘\‘‘)
charcount++;

可以重写为以下方式

if (ch != ‘"‘ and ch!= ‘\‘‘)
charcount++;

表 7.4 列出了这些选择;很容易记住它们。事实上,你可能想知道为什么 C 不完全使用这些新术语。回答可能是 C 历史上一直设法保持尽量少的关键字。参考资料7 “扩展的字符支持”中列出了我们还没遇到过的一些运算符的更多可供选择的拼写法。

表 7.4 逻辑运算符的可选表示法
------------------------------------------------
传统的 iso646.h
------------------------------------------------
&& and
-----------------------------------------------
|| or
-----------------------------------------------
! not
-----------------------------------------------

7.3.2 优先级

!运算符的优先级很高。它高于乘法运算,和增量运算符的优先级相同,仅次于圆括号。&&运算符的优先级高于 || ,这二者的优先级都低于关系运算符而高于赋值运算。因此,下列表达式:

a > b && b > c || b > d

将被认为是这样的:

((a > b)&& (b > c)||(b > d)

也就是说,b 界于 a 和 c 之间,或者 b 大于 d 。

很多程序员都愿意使用圆括号,正如上面第二种写法那样,尽管这并不是必需的。这样的话,即使你不能完全记住逻辑运算符的优先级,表达式的含义仍然很清楚的。

7.3.3 求值的顺序

除了那些两个运算符共享一个操作数的情况以外,C 通常不保证复杂表达式的哪个部分首先被求值。
例如在下面的语句里,可能先计算表达式 5 + 3 的值,也可能先计算 9 + 6 的值。

apples = (5 + 3)* (9 + 6);

C 语言允许这种不确定性,以便编译器设计者可以针对特定的系统做出最有效率的选择。一个例外是对逻辑运算符的处理。C 保证逻辑表达式是从左到右求值的。&& 和 || 运算符是序列的分界点,因此在程序从一个操作数前进到下一个操作数之前,所有的副作用都会生效。而且,C 保证一旦发现某个元素使表达式总体无效,求值将立刻停止。这些约定使像下面的这样的结构成为可能;

while ((c = getchar()) != ‘‘ && c != ‘\n‘)

这个结构建立一个循环读入字符,直到出现第一个空格符或换行符。第一个子表达式给 c 赋值,然后 c 的值被用在第二个子表达式中。如果没有顺序保障,计算机可能试图在 c 被赋值之前判断第二个子表达式。

下面是另外一个例子:

if (number != 0 && 12/number == 2)
printf ("The number is 5 or 6 \n");

如果 number 值为 0,那么第一个子表达式为假,就不再对关系表达式求值。这就避免了计算机试图把 0 作为除数。很多语言都没有这个特性,在知道 number 为 0 后,它们仍继续后面的条件检查。

最后,考虑这个例子:

while (x++ < 10 && x + y < 20)

&& 运算符是序列分界点这一事实保证了在对右边表达式求值之前,先把 x 的值增加 1 。

PS: 总结:逻辑运算符和表达式

逻辑运算符:

逻辑运算符通常使用关系表达式作为操作数。! 运算符带一个操作数。 其他两个逻辑运算符带有两个操作数:一个在左边,一个在右边。

运算符 意义
&& 与
|| 或
! 非

逻辑表达式:

当且仅当两个表达式都为真时,expression1 && expression2 为真。 如果其中一个为真或两个表达式都为真, expression1 || expression2 为真。 如果 expression 为假,则 !expression 为真,反之亦然。

求值顺序:

逻辑表达式是从左到右求值的。一旦发现有使表达式为假的因素,立即停止求值。

例如:

6 > 2 && 3 == 3 为真

!(6 > 2 && 3 == 3) 为假

x != 0 && (20 /5) < 5 只有当 x 不为 0 时,才计算第二个表达式的值

7.3.4 范围

可以把 && 运算符用于测试范围。例如,若要检查 90 到 100 范围内的得分,可以这样做:

if (range >= 90 && raned <= 100)
printf (" Good show \n");

一定要注意避免效法像下面这样的数学上常用的写法:

if (90 <= raned <= 100) // 不! 不要这样写!
printf (" Good show \n");

问题在于该代码是语义错误,而不是语法错误,所以编译器并不会捕获它(尽管可能会发出警告)。因为对 <= 运算符的求值顺序是由左到右的,所以会把该测试表达式解释为如下形式:

(90 <= range )<= 100

子表达式 90 <= range 的值为 1 (真)或 0 (假)。任何一个值都小于 100,因此不管 range 的值是什么,整个表达式总为真,所以需要使用 && 来检查范围。

大量现有代码利用范围测试来检测一个字符是不是(比方说)小写字母。例如,假设 ch 是个 char 变量:

if (ch >= ‘a‘ && ch <= ‘z‘)
printf ("That‘s a lowercase character \n");

这对于像 ASCII 那样的字符编码可以工作,因为在这种编码中连续字母的编码是相邻的数值。然而,对于包括 EBCDIC 在内的一些编码就不正确了。进行这种测试的移植性更好的方法是使用 ctype.h 系列(请参见 表 7.1 )中的 islower()函数:

if (islower(ch))
printf ("That‘s a lowercase charcter \n");

不管使用哪种特定的字符编码,islower()函数都能很好地运行(不过,一些早期的实现没有 ctype.h 系列的头文件)。

7.4 一个统计字数的程序

现在我们已经有工具来编写一个统计字数的程序了。统计字数的程序读取输入的字符并报告其中的单词个数。处理时也可以统计字符个数和行数。我们来看看这样一个程序包含哪些步骤。

首先,这个程序应该逐个读取字符,并且应该有些方法判断何时停止;第二,它应该识别并统计下列单位:字符,行和单词。下面是伪代码描述:

read a character // 读取一个字母
while there is more input // 如果输入的类型
increment character count // 是字符的字母计数加 1
if a line has been read increment line count // 如果是一行 行计数加1
if a word has been read increment word count // 如果是单词 单词计数加1
read next character //继续读入下一个

前面已经有输入的循环的模型了:

while ((ch = getchar()) != STOP)
{
...
}

这里,STOP 代表通知输入结束的 ch 取值。前面的示例程序已经使用了换行符和句号来用于该目的,但对于一个通用的单词统计程序这两个都不合适。现在我们暂且选择一个在文本中不常见的字符(|)。在第 8 章“字符输入/输出和输入确认”中,我们将会介绍一个更好的解决方案,以使程序既能处理文本文件又能处理键盘输入。

现在来考虑一下循环体。因为程序使用 getchar()来输入字符,所以可以在每个循环周期通过递增一个计数器的值来统计字符。为了统计行数,程序可以检查换行符。如果字符是换行符,程序就递增行数计数器的值。有个问题是如果 STOP 字符出现在一行的中间该怎么办。行数计数器应该不应该增加呢?一种做法是将它作为一个不完整行统计,也就是说,该行有字符而没有换行符。可以通过追踪一个字符来识别这种情况。如果 STOP 之前所读入的最后一个字符不是换行符,就计为一个不完整行。

最棘手的部分是识别单词。首先,必须明确定义一个单词意味着什么。让我们以一个相对简单的方法将一个单词定义为不包括空白字符(也就是说,没有空格,制表符或换行符)的一系列字符。因此,“glymxch ”和 “r2d2”是单词。一个单词以程序首次遇到非空白字符开始,在下一个空白字符出现时结束。检测非空白字符最简单明了的判断表达式是这样的:

c != ‘‘ && c != ‘\n‘ && c != ‘\t‘ // 当 c 不是空白字符时,表达式为真

检测空白字符最简单明了的判断是:

c == ‘‘ || c == ‘\n‘ || c = ‘\t‘ // 当 c 是空白字符时,该表达式为真

但是,使用 ctype.h 中的 isspace()函数会更简单。如果该函数的参数是空白字符,它就返回真。因此如果 c 是空白字符,isspace(C) 为真; 而如果 c 不是空白字符,!isspace(c)为真。

为了知道一个字符是不是在某个单词里,可以在读入一个单词的首字符时把一个标志(命名为 inword)设置为 1 。也可以在此处递增单词计数。

然后,只要 inword 保持为 1 (或真),后续的非空白字符就不标记为一个单词的开始。到出现下一个空白字符时,必须将此标志重置为 0 (或假),并且程序准备搜索下一个单词。转换为伪代码是这样的:

if c is not whitespace and inword is false // 如果 c 不是空白字符和标志为假
set inveord to true and count the word // 将标志置1(或真)和单词计数递增
if c is whitespace and inword is true // 如果 c 是空白字符和标志为真
set inword to false // 将标志置0 (或假)

这种方法在每个单词开始时将 inword 设为 1 (真),而在每个单词结束时将其设为 0 (假)。仅在该标志从 0 变为 1 时对单词计数。如果在你的系统上可以使用 _Bool 型变量,可以包含stdbool.h 头文件并用 bool 作为 inword 的类型,取值分别为 true 和 false 。否则,就使用 int 类型, 取值为 0 和 1.

如果使用布尔型变量,通常的习惯是用变量自身的值作为判断条件。也就是说用:

if (inword)

来代替

if (inword == true)

并且用

if(!inword)

来代替

if (inword == false)

依据是,如果 inword 为 true,则表达式 inword == true 结果为 true;而如果 inword 为false,则该表达式也为 false。因此,倒不只用 inword 作为判断条件。与之类似,!inword 与表达式 inword == false 值相同(非真即false,非假即 true)。

程序清单 7.7 将这些思想(识别行,识别不完整行以及识别单词)翻译为 C 代码。

程序清单 7.7 wordcnt.c 程序
-----------------------------------------------------------------
/* wordcnt.c -- 统计字符,单词 和 行 */
#include <stdio.h>
#include <ctype.h> // 为 isspace()提供函数原型
#include <stdlib.h>
#define STOP ‘|‘
int main (void)
{
char c; //读入字符
char prev; //前一个读入字符
long n_chars = 0L; //字符数
int n_lines = 0; //行数
int n_words = 0; //单词数
int p_lines = 0; // 不完整的行数
bool inword = false; // 如果 c 在一个单词中,则 inwrd 等于 true

printf ("Enter text to be analyzed(| to terminate) : \n");
prev = ‘\n‘; // 用于识别完整的行
while ((c = getchar()) != STOP)
{
n_chars++; // 统计字符
if (c == ‘\n‘)
n_lines++; // 统计行
if (!isspace(c) && !inword)
{
inword = true; // 开始一个新单词
n_words++; // 统计单词
}
if (isspace(c) && inword)
inword = false; // 到达单词的尾部
prev = c; //保存字符值

}
if (prev != ‘\n‘)
p_lines = 1;
printf (" characters = %d, words =%d, lined = %d",n_chars,n_words,n_lines);
printf (" partial lines = %d\n",p_lines);
system("PAUSE");
return 0;
}

下面是一个运行实例:
Enter text to be analyzed(| to terminate) :
Reason is a
Dowerful servant but
an inadequate msster.
|
characters = 55, words =9, lined = 3 partial lines = 0

程序用逻辑运算符把伪代码翻译为 C 代码。例如:

if (!isspace(c) && !inword)

注意,!inword 等价于 inword == false 。整个判断条件当然比单独判断每个空白字符要更可读。

if (c != ‘ ‘ && c != ‘\n‘ && c != ‘\t‘ && !inword)

上面的任何一种形式都表示:“如果 c 不是空白字符,并且如果 c 不处于一个单词里”。假如两个条件都满足,那么一定是新单词的开始,所以递增 n_words。如果处于一个单词的中间,那么第一个条件是满足的,但是 inword 为 true,所以 n_words 不递增。

当遇到下一个空白字符时,将 inword 再次设为 false。检查一下代码,看看当两个单词之间有数个空格时,程序是否正确。第 8 章说明了怎样修改这个程序以统计一个文件中的单词。

7.5 条件运算符 ?:

C 提供一种简写方式来表示 if else 语句的一种形式。这被称为条件表达式,并使用条件运算符
(?:)。这是个有三个操作数的分两部分的运算符。回忆一下,有一个操作数的运算符称为一元运算符,有两个操作数的运算符称为二元运算符。按照该惯例,有三个操作数的运算符就应该称为三元运算符,条件运算符就是 C 的该类型的唯一的一个例子。下面是一个得到一个数的绝对值的例子:

x = (y < 0) ? -y : y;

在 = 和分号之间就是条件表达式。这个语句的意思是:“如果 y 小于 0 ,那么 x =-y ;否则,x = y 。以 if else 的说法,可以这样表达:

if (y < 0)
x = -y;
else
x = y;

下面是条件表达式的一般形式;

expression1 ? expression2 : expression3

如果 expression1 为真(非零),整个条件表达式的值和 expression2 的值相同。如果
expression1 为假(零),整个条件表达式的值等于 expression3 的值。

当希望将两个可能的值中的一个赋给时,可以使用条件表达式。典型的例子是将两个值中最大的值赋给变量:

max = (a > b) ? a: b; (如果 a 大于 b 那么 max 等于 a ,否则等于 b)

通常,if else 语句能完成与条件运算符同样的功能。但是,条件运算符语句更简洁;并且,依赖编译器,可以产生更精简的程序代码。

我们来看看程序清单 7.8 所示的一个喷漆程序的例子。这个程序计算向给定的平方英尺的面积涂油漆,全部涂完需要多少罐油漆。基本的数学法则很简单;用平方英尺数除以每罐油漆能涂抹的平方英尺数。但是,假设结果是 1.7 罐会怎样呢?商店整罐卖油漆,而不拆开卖,所以必须买两罐。因此,程序在得到非整数罐的结果时应该进 1 。条件运算符常用于处理这种情况,而且在适当的时候也用来打印 can 或 cans

程序清单 7.8 paint.c 程序
---------------------------------------------------
/* paint.c --- 使用条件运算符 */
#include <stdio.h>
#define COVERAGE 200 /* 每罐油漆可喷平方英尺数 */
int main (void)
{
int sq_feet;
int cans;

printf ("Enter number of square feet to be painted : \n");
while (scanf("%d",&sq_feet) == 1)
{
cans = sq_feet / COVERAGE;
cans += ((sq_feet % COVERAGE == 0)) ? 0:1;// 输入除常量的余数=0则cans+0 否则+1
printf ("You need %d %s of paint \n",cans, cans == 1? "can":"cans");
printf ("Enter next value(q to quit) :\n");
}
return 0;
}

下面是一个运行示例:

Enter number of square feet to be painted :
200
You need 1 can of paint
Enter next value(q to quit) :
215
You need 2 cans of paint
Enter next value(q to quit) :
q

因为程序使用 int 类型,所以除法被截断了;也就是说,215/200 的结果是 1 。因此, cans 被四舍五入为整数部分。如果 sq_feet % COVERAGE 等于 0, 那么 sq_feet 被 COVERAGE 整除, cnas值不变,否则有余数,所以加上 1 。这由下列语句完成的:

cans += ((sq_feet % COVERAGE == 0)) ? 0:1;

它将 cans 加上 += 右边表达式的值。右边的表达式是个条件表达式,值为 0 或 1,依赖于
sq_feet 是否被 COVERAGE 整除。

printf()函数最终的参数也是一个条件表达式

cans == 1? "can":"cans");

如果 cans 的值是 1,使用字符串 can ,否则使用字符串 cans 。这表明条件运算符也可以使用字符串作为它的第二和第三个操作数 。

-------------------------------------------------------
总结: 条件运算符

条件运算符:

?:

总体注解:

这个运算符带有三个操作数,每个操作数都是一个表达式。它们如下排列:

expression1 ? expression2 : expression3;

如果 expression1 为真,它的值为 expression2 ,否则为 expression3.

例如:

(5 > 3) ? 1: 2 // 5 > 3 为真,所以结果为 1

(3 > 5) ? 1: 2 // 3 > 5 为假,所以结果为 2

(a > b)? a: b // a > b 为真,取值a, 为假的话 取值 b

7.6 循环辅助手段: continue 和 break

一般说来,进入循环体以后,在下次循环判断之前程序执行循环体中所有的语句。continue 和 break 语句使你可以根据循环体内进行的判断结果来忽略部分循环甚至终止它。

7.6.1 continue 语句

该语句可以用于三种循环形式。当运行到该语句时,它将导致剩余的迭代部分被忽略,开始下一次迭代。如果 continue 语句处于嵌套结构中,那么它仅仅影响包含它的最里层的结构。让我们在程序清单 7.9 简短程序中来试验一下 continue 。

程序清单 7.9 skippart.c 程序
-----------------------------------------------------------------------
/* skippart.c --- 使用 continue 跳过部分循环 */
#include <stdio.h>
int main (void)
{
const float MIN = 0.0f;
const float MAX = 100.0f;

float score;
float total = 0.0f;
int n = 0;
float min = MAX;
float max = MIN;

printf (" Enter the first score (q to quit) :");
while (scanf("%f",&score) == 1)
{
if (score < MIN || score > MAX)
{
printf ("%0.1f is an invalid value. Try again :",score);
continue;
}
printf (" Acceptiong %0.1f :\n",score);
min = (score < min) ? score: min;
max = (score > max) ? score: max;
total += score;
n++;
printf ("Enter next score (q to quit) : ");
}
if (n > 0)
{
printf (" Average of %d scores is % 0.1f.\n",n,total/n);
printf ("Low = % 0.1f, high = %0.1f \n",min,max);
}

else
printf ("No valid scores were entered .\n");
system("PAUSE");
return 0;
}

在程序清单 7.9 中,while 循环读取输入内容,直到输入非数字数据。循环里的 if 语句筛选出无效的分数值。比如,如果输入 188 ,那么程序就会说明: 188 is an invalid value 。然后,continue 语句导致程序跳过循环其余的用于处理有效输入的部分。程序开始下一个循环周期,试图读取下一个输入值。

注意,有两种方法可以避免使用 continue 。 一种是省去 continue ,将循环的剩余部分放在一个 else 代码块中:

if (score < 0 || score > 100)
/* printf () 语句 */
else
{
/* 语句 */
}

或者用这种格式来代替:

if (score >= 0 && score <= 100)
{
/* 语句 */
}

在这种情况中使用 continue 的一个臼是可以在主语句组中消除一级缩排。当语句很长或者已经有很深的嵌套时,简练可以增强可读性。

continue 的另一个用处是作为占位符。例如,下面的循环读取并丢弃输入,直到一行的末尾(包括行尾字符):

while (getchar() != ‘\n‘)
;

当程序已经从一行中读取了一些输入并需要跳到下一行的开始时,使用上面的语句很方便。问题是孤立的分号难以被注意。如果使用 continue, 代码就更具可读性,如下所示:

while ((ch = getchar()) != ‘\n‘)
{
if (ch == ‘\t‘)
continue;
putchar(ch);
}

该循环跳过制表符,并且仅当遇到换行符时退出。它可以更简洁地这样表示:

while ((ch = getchar()) != ‘\n‘)
if (ch != ‘\t‘)
putchar(ch);

通常,在这种情况下,可以把 if 的判断取逆以消除对 continue 的需求。

你已经看到了 continue 语句导致循环体的剩余部分被跳过。那么在什么地方继续循环呢?对于 while 和 do while 循环,continue 语句之后发生的动作是求循环判断表达式的值。例如,考虑下列循环:

count = 0;
while (count < 10);
{
ch = getchar();
if (ch == ‘\n‘)
continue;
putchar(ch);
count++;
}

它读入 10 个字符(换行符除外,因为当 ch 为换行符时会跳过 count++; 语句)并回显它们,其中不包括换行符。continue 语句被执行时,下一个被求值的表达式是循环判断条件。

对于 for 循环,下一个动作是先求更新表达式的值,然后再求循环判断表达式的值。例如,考虑下列循环:

for (count = 0; count < 10; count++)
{
ch = getchar();
if (ch == ‘\n‘)
continue;
putchar(ch);
}

在本例中,当 continue 语句被执行时,首先递增 count,然后把 count 与 10 相比较。因此,这个循环的动作稍稍不同于 while 循环的例子。像前面那样,仅仅显示非换行符,但这时换行符也被包括在计数中,因此它读取包含换行符在内的 10 个字符。

7.6.2 break 语句

循环中的 break 语句导致程序终止包含它的循环,并进行程序的下一阶段。在程序清单 7.9 中,如果用 break 代替 continue ,那么(比方说)在输入了 188 的时候,不是跳到下一个循环周期,而是导致循环退出。图 7.4 比较了 continue 和 break 。如果 break 语句位于嵌套循环里,它只影响包含它的最里层的循环。

图 7.4
-----------------------------------------------------------------------------------------
-------------------
| |
while ((ch + getchar()) != EOF) while ((ch = getchar()) != EOF) |
{ { |
blahblah(ch); blahblah(ch); |
if (ch == ‘\n‘); if (ch == ‘\n‘); |
break; ----------- continue; >---------------------
yakyak(ch); | yakyak(ch);
} | }
blunder(n,m); <------- blunder(n,m);

比较 break 和 continue
------------------------------------------------------------------------------------------

有时,break 被用于在出现其他原因时退出循环。程序清单 7.10 用一个循环来计算矩形的面积。如果用户输入一个非数字作为矩形的长度或宽度,那么循环终止。

程序清单 7.10 break.c 程序
----------------------------------------------------
/* break.c --- 使用 break 退出循环 */
#include <stdio.h>
int main (void)
{
float length,width;

printf (" Enter the length of the rectangle : \n");
while ( scanf ("%f",&length) == 1)
{
printf (" Length = %0.2f; \n",length);
printf (" Enter its width : \n");
if (scanf ("%f", &width) != 1)
break;
printf (" Width = %0.2f; \n",width);
printf (" Area = %0.2f :\n",length * width);
printf (" Enter the length of the rectangle :\n");
}
printf ("Done \n");
return 0;
}

也可以这样控制循环:

while (scanf ("%f %f",&length, &width) == 2)

但是,使用 break 可以使单独回显每个输入值方便。

和 continue 一样,当 break 使代码更复杂时不要使用它。例如,考虑下列循环:

while ((ch = getchar()) != ‘\n‘)
{
if (ch == ‘\t‘)
break;
putchar(ch)‘
}

如果两个判断都在同一个位置,逻辑就更清晰了:

while ((ch = getchar()) != ‘\n‘ && ch != ‘\t‘)
putchar(ch);

break 语句实质上是 switch 语句的附属物,这在后面讨论。

break 语句使程序直接转到紧接着该循环后的第一条语句去执行;在 for 循环中,与 continue 不同,控制段的更新部分也将被跳过。嵌套循环中的 break 语句只是使程序跳出里层的循环,要跳出外层的循环则还需要另外一个 break 语句。

int p,q;
scanf("%d",&p);
while (p > 0)
{
printf ("%d\n",p);
scanf ("%d",&q);
while ( q > 0)
{
printf ("%d\n",p * q);
if ( q > 100)
break; // 跳出里层循环
scanf ("%d",&q);
}
if ( q > 100)
break; // 跳出外层循环
scanf ("%d",&p);
}

7.7 多重选择: switch 和 break

使用条件运算符和 if else 结构可以很容易地编写从两个选择中进行选择的程序。然而,有时程序需要从多个选择中选择一个。可以利用 if else if...else 来这样做,但多数情况下,使用 C 的switch 语句更加方便。程序清单 7.11 举例说明了 switch 语句是怎样工作的。该程序读入一个字符,然后相应地输出以该字符开关的动物名称。

程序清单 7.11 animals.c 程序
--------------------------------------------------------
/* animals.c ---- 使用 switch 语句 */
#include <stdio.h>
#include <ctype.h>
int main (void)
{
char ch;
printf ("Give me a letter of the alphabet,and I will give ");
printf (" an animal name\nbeginning with that letter \n");
printf (" Please type in a letter : type # to end my act .\n");
while ((ch = getchar()) != ‘#‘)
{
if (‘\n‘ == ch)
continue;
if (islower(ch)) // 只识别小写字母
switch(ch)
{
case ‘a‘:
printf ("argali,a wild sheep of Asia \n");
break;
case ‘b‘:
printf ("babirusa,a wild pig of Malay \n");
break;
case ‘c‘:
printf (" coati,racoonlike mammal \n");
break;
case ‘d‘:
printf ("desman,aquatic,molelike critter \n");
break;
case ‘e‘:
printf (" echidna,the spiny anteater \n");
break;
case ‘f‘:
printf (" fisher,brownish marten \n");
break;
default:
printf (" That‘s a stumper \n");
} // switch 语句结束
else
printf (" I recognize only lowercase letters \n");
while (getchar() != ‘\n‘)
continue; // 跳过输入行的剩余部分
printf (" Please type another letter of a #\n");
} // while 循环结束
printf ("bye \n");
return 0;
}

我们很懒散地只到 f 就停止了,但是后面可以依此类推。

本程序的两个主要特点是 switch 谗的使用和输入的处理。首先看看 switch 语句怎样工作。

7.7.1 使用 switch 语句

紧跟在单词 switch 后的圆括号里的表达式被求值。在这里,它就是刚刚输入给 ch 的值。然后程序扫描标签(label)列表(case‘a‘:, case‘b‘:,以此等等),如果有被标记为 default:的标签行,程序就跑到该行;否则,程序继续处理跟在 switch 语句之后的语句。

break 语句有什么作用呢?它导致程序脱离 switch 语句,跳到 switch 之后的下一条语句(请参见图 7.5)。如果没有 break 语句,从相匹配的标签到 switch 末尾的每一条语句都被处理。例如,如果把程序里面的 break 去掉,然后输入字母 d 来运行程序,会得到如下交互结果:

从 case‘d‘: 到 switch 结尾的所有语句都被执行了。

顺便提一下,break 语句用于循环和 switch 中,而 continue 仅用于循环。但是,如果 switch 语句位于一个循环中,则可以把 continue 用于 switch 语句的一部分。在这种情况下,就像在其他的循环中一样,continue 导致程序跳过该循环的其余部分,其中包括 switch 的其余部分。

如果你熟悉 pascal,就会发觉 switch 语句和 pascal 的 case 语句很相似。最大的差别在于,如果仅希望处理某个带有标签的语句,switch 语句要求使用 break。另外,不能在 C 的 case 中使用一个范围。

圆括号中的 switch 判断表达式应该具有整数值(包括 char 类型)。case 标签必须是整型(包括 char)常量或者整数常量表达式(仅包含整数常量的表达式)。不能用变量作为 case 标签。因而,switch 结构是这样的:

switch (integer expression)
{
case constant1:
statements -- 可选
case constant2:
statements -- 可选
default:
statements -- 可选
}

7.7.2 只读取一行的首字符

animals.c 的另一个特点是它读取输入的方法。在运行示例中你可能已经注意到,当输入 dab 时,仅仅处理了第一个字符。这种特性在期望响应单字符的交互式程序中通常很合适。产生这种动作的是下面的代码:

while (getchar() != ‘\n‘);
continue; // 跳过输入行的剩余部分

这个循环从输入读取字符,直到出现由回车键产生的换行字符。注意,函数返回值没有被赋给 ch ,因此,字符仅被读取并丢弃。因为最后一个被丢弃的字符是换行符,所以下个读入的字符是下一行的首字符。在外层 while 循环中,由 getchar() 读取它并将其值赋给 ch 。

假设用户开始时按下了回车键,以致遇到的第一个字符是换行符。下面的代码处理这种可能:

if (ch == ‘\n‘)
continue;

7.7.3 多重标签

如程序清单 7.12 所示,可以对一个给定的语句使用多重 case 标签;

程序清单 7.12 vowels.c 程序
-------------------------------------------------------------------------
/* vowels.c -- 使用多重标签 */
#include <stdio.h>
int main (void)
{
char ch;
int a_ct, e_ct, i_ct, o_ct, u_ct;
a_ct = e_ct = i_ct = o_ct = u_ct = 0;

printf (" Enter some text: enter # to quit \n");
while ((ch = getchar()) != ‘#‘)
{
switch(ch)
{
case ‘a‘:
case ‘A‘: a_ct++;
break;
case ‘e‘:
case ‘E‘: e_ct++;
break;
case ‘i‘:
case ‘I‘: i_ct++;
break;
case ‘o‘:
case ‘O‘: o_ct++;
break;
case ‘u‘:
case ‘U‘: u_ct++;
break;
default : break;
} // switch 语句结束
} // while 循环结束
printf (" number of vowels : A E I O U \n");
printf (" %4d %4d %4d %4d %4d\n",a_ct, e_ct, i_ct, o_ct, u_ct);
return 0;
}

假定 ch 是字母 i ,则 switch 语句定位到标签为 case ‘i‘: 的位置。因为没有 break 同该标签相关联所以程序流程继续前进到下一个语句,即 i_ct++; 如果 ch 是 I ,程序流程就直接写作到那条语句。本质上,两个标签都指向相同的语句。

严格地讲,case ‘U‘ 的 break 语句并不是必需的,因为即便没有这个 break,程序还是会进行
switch 结构的下一个语句,也就是 default 情况下的 break 语句。因此, case ‘U‘ 的 break 语句可以去掉以缩短代码。另一方面,如果后面还需要再添加其他的情况(例如,你可能需要把 y 计为元音),那么现在保留这个 break 可以防止你到时候记了添加。

下面是一个运行示例:

Enter some text: enter # to quit
I see under the overseer.#
number of vowels : A E I O U
0 7 1 1 1

在这个特例中,可以通过使用 ctype.h 系列 (表 7.2 )中的 toupper()函数在进行判断之前将所有的字母转换为大写字母来避免多重标签。

while ((ch = getchar()) != ‘#‘)
{
switch(ch)
{
case ‘a‘:
case ‘A‘: a_ct++;
break;
case ‘e‘:
case ‘E‘: e_ct++;
break;
case ‘i‘:
case ‘I‘: i_ct++;
break;
case ‘o‘:
case ‘O‘: o_ct++;
break;
case ‘u‘:
case ‘U‘: u_ct++;
break;
default : break;
} // switch 语句结束
} // while 循环结束

或者,如果希望保留 ch 的值不变化,可以这样使用该函数:

switch (toupper(ch))

------------------------------------------------------------------------

PS 总结: 使用 switch 进行多重选择

关键字:

switch

总体注解:

程序控制按照 expression 的值跳转到相应的 case 标签处。然后程序流程继续通过所有剩余的语句,直到再次由 break 语句重定向。 expression 和 case 标签必须都是整型值(包括类型 char ),那么控制定位到标签为 default的语句,如果它存在的话。否则,控制传递给紧跟着 switch 语句的下一条语句。

格式:

switch (expression)
{
case label1: statement1 // 使用 break 跳至 结尾处
case label2:statement2
default: statement3
}

可以存在两以上的标签语句,并且 default 语句是可选的。

例如:

switch (choice)
{
case 1:
case 2: printf (" Darn tootin \n");
case 3: printf ("Quite right \n");
case 4: printf (" Good show \n");
default: printf ("Have a nice day \n");
}

如果 choice 为整型值 1 或 2,则打印第一条消息。如果它的值为 3,则打印第二条和第三条消息(因为在 case 3 后没有 break 语句,所以流程继续到随后的语句)。如果它的值为 4,则打印第三条消息。对于其他值,仅打印最后一条消息。

7.7.4 switch 和 if else

什么时候该使用 switch ,而什么时候又该使用 if else 结构呢?通常是没有选择的。如果选择是基于求一个浮点型变量或表达式的值,就不能使用 switch。如果变量必须落入某个范围,也不能很方便地使用 switch 。这样写是很简单的:

if ( integer < 1000 && integer > 2)

很不幸,用一个 switch 语句覆盖该范围将涉及为从 3 到 999 的每个整数建立 case 标签。然而,如果可以使用 switch,程序通常运行得稍快点,而且占据较小的代码。

7.8 goto 语句

早期版本 BASIC 和 FORTRAN 所依赖的 goto 语句在 C 语言中是有效的。然而,不同于那两种语言,C 没有它也可以工作得相当好。Kernighan 和 Ritchie 认为 goto 语句“非常容易被滥用”,并且建议“要谨慎使用,或者根本不用”。我们首先介绍怎样使用 goto,然后说明为什么通常不需要使用它。

goto 语句包括两个部分:goto 和一个标签名称。标签的命名遵循与命名变量相同的约定,如下例所示:

goto part2;

为使上述语句工作,函数必须包含由 part2 标签定位的其他语句。这可以通过以标签名紧跟一个冒号来开始一条语句完成:

part2: printf ("Refined analysis: \n");

避免 goto

原则上,C 程序根本不需要使用 goto 语句。但是如果你有使用早期版本 FORTRAN
或 BASIC --- 这两种语言都需使用 goto 的背景,可能会有依赖使用 goto 的开发设计习惯。为帮你克服这种依赖性,我们将略述一些常见的 goto 情形,然后展示一个 C 习惯的方式。

---------------------------------------------------

1. 处理需要多条语句的 if 情形:

if (size > 12)
goto a;
goto b;
a: cost = cost * 1.05;
flag = 2;
b: bill = cost * flag;

在旧式风格的 BASIC 和 FORTRAN 中,只有直接跟在 if 条件后的单条语句隶属于该 if 。没有代码块或复合语句的规定。我们已经将这种模式转换为与 C 等价的模式。使用复合语句或代码块的标准 C 方法更易于使用:

if ( size > 12)
{
cost = cost * 1.05;
flag = 2;
}
bill = cost * flag;
-------------------------------------------------------

2. 二中选一;

if (ibex > 14)
goto a;
sheds = 2;
goto b;
a: sheds = 3;
b: help = 2* sheds;

C 语言可以使用 if else 结构更清晰地表示这种选择:

if (ibex > 14)
sheds = 3;
else
sheds = 2;
help = 2 * sheds;

实际上,新版本的 BASIC 和 FORTRAN 已经将 else 加入到新的语法中。

---------------------------------------------------------------------

3. 建立不确定循环:

reabin : scanf ("%d",&score);
if (score < 0)
goto stage2;
lots of statements;
goto reabin;
stage2: more stuff;

用 while 循环代替

while ((scanf ("%d",&score)) <= 0)
{
lots of statements;
scanf ("%d",&score);
}
more stuff;

-------------------------------------------------------------------------

4. 跳到循环末尾并开始下一轮循环: 用 continue 代替。

-------------------------------------------------------------------------

5. 跳出循环: 用 break 代替。实际上,break 和 continue 是 goto 的特殊形式。使用它们的好处是它们的名字表明它们的意味着什么;并且,因为它们不使用标签,所以不存在放错标签位置的潜在危险。

---------------------------------------------------------------------------

6. 胡乱地跳转到程序的不同部分: 千万不要!!

---------------------------------------------------------------------------

但有一种 goto 的使用被许多 C 专业人员所容忍:在出现故障时从一组嵌套循环中跳出(单条 break 仅仅跳出最里层的循环)。

while (funct > 0)
{
for ( i=1; i <= 100; i++)
{
for (j = 1; j <= 50; j++)
{
statements galore;
if (bit trouble)
goto help;
statements;
}
more statements;
}
yet more statemens;
}
and more statements;
help: bail out;

正如从其他的例子可以看到的,可供选择的形式比 goto 形式更清晰。当这几种情形混合在一起时,这种差异甚至变得更明显。哪些 goto 协助 if ,哪些 goto if else ,哪些 goto 控制循环,哪些只是因为你的程序已经无路可走才放在那里的?过度地使用 goto ,会引起程序流程的错综复杂。如果不熟悉 goto ,要不使用它;如果已经习惯于使用它,试着训练自己不使用。具有讽刺意味的是, C 不需要 goto ,却有一个比大多数语言更好的 goto ,因为它允许你在标签中使用描述性的单词而不是数字。

PS: 总结: 程序跳转

关键字:

break,continue,goto

总体注解:

下面三条指令导致程序流程从程序的一个位置跳转到另一个位置

-----------------------------------------------------------------------
break命令:

break 命令可以与三种循环形式中的任何一种以及 switch 语句一起使用。它导致程序控制跳过包含它的循环或 switch 语句的剩余部分,继续执行紧跟在循环或 switch 后的下一条命令。

例如:

switch (number)
{
case 4: printf (" Thas‘s a good choice \n");
break;

case 5: printf (" That‘s a fair choice \n");
break;

default: printf (" Thas‘s a poor choice \n");
}

---------------------------------------------------------------------------------

continue 命令:

continue 命令可以与三种循环形式中的任何一种一起使用,但不能和 switch 语句一起使用。它导致程序控制跳过循环中的剩余语句。对于 while 或 for 循环,开始下一个循环周期。对于 do while 循环,对退出条件进行判断,如果必要,开始一个循环周期。

例如:

while ((ch = getchar()) != ‘\n‘)
{
if (ch == ‘ ‘)
continue;
putchar(ch);
chcount++;
}

这段代码回显并统计非空格字符。

----------------------------------------------------------------------------

goto命令:

goto 语句导致程序控制跳转到由指定标签定位的语句。冒号用来将被标记的语句同它的标签相分隔。标签名遵循变量的命名规则。被标记的语句可以出现在 goto 之前或之后。

格式:

goto label:
.
.
.
label;statement

例如:

top: ch =getcahr();
.
.
.
if (ch != ‘y‘);
goto top;

7.9 关键概念

智能的一个体现方面是根据环境调节反应的能力。所以,选择语句是开发具有智能行为程序的基础。在 C 中, if ,if else 和 switch 语句,连同条件运算符 (?:) 一起实现了选择。

if 和 if else 语句使用一个判断条件来决定执行哪条语句。任何非零值被视为 true,零值被视为 false。 典型地,判断包括关系表达式(它比较两个值)以及逻辑表达式(它使用逻辑运算符组合或更改其他表达式)。

需要牢记的一条通用规则是,如果想要判断两个条件,应该使用逻辑运算符将两个完整的判断表达式连接起来。例如,发下两个尝试是错误的。

if (a < x < z) // 错误。没有逻辑运算符

if (ch != ‘q‘ && != ‘Q‘) // 错误,缺少完整的判断

记住,正确的方法是用逻辑运算符将两个关系表达式连接起来;

if (a < x && x < z) // 使用 && 组合两个表达式

if (ch != ‘q‘ && ch != ‘Q‘) // 使用 &7 组合两个表达式

最近两章所介绍的控制语句使你能够处理比在这之前所处理的更加强大和更具挑战性的程序。只要将这些章节的一些例子与前些章节的相比较,你就可以看出这一点。

7.10 总结

本意给出了相当多的要回顾的主题,那么让我们来看看。

if 语句利用判断条件来控制程序是否执行紧跟在判断条件后的一个简单语句或代码块。如果判断表达式为非零值,就执行语句;如果为零值,则不执行语句。

if else 语句使你能够从两个选项中进行选择。如果判断条件为非零值,就执行 else 之前的语句。如果判断表达式的结果为非零值,就执行 else 之前的语句。如果判断表达式的结果为零值,执行紧跟在 else 之后的语句。通过紧跟在 else 语句之后使用另一个 if 语句,可以建立在一系列可供选择的事物中进行选择的结构。

判断条件通常是一个关系表达式,也就是用一个关系运算符构成的表达式,例如 < 或者 == 。利用
C 的逻辑运算符,可以组合多个关系表达式以创建更复杂的判断。

使用条件运算符 (?:) 可以产生一个表达式,这样的表达式在多数情况下比 if else 语句提供更加简洁的二中选一。

ctype.h 系列字符函数(例如 isspace() 和 isalpha())为创建基于分类字符的判断表达式提供了使得的工具。

switch 语句使你能够从一系列以整数值为标签的语句中进行选择。如果紧跟在 switch 关键字后的判断条件的整数值与某标签相匹配,执行就定位到由该标签定位的语句。然后执行继续完成紧跟在该标签语句后的语句,直到遇到一个 break 语句。

break ,continue 和 goto 是跳转语句,导致程序流程跳转到程序的其他位置。break 语句导致程序跳转到紧跟在包含它的循环或 switch 末尾的下一条语句。continue 语句导致程序跳过包含它的循环的剩余部分,开始下一下循环周期。

7.11 复习题

----------------------------------------------------------

1. 确定哪个表达式为 true,哪个为 false。

a. 100 > 3 && ‘a‘ > ‘c‘ // false 因为不确定 a 是否大于 c &&是要表达式都为真才为真

b. 100 > 3 || ‘a‘ > ‘c‘ // true 因为 || 只要一个表达式为真 即为真

c. !(100 > 3) // false 因为 ! 表达式为真即为假,或相反

-------------------------------------------------------------------

2. 构造一个表达式来表示下列条件:

a. number 等于或大于 90 ,但是小于 100 // number >= 90 && number < 100;

b. ch 不是字符 q 也不是字符 k // ch != ‘q‘ && ch != ‘k‘;

c. number 界于 1 到 9 之前(包括 1 和 9 ),但是不等于 5 。
// number >= 1 && number <=9 && number != 5;

d. number 不在 1 到 9 之间 // number < 1 || number > 9 ;

--------------------------------------------------------------------------

3. 下面程序中的关系表达式过于复杂,并有些错误,请简化并改正它。

#include <stdio.h>
int main (void) // 1
{ // 2
int weight,height; // weight 以磅为单位, height 以英寸为单位 // 3
// 4
scanf ("%d,weight,height); // 5
if (weight < 100 && height > 64) // 6
if ( height >= 72) // 7
printf ("you are very tall for your weighe \n"); // 8
else if (height < 72 && >64) // 9
printf ("you are tall for your weight \n"); //10
else if (weight > 300 && !(weight <= 300)) // 11
&& height < 48) // 12
if (!(height >= 48) // 13
printf ("you are qutie short for your weight \n"); //14
else // 15
printf ("your weight is ideal \n"); // 16
return 0; // 17
}

答:

第5行: 应该是 scanf("%d %d",&weight,&height); 在 scanf()中不要忘记使用 & 运算符。这一行前面也应该有提示输入的语句。但第 6 行已经保证 heigh > 64,因此,不需要任何测试,并且 if
else 应该是 else 。

第9行; 它的意思是 (height < 72 && height > 64)。但是表达式的第一部分是不必要的,因为既然程序已经到达了这个 else if ,那么 height 必然小于 72 。因此一个简单的 (height > 64)就可以了。

第11行; 条件冗余;第二个子表达式(weight 不是小于或等于300的)与第一个子表达式意义相同。所需要的只是一个简单的( weight > 300)。但是这里还有更多的问题。第11行属于一个不正确的if!
很明显,这个 else 是与第 6行中的 if 相匹配的。但是根据 if 的“最接近规则”,它会与第 9行的 if 相匹配。因此会在 weight 小于 100 并且 height 小于或等于 64 时到达第11行。这就使得在到达该语句时 weight 不可能超过 300 。

第 7 到 9 行: 应该用花括号括起来。这样第 11 行就会是这第 6行而不是第 9行的可选情况。而如果第 9行的 else if 由一个简单的 else 替代了,就不再需要花括号了。

第 13行: 应该简化为 if (height > 48)。其实这一行完全可以忽略,因为第 12行已经作了这种测试。

第 15行: 这个else 与第 13行的 if 相匹配。把第 13行和 14行括在花括号中可以强制使这个 else与第 11 行的 if 相匹配。或者,按照建议,简单地删掉第 13行。

下面是一个正确的版本;

#include <stdio.h>
int main (void)
{
int weight,heght; /* weight 以磅为单位,height 以英寸为单位 */

printf ("Enter your weight in pounds and ");
printf ("your height in inches \n");
scanf ("%d %d",&weight,%height);
if (weight < 100 && height > 64)
if (height >= 72)
printf ("you are very tall for your weight \n");
else
printf ("you are tall ofr your weight \n");
else if (weight > 300 && height < 48)
printf ("you are quite short for your weight \n"):
else
printf ("your weight is ideal \n");
return 0;
}

-----------------------------------------------------------------------------------------

4. 下列每个表达式的数值是多少?

a. 5 > 2 // 表达式为真 所以为 1

b. 3 + 4 > 2 && 3 < 2 // 3 < 2 不对,右边表达式为假 && 既然都为假 所以值为 0

c. x >= y || y > x // 表达式一个为真 即为真, 所以值为 1

d. d = 5 + (6 > 2) // d = 6 因为 (6>2)为真 值为1 5+1=d 即 6

e. ‘X‘ > ‘T‘ ? 10:5 // 值为 10 因为判断的条件为真

f. x > y ? y > x: x > y // 值为 0 因为如果 x > y 为真那么表达式的值就是 y > x 这种 情况下它就为假或0 ,如果 x > y 为假,表达式的值就是 x > y
这种情况下它也是为假或0.

----------------------------------------------------------------------------------------

5. 下列程序将打印出什么?

#include <stdio.h>
int main (void)
{
int num;
for (num = 1; num <= 11; num++)
{
if (num % 3 == 0)
putchar (‘$‘);
else
putchar (‘*‘);
putchar (‘#‘)
printf (‘%‘);
}
putchar (‘\n‘);
return 0;
}

答: *#%*#%$#%*#%*#%$#%*#%$#%*#%

--------------------------------------------------------------------------------

6. 下列程序将打印出什么?

#include <stdio.h>
int main (void)
{
int i = 0;

while ( i < 3){
switch (i++){
case 0: printf ("fat");
case 1: printf ("hat");
case 2: printf ("cat");
default : printf("Oh no!");
}
putchar (‘\n‘);
}
return 0;
}

答:
fathatcatOh no!
hatcatOh no!
catOh no!

注; 第一次 i = 0 i++ 会等于1 但是是先选择 case 0 显示后 值才为 1
----------------------------------------------------------------------------------

7. 下列程序有什么错误 ?

#include <stdio.h>
int main (void)
{
char ch;
int lc = 0; /* 统计小写字符 */
int uc = 0; /* 统计大写字符 */
int oc = 0; /* 统计其他字符 */

while ((ch = getchar()) != ‘#‘)
{
if (‘a‘ <= ch >= ‘z‘)
lc++;
else if (!(ch <‘A‘) || !(ch >‘Z‘)
uc++;
oc++;
}
printf ("%d lowercase, %d uppercase, %d other ,lc,uc,oc);
return 0;
}

表达式 ‘a‘<= ch >=‘z‘ 应该被写成这样:

ch >= ‘a‘ && ch <= ‘z‘

或者用一种更简单也更通知的方法:包含 ctype.h 文件并使用 islower()。顺便提一下,‘a‘<= ch >=‘z‘ 在 C 中是合法的,只是不具有正确的意义。因为关系运算符是从左到右结合的,所以这个表达式被解释为值编码。0 和 1 都不能满足这个条件,所以整个表达式的值总是为 0(假)。在第二个判断表达式中,|| 应该是 && 。尽管 !(ch < ‘A‘ 是合法的,而且意义也正确,但 ch >= ‘A‘ 更为简单。‘Z‘ 后面需要有两个结束圆括号而不是一个。再一次,更简单的方法是使用 isupper()。应该在 oc++; 语句前面诱因一个 else,否则,每输入一个字符,它都会增加 1. 在 printf()调用中的控制表达式应该用双引号引起来。

下面是正确的版本

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main (void)
{
char ch;
int lc = 0; /* 统计小写字符 */
int uc = 0; /* 统计大写字符 */
int oc = 0; /* 统计其他字符 */

printf ("please input chars : \n");
//ch = getchar(); /* 如果要这个语句的话 第一个字母将会不算进统计 */
while ((ch = getchar()) != ‘#‘)
{
if (islower(ch))
lc++;
else if (isupper(ch))
uc++;
else
oc++;
}
printf ("%d lowercase, %d uppercase, %d other \n", lc,uc,oc);
system("PAUSE");
return 0;
}

-------------------------------------------------------------------

8. 下列程序将打印出什么?

/* retire.c */
#include <stdio.h>
int main (void)
{
int age = 20;

while (age++ <= 65)
{
if ((age %20) == 0) /* age 能被 20 整除么? */
printf (" You are %d .Here is a raise.\n",age);
if (age = 65)
printf (" You are %d .Here is your gold watch \n",age);
}
return 0;
}

答: 不幸的是,它无休止地打印同一行:

You are 65 .Here is your gold watch

问题在于 if (age = 65)

这行代码把 age 设置为 65 使得每个循环周期中判断条件都为真。

---------------------------------------------------------------------------------------

9. 当给定下述输入时,下列程序将打印出什么?

q
c
g
b
#include <stdio.h>
int main (void)
{
char ch;

while ((ch =getchar()) != ‘#‘)
{
if (ch == ‘\n‘)
continue;
printf ("Step 1\n");
if (ch == ‘c‘)
continue;
else if (ch == ‘b‘)
break;
else if (ch == ‘g‘)
goto laststep;
printf ("Step 2 \n");
laststep: printf ("Step 3\n");
}
printf ("Done \n");
return 0;
}

答:q :Step 1
Step 2
Step 3

c: Step 1

g: Step 1
Step 3

b: Step 1
Done

-----------------------------------------------------------------

10. 重写题目 9 的程序,以使它表现相同的行为但不使用 continue 或 goto

答:
#include <stdio.h>
int main (void)
{
char ch;

while ((ch =getchar()) != ‘#‘)
{
if (ch != ‘\n‘)
{
printf ("Step 1\n");
if (ch == ‘b‘)
break;
else if (ch != ‘c‘)
{
if (ch != ‘g‘)
printf ("Step 2 \n");
printf ("Step 3\n");
}
}
}
printf ("Done \n");
return 0;
}

7.12 编程练习

---------------------------------------------------------------------------------------
1. 编写一个程序。该程序读取输入直到遇到 # 字符,然后报告读取的空格数目,读取的换行符数目以及读取的所有其他字符数目。

解:

#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch;
int blank = 0; // 空格
int line = 0; // 行
int chars = 0; // 其他字母

printf (" please input chars ( # to exit ) : \n");
while ((ch = getchar()) != ‘#‘)
{
if (ch == ‘ ‘)
blank++;
else if (ch == ‘\n‘)
line++;
else
chars++;
}
printf (" blank: %d, line : %d, chars : %d \n",blank,line,chars);
system("PAUSE");
return 0;
}

--------------------------------------------------------------------------------------

2. 编写一个程序,该程序读取输入直到遇到 # 字符。使程序打印每个输入的字符以及它的十进制 ASCII 码。每行打印 8 个字符/编码对。建议:利用字符计数和模运算符 (%)在每 8个循环周期打印一个换行符。

解:

#include <stdio.h>
#include <stdlib.h>
#define LINE 8
int main (void)
{
char ch;
int num = 0;

printf (" Please input words (# to quit ) : \n");

while (( ch = getchar()) != ‘#‘)
{
num++;
printf ("%c: %2d |",ch,ch);

if (num % LINE == 0)
printf ("\n");

}
system("PAUSE");
return 0;

}

---------------------------------------------------------------------------------------

3. 编写一个程序。该程序读取整数,直到输入 0 。输入终止后,程序应该报告输入的偶数(不包括 0 )总个数,偶数的平均值,输入的奇数总个数以及奇数的平均值。

解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int num = 0;
int odd_count = 0 ; // 奇数的个数
int odd_sum = 0; // 奇数的总值
int dual_count = 0 ; // 偶数的个数
int dual_sum = 0; // 偶数的总值
float odd_average = 0;
float dual_average = 0;

printf ("Please input numbers (0 to quit) : \n");
while((scanf("%d",&num)==1)&&(num!=0))
{
if (num % 2 == 0)
{
odd_count++;
odd_sum += num;
continue;
}
else
{
dual_count++;
dual_sum += num;
continue;
}
}

if (odd_count > 0)
{
odd_average = odd_sum / odd_count ;
printf (" odd count : %d , doo sum :%.2f \n",odd_count,odd_average);
}
if (dual_count >0)
{
dual_average = dual_sum / dual_count;
printf (" dual count: %d, dual sum ;%.2f \n",dual_count,dual_average);
}

printf (" The End\n");
system("PAUSE");
return 0;
}

-----------------------------------------------------------------------------------------

4. 利用 if else 语句编写程序读取输入,直到 #,用一个感叹号代替每个句号,将原有的每个感叹号用两个感叹号代替,最后报告进行了多少次替代。

解:

#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch;
int num = 0;

printf(" Please input chars (# to quit) : \n");
while ((ch = getchar()) != ‘#‘)
{
if ( ch == ‘.‘)
{
ch = ‘!‘;
num++;
}
else if (ch == ‘!‘)
{
putchar(‘!!‘);
num++;
}
putchar(ch);
}
printf ("一共替换了 %d 次\n",num);
system("PAUSE");
return 0;
}
-------------------------------------------------------------------------------------

5. 用 switch 重做练习 3

解:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int num = 0;
/* bool s_num; 使用另外一个语句要增加的布尔变量 */
int o_count, d_count ; // 奇数和偶数的计数
int o_sum, d_sum; // 奇数和偶数的总值
float o_average,d_average;

o_count = d_count = o_sum = d_sum = o_average = d_average = 0;

printf ("Please input number (0 to quit) : \n");
while ((scanf("%d",&num) == 1) && (num != 0))
{
switch((num % 2 == 1)||(num % 2 != 0))
/* switch (s_num = num %2) 如果此语句比较明了,但要增加多一个布尔变量 */
{
case 0:
d_count++;
d_sum += num;
break;
case 1:
o_count++;
o_sum +=num;
break;
}
}

if (d_count >0) {
d_average = d_sum / d_count;
printf ("dual : %d age; dou sum : %.2f \n" ,d_count,d_average);
}
if (o_count > 0) {
o_average = o_sum / o_count;
printf ("odd : %d gae; dou sum ;%.2f \n",o_count,o_average);
}
printf (" The End \n");
system("PAUSE");
return 0;
}

----------------------------------------------------------------------------------

6. 编写一个程序读取输入,直到 # ,并报告序列 ei 出现的次数。

PS: 此程序必须要记住前一个字符和当前字符。用诸如 Receive your eieio avard 的输入测试它

解:

#include <stdio.h>
#include <stdlib.h>
int main (void)
{
char ch,t_ch;
int count = 0;

printf (" Please input chars (# to quit ): \n");
while ((ch = getchar()) != ‘#‘)
{
if ((ch == ‘i‘) && (t_ch == ‘e‘) ) // 关键1
count++;
t_ch = ch; // 关键2
}
printf ("You have ‘ei‘ %d ‘s",count);
system("PAUSE");
return 0;
}

-----------------------------------------------------------------------------------------

7. 编写程序,要求输入一周中的工作小时数,然后打印工资总额,税金以及净工资。作如下假设;

a. 基本工资等级 10.00 美元 / 小时
b. 加班 (超过 40 小时) = 1.5倍时间
c. 前 300 美元为 15%
下一个 150 美元为 20%
余下的为 25% 212.5

用 #define 定义常量,不必关心本例是否符合当前的税法。

解;
#include <stdio.h>
#include <stdlib.h>
#define BASEPAY 10.00 /* 每小时工资 */
#define BASEHOUR 40 /* 一周工作的基本时间 */
#define OVERTIME 1.5 /* 加班计时倍数*/
#define BEFORE 300 /* 第一个税金金额 */
#define NEXT 150 /* 第二个税金金额 */
#define TAX_A 0.15 /* 第一税金比例 */
#define TAX_B 0.20 /* 第二税金比例 */
#define TAX_C 0.25 /* 第三税金比例 */
float time (float t_hour);
float fnutax (float f_sum);
int main (void)
{
float hour = 0;
float sum,tax,wage;
sum = tax = wage = 0;

printf (" Please input time : \n");
while ((scanf("%f",&hour)) == 1)
{
time(hour);
sum = time(hour) * BASEPAY; /* 这里工作时间要用函数的返回值,直接用 hour的话 得到的就是输入的时间而不会有加班费的计数 */
tax = fnutax(sum);
wage = sum - tax;
printf (" sum = $%.2f, tax = $%.2f, wage =%.2f \n",sum,tax,wage);
break;
}
system("PAUSE");
return 0;
}

float time (float t_hour) /* 工作小时计算函数 */
{
if (t_hour <=BASEHOUR)
t_hour = t_hour;
else
t_hour = (t_hour - BASEHOUR) * OVERTIME + BASEHOUR;
return t_hour; /* 函数的返回值 只需要在函数原型上定义,便可以返回 命名不论*/
}

float fnutax (float f_sum) /* 税金计算函数 */
{

if ( f_sum <= BEFORE)
f_sum = f_sum * TAX_A;
else if (f_sum <=( BEFORE +NEXT))
f_sum = (f_sum -BEFORE) * TAX_B + BEFORE * TAX_A ;
else
f_sum = BEFORE * TAX_A + NEXT * TAX_B + (f_sum -BEFORE - NEXT) * TAX_C;
return f_sum;
}

-------------------------------------------------------------------------------

8. 修改练习 7 中的假设 a,使程序提供一个选择工资等级的菜单。用 switch 选择工资等级。程序运行的开头应该像这样:

*************************************************************************
Enter the number corresponding to the desired pay rate or action
1) $ 8.75 /hr 2)$ 9.33 /hr
3) $ 10.00 /hr 4)$ 11.20 /hr
5) $ quit
*************************************************************************

如果选择 1 到 4 ,那么程序应该请求输入工作小时数。程序应该一直循环运行,直到输入 5 。如果输入 1 到 5 以外的选项,那么程序应该提醒用户合适的选项是哪些,然后再循环。用 #diefne 为各种工资等级和税率真定义常量。

解:

#include <stdio.h>
#include <string.h> 一
#include <stdlib.h>
#include <ctype.h>
#define ONE 8.75 // 第一类工资
#define TWO 9.33 // 第二类工资
#define THREE 10.00 // 第三类工资
#define FOUR 11.20 // 第四类工资
#define BASEHOUR 40 /* 一周工作的基本时间 */
#define OVERTIME 1.5 /* 加班计时倍数*/
#define BEFORE 300 /* 第一个税金金额 */
#define NEXT 150 /* 第二个税金金额 */
#define TAX_A 0.15 /* 第一税金比例 */
#define TAX_B 0.20 /* 第二税金比例 */
#define TAX_C 0.25 /* 第三税金比例 */
#define STR "Enter the number corresponding to the desired pay rate or action"

float time (float t_hour);
float fnutax (float f_sum);
void star (char ch, int num);
void temp (float num);
int main (void)
{
float hour = 0;
float sum,tax,wage;
char ch;
sum = tax = wage = 0;
float num;

begin: // goto 跳转
star (‘*‘ ,strlen(STR)); // 程序头
printf("%s \n",STR);
printf(" 1) $ 8.75 /hr 2)$ 9.33 /hr \n");
printf(" 3) $ 10.00 /hr 4)$ 11.20 /hr \n");
printf(" 5) $ quit \n");
star(‘*‘,strlen(STR));
while ((ch = getchar()) != ‘5‘)
{
if (isalnum(ch)) // 调用isalnum() 函数
switch(ch)
{
case ‘1‘:
num = ONE;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;

case ‘2‘:
num = TWO;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;

case ‘3‘:
num = THREE;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;

case ‘4‘:
num = FOUR;
printf (" Please input time : ");
temp(num);
printf ("\n");
goto begin;
break;

default:
printf ("你的输入有错误,请重新输入 \n");
goto begin;
break;
}
}
printf ("程序退出 \n");
system("PAUSE");
return 0;
}

float time (float t_hour) /* 工作小时计算函数 */
{
if (t_hour <=BASEHOUR)
t_hour = t_hour;
else
t_hour = (t_hour - BASEHOUR) * OVERTIME + BASEHOUR;
return t_hour; /* 函数的返回值 只需要在函数原型上定义,便可以返回 命名不论*/
}

float fnutax (float f_sum) /* 税金计算函数 */
{

if ( f_sum <= BEFORE)
f_sum = f_sum * TAX_A;
else if (f_sum <=( BEFORE +NEXT))
f_sum = (f_sum -BEFORE) * TAX_B + BEFORE * TAX_A ;
else
f_sum = BEFORE * TAX_A + NEXT * TAX_B + (f_sum -BEFORE - NEXT) * TAX_C;
return f_sum;
}

void star (char ch, int num) /* 原型使用 star(‘*‘,num) * = ch, num = num */
{
int temp;
for (temp = 0; temp < num; temp++) {
putchar(ch);
}
printf ("\n");
}

void temp (float num) // 暂时税金调用函数
{
float hour = 0;
float sum,tax,wage;
sum = tax = wage = 0;

scanf("%f",&hour);
time(hour);
sum = time(hour) * num;
tax = fnutax(sum);
wage = sum - tax;
printf (" sum = $%.2f, tax = $%.2f, wage =%.2f \n",sum,tax,wage);
}

------------------------------------------------------------------------------------------

9. 编写一个程序,接受一个整数的输入,然后显示所有小于或等于该数的素数

解:

#include<stdio.h>
int main(void)
{
int num,begin,end,temp;
bool isprime;
printf("Hey you,the foolish guy,input your number: ");
while(scanf("%d",&num)==1){
for(begin=2;begin<=num;begin++){
for(end=2,isprime=true;end<=begin/2;end++){
if (begin%end==0)
isprime=false;
}
if(isprime){
printf("%5d",begin);
}
}
printf("\n\nTry again(Enter q to quit): ");
}
printf("Done.");
return 0;
}

注:求质数还是算法和效率上的问题,由于对算法还不理解,暂抄了网上的答案。
以后再自己想。。

-------------------------------------------------------------------------------

10. 1988 年 United States Federal Tax Schedule 是近期最基本的。它分为 4 类,每类有两个等级。下面是其摘要;美元数为应征税的收入。

---------------------------------------------------------------
各类 税金
--------------------------------------------------------------
单身 前 17,850美元按 15%,超出部分按 28%
--------------------------------------------------------------
户主 前 23,900美元按 15%,超出部分按 28%
--------------------------------------------------------------
已婚,共有 前 29,750美元按 15%,超出部分按 28%
--------------------------------------------------------------
已婚,离异 前 14,875美元按 15%,超出部分按 28%
--------------------------------------------------------------
编写一个程序,让用户指定税金各类和应征税收入,然后计算税金。使用循环以便用户可以多次输入。

解:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define BASE_A 17850
#define BASE_B 23900
#define BASE_C 29750
#define BASE_D 14875
#define STR "请选择你的税金种类"

void star (char ch, int num);
float fnutax (float f_sum);
void temp (float num);

int main (void)
{
char ch;
float sum, tax;
sum = tax = 0;
float tax_a,tax_b;
tax_a = 0.15;
tax_b = 0.28; // switch 中 case 中好像不能用常量进行计算 这里只好用变量

begin:
star(‘-‘,strlen(STR)*3);
printf ("%s \n",STR);
printf (" A) 单身 : 前 17,850美元按 15%,超出部分按 28% \n");
printf (" B) 户主 : 前 23,900美元按 15%,超出部分按 28% \n");
printf (" C) 已婚,共有 : 前 29,750美元按 15%,超出部分按 28% \n");
printf (" D) 已婚,离异 : 前 14,875美元按 15%,超出部分按 28% \n");
printf (" E) 退出程序 \n");
star(‘-‘,strlen(STR)*3);
while ((ch = getchar()) != ‘E‘)
{
if (isalnum(ch))
switch(ch)
{
case ‘A‘:
printf (" 请输入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_A)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_A) * tax_b + BASE_A * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
goto begin;
break;

case ‘B‘:
printf (" 请输入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_B)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_B) * tax_b + BASE_B * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
goto begin;
break;

case ‘C‘ :
printf (" 请输入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_C)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_C) * tax_b + BASE_C * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
goto begin;
break;

case ‘D‘:
printf (" 请输入你的收入 :");
scanf("%f",&sum);
if (sum <= BASE_D)
{
tax = sum * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
else
{
tax = (sum - BASE_D) * tax_b + BASE_D * tax_a;
printf("\n 你的收入是 %.2f 应付税金为 %.2f \n ",sum,tax);
}
goto begin;
break;

default:
printf ("\n 你的输入有错误,请重新输入 \n");
goto begin;
break;
}
}
system("PAUSE");
return 0;
}

void star (char ch, int num)
{
int temp;
for (temp = 0; temp < num; temp++) {
putchar(ch);
}
printf ("\n");
}

-------------------------------------------------------------------------------------

11. ABC Mail Order Grocery 朝鲜蓟的售价是 1.25 美元/磅,甜菜的售价是 0.65美元/磅,胡萝卜的售价是 0.89美元/磅。在添加运输费用之前,他们为 100 美元的订单提供 5%的打优惠折。对 5 磅以下的定单收取 3.50美元的运输和装卸费用;超过 5磅而不足 20磅的定单收到 10.00美元的运输和装卸费用;20磅以上的运输在 8 美元的基础上每磅加收 0.1 美元。编写程序,在循环中使用 switch 语句。以便对输入 a 的响应是让用户输入所需的朝鲜蓟磅数,b 为甜菜磅数, c 为胡萝卜磅数,而 q 允许用户退出订购过程。然后程序计算总费用,折扣和运输费用(如果有运输费用的话),以及总数。随后程序应该显示所有购买信息:每磅的费用,订购的磅数,该订单每种蔬菜的费用,订单的总费用,折扣,如果有的话加上运输费用, 以及所有费用的总数。 279 265.5 12.8

解:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define THISTLE 1.25 //朝鲜蓟
#define BEET 0.65 //甜菜
#define CARROT 0.89 //胡萝卜
#define REBATE 0.05 // 5%的打折优惠 (100美元以上)
#define BASE 100.0 // 打折优惠的金额下限 100
#define HEFT_A 5.0 // 5 磅
#define HEFT_B 20.0 // 20 磅
#define HEFT_R_A 3.50 // 5 磅的运费
#define HEFT_R_B 10.00 // 5 - 20磅的运费
#define HEFT_R_C 8.0 // 20磅的运费
#define HEFT_R_C_A 0.1 // 20+ 每磅的运费
#define STR " 请选择你要购买的种类: a)朝鲜蓟 b)甜菜 c)胡萝卜 q) 结算&退出"

void star (char ch, int num);
int main (void)
{
char ch;

float thistle_c,beet_c,carrot_c; // 商品的计数
thistle_c = beet_c = carrot_c = 0;

float total_t,total_b,total_c; // 商品的累积计数
total_t = total_b = total_c = 0;

float sum_t,sum_b,sum_c; // 商品的总金额
sum_t = sum_b = sum_c = 0;

float sum_all,heft, all,rate,base;
sum_all = heft = all = rate = base = 0;

float heft_c,all_sum;
heft_c = all_sum = 0;

begin:
star (‘-‘,strlen(STR));
printf ("\n %s \n \n",STR);
star (‘-‘,strlen(STR));
while ((ch = getchar()) != ‘q‘)
{
if (isalnum(ch))
switch(ch)
{
case ‘a‘:
printf (" 请输入你要购买的数量 朝鲜蓟(磅) :");
scanf ("%f",&thistle_c);
total_t += thistle_c;
goto begin;
break;

case ‘b‘:
printf (" 请输入你要购买的数量 甜菜(磅) :");
scanf ("%f",&beet_c);
total_b += beet_c;
goto begin;
break;

case ‘c‘:
printf (" 请输入你要购买的数量 胡萝卜(磅) :");
scanf ("%f",&carrot_c);
total_c += carrot_c;
goto begin;
break;

default:
printf ("输入错误,请重新输入\n");
goto begin;
break;
}
}
if ((total_t > 0) || (total_b > 0) || (total_c > 0))
{
printf ("朝鲜蓟:$1.25/磅 甜菜:$0.65/磅 胡萝卜:$0.89/磅\n");
}
if (total_t != 0)
{
sum_t = THISTLE * total_t;
printf("你购买了朝鲜蓟 %.2f /磅,共 $%.2f(美元)\n",total_t,sum_t);
}
if (total_b != 0)
{
sum_b = BEET * total_b;
printf("你购买了甜菜 %.2f /磅,共 $%.2f(美元)\n",total_b,sum_b);
}
if (total_c != 0)
{
sum_c = CARROT * total_c;
printf("你购买了胡萝卜 %.2f /磅,共 $%.2f(美元)\n",total_c,sum_c);
}
if ((sum_t != 0) || (sum_b != 0) || (sum_c != 0))
{
sum_all = sum_t + sum_b + sum_c;
heft = total_t + total_b + total_c;

if (heft <= HEFT_A)
heft_c = HEFT_R_A;
if ((heft >HEFT_A) && ( heft <=HEFT_B))
heft_c = HEFT_R_B;
if (heft > HEFT_B)
heft_c = HEFT_R_C + (heft - HEFT_B) * HEFT_R_C_A;

if (sum_all >= BASE)
all = sum_all * REBATE;
printf ("%.2f",sum_all);

all_sum = sum_all - all +heft_c;

printf("你购买的磅数共为 %.2f 运费 $%.2f 折扣为 $%.2f 总费用为 $%.2f \n",
heft,heft_c,all,all_sum);

}
printf ("程序退出\n");
system("PAUSE");
return 0;
}

void star (char ch,int num)
{
int temp;
for (temp =0; temp <= num; temp++) {
putchar(ch);
}
printf ("\n");
}

注; 做得真他妈的累。。。 要考虑的东西太多了 编译器也不够智能。。
if 作为判断那句 一定小心不能用 分号, 有不 编译正常,运算的时候却有点差错。

时间: 2024-10-19 08:42:18

C Primer Plus(第五版)7的相关文章

C++ Primer【第五版】习题参考答案——第六章(函数)

本系列文章会不断更新,但是时间不能保证.另外基本上都是自己做的答案,仅供参考,如果有疑问欢迎交流. #include <iostream> #include <initializer_list> using namespace std; int test_Ex_6_27(std::initializer_list<int> li); int main() { cout << test_Ex_6_27({23,78,89,76,90}) << en

c++ primer(第五版)学习笔记及习题答案代码版(第十四章)重载运算与类型转换

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.h 和.cc 中,需要演示某一题直接修改 #define NUM****, 如运行14.30题为#define NUM1430: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful f

C++ Primer【第五版】习题参考答案——第五章(语句)

#include <iostream> #include <vector> #include <string> using namespace std; /******************************************************************* Ex_5_1: 空语句就是只含有一个分号的语句. 如果在程序的某个地方,语法上要求有一条语句,但是逻辑上不需要, 这时就需要一条空语句. Ex_5_2: 块就是由花括号包围的复合语句

c++ primer(第五版)学习笔记及习题答案代码版(第十一章)关联容器

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,包含Chapter7.h头文件,读入文件包括./test ./rules .需要演示某一题直接修改 #define NUM****, 如运行11.23题为#define NUM1123: chapter 11 1.  关联容器不支持顺序容器的位置相关的操作,例如push_front或push_back.原因是关联容器中元素是根据关键字存储的,这些操作对 关联容器没有意义.而且关联容器也不支持构造函数或插入操作这些接收一个元素值和

c++ primer(第五版)学习笔记及习题答案代码版(第六章)函数

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,编译需要包含Chapter6.h头文件. 需要演示某一题直接修改 #define NUM***, 如运行6.23题为#define NUM623: chapter 6 1. 形参初始化的机理与变量初始化一样. 当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用. 2. const和实参 void fcn(const int i){ /*fcn能够读取i,但是不能向i写值*/} void fcn(int i){ /*.

C++ Primer(第五版) 笔记 C01-02

C01 ++val; 优于 val++; 对数量不定的输入数据:while(cin>>value)... 遇到无效的输入或eof后,cin变为无效状态,条件变为假. 来自标准库的头文件用<>包围,不属于标准库的用""包围. 文件重定向工作:exename.exe <infile >outfile 点运算符:左侧运算对象是类类型的,右侧是该类型的成员. 参数 = 实参 = 值,形参指出调用函数可使用什么实参. 定义在函数内部的内置类型通常不初始化. C

《C++Primer》第五版习题详细答案--目录

作者:cosefy ps: 答案是个人学习过程的记录,仅作参考. <C++Primer>第五版习题答案目录 第一章:引用 第二章:变量和基本类型 第三章:字符串,向量和数组 第四章:表达式 原文地址:https://www.cnblogs.com/cosefy/p/12180771.html

C primer plus 第五版十二章习题

看完C prime plus(第五版)第十二章,随带完成了后面的习题. 1.不使用全局变量,重写程序清单12.4的程序. 先贴出12.4的程序,方便对照: 1 /* global.c --- 使用外部变量 */ 2 #include <stdio.h> 3 int units = 0; //一个外部变量 4 void critic(void); 5 int main(void) 6 { 7 extern int units; 8 9 printf ("How many pounds

C Primer Plus(第五版)12

第 12 章 存储类, 链接和内存管理 在本章中你将学习下列内容 . 关键字: auto, extern, static, register, const, volatile, restricted. . 函数: rand(), srand(), time(), malloc(), calloc(), free() . 在 C 中如何确定变量的作用域 ( 它在多大范围内可知) 以及变量的生存期 (它存在多长时间). . 设计更复杂的程序. C 的强大功能之一在于它允许你控制程序的细节. C 的内