第 6 章 C 控制语句 : 循环

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

已经多次学过,没怎么标注

· 关键字: for while do while

· 运算符: < > >= <= != == += *= -= /= &=

· 函数: fabs()

· C 的三种循环结构:while , for 和 do while

· 使用关系运算符构建控制循环的表达式

· 其他一些运算符

· 循环中常用的数组

· 编写具有返回值的函数

强壮,聪明,全能和有用, 我们多数人都喜欢人们这样描述自己。使用 C ,你至少可以有机会让人们这样描述你的程序。诀窍就在于对程序流进行控制。根据计算机科学(目前为止仍是关于计算机的科学,而不是计算机来研究的科学),一种好的语言应该能提供以下三种形式的程序流。

· 顺序执行语句序列(顺序)。

· 在满足某个条件之前反复执行一个语句序列 (循环)

· 通过进行一个判断在两个可选的语句序列之前选择执行 (分支)。

第一种形式你已经很熟悉了,前面的所有程序都是由这种语句序列组成的。while 循环是第二种形式的一个例子。本意将详细地介绍 while 循环以及另外两种循环结构: for 循环和 do while 循环。最后一种形式在几种可能不同的执行路线之间进行选择,它使程序更加“智能”并极大地增加了计算机的用途。这部分内容将在下一章介绍。本章也介绍了数组,因为它使你可以运用有关循环的新知识,本章还继续介绍有关函数的知识。我们首先从学习 while 循环开始。

6.1 再探 while 循环

你已经多少有点熟悉 while 循环了。让我们用一个程序来回顾一下,这个程序对从键盘输入的整数进行求和(请参见程序清单 6.1)。这个例子使用了 scanf()的返回值来结束输入。

程序清单 6.1 summing.c 程序
--------------------------------------------------------------------
/* summing.c -- 对用户输入的整数求和 */
#include <stdio.h>
int main (void)
{
long num;
long sum = 0L; /* 把 sum 初始化为零 */
int status;

printf ("Please enter an integer to be summed .");
printf (" q to quit");
status = scanf("%ld",&num);
while (status == 1) /* == 的意思是 等于 */
{
sum = sum + num;
printf ("Please tnter next integer (q to quit):");
status = scanf ("%ld",&num);
}
printf ("those integers sum to %ld .\n",sum);
getchar();
getchar();
return 0;
}

程序清单 6.1 使用类型 long 来允许较大的数。尽管 C 的自动转换允许你简单地使用 0,但程序为了保持一致性,把 sum 初始化为 0L (类型为 long 的零)而不是 0 (类型为 int 的零)。

下面是一个运行示例:
Please enter an integer to be summed . q to quit: 44
Please tnter next integer (q to quit):33
Please tnter next integer (q to quit):88
Please tnter next integer (q to quit):q
those integers sum to 165 .

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

6.1.1 程序注解

我们首先看一下 while 循环。这个循环的判断条件是以下表达式:

status == 1

== 运算符是 C 的相等运算符(equality operator),也就是说,这个表达式判断 status 是否等于 1,不要把它与 status = 1 相混淆,后者把值 1 赋给 status 。 使用 status == 1 作为判断条件,那么只要 status 等于 1,循环就会重复执行。在每次循环中,循环体把 num 的当前值加到 sum 上,这样 sum 就始终保持为总和。当 status 的值不为 1 时循环终止,然后程序报告 sum 的最终结果。

要使程序正确运行,在每次循环中应该为 num 获取一个新值,并且重置 status。程序使用 scanf()的两个不同功能来做到这一点。首先,使用 scanf()来尝试为 num 读入新值。然后,使用 scanf()的返回值来报告执行是否成功。回收一下第 4章“字符串和格式化输入/输出”,scanf()返回成功读入的项目的个数。如果 scanf()成功读入一个整数,就把这个整数放在 num 中并返回值 1,随后值 1 被赋给 status (请注意输入值赋给 num,而不是status。)这样就更新了 num 和 status 的值,while 循环也经过了另一个周期。如果你输入的不是数字,例如输入 q,那么 scanf()就不能读入一个整数,所以它的返回值 和 status 都为 0 。这将使循环终止。因为输入的字符 q 不是数字,所以它又被放回输入队列中,不能被读取(实际上,不仅仅是 q ,任何非数字的输入都将使循环终止,但是用户输入 q 比提示输入一个非数字字符要简单一些)。

如果 scanf()尝试转换一个数值前遇到了问题(例如,检测到文件的尾部或者遇到一个硬件问题),它就会返回一个特殊值 EOF,这个值一般被定义为 -1 。这个值同样也会导致循环终止。

scanf()的双重用法避免了在循环中进行交互输入时的一个辣手的问题:你如何告诉循环什么时候停止?例如,假定 scanf()没有返回值,那么在每次循环中唯一改变的就是 num 的值,你可以使用 num 的值来终止循环,比如使用 num > 0或 num != 0 来作为判断条件,但是这使你不能输入特定的值,例如 -3 或 0 。你也可以在循环中添加新的代码,例如在每次循环中询问‘Do you wish to continue?<y/n>’,然后进行判断用户是否输入了 y。这有些笨拙,而且也减慢了输入。使用 scanf()的返回值避免了这些问题。

现在我们更仔细地看一下程序结构。可以进行总结如下:

initialize sum to 0 /* 初始化 变量 sum 为 0 */
prompt user /* 提示用户 */
read input /* 读取 输入 */
while the input is an integer, /* 如果输入为整数 */
add the input to sum, /* 将输入的数 与 变量 sum 相加 */
prompt user /* 提示用户 */
then read next input /* 接着读取下一个输入 */
after input completes,print sum /* 在输入全部完成之后,显示 sum 的值 */

顺便说一下,这是个伪代码(pseudocode)的例子,伪代码是一种用简单的英语来表示程序的方法,它与计算机语言的形式相对应。伪代码有助于设计程序的逻辑。在认为逻辑正确之后,就可以把伪代码翻译成实际的编程代码。伪代码的一个好处是它可以使你专注于程序的逻辑与组织,使你不必同时担心如何用计算机语言来表达你的想法。例如,你可以用缩排来代表一块代码而不用担心要求花括号的 C 语言语法。另一个好处是伪代码不与某一个特定的语言联系,这样同一伪代码可以被翻译为多种计算机语言。

总之,因为 while 循环是一个入口条件循环,所以程序必须在进入循环体之前获取输入并检查 status 的值。这就是程序在 while 之前有一个 scanf()调用的原因。要使循环继续执行,在循环中需要一个读语句,这样程序才可以得出下一个输入的状态。这就是程序在 while 循环的结尾处还有一个 scanf()的原因,它为下一次循环做准备。可以把如下用法作为循环的标准格式:

get first value to be tested /* 检测获取的(输入)的第一个值 */
while the test is successful /* 如果检测通过 */
process value /* 过程 值 */
get next value /* 获取下一个值 */

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

6.1.2 C 风格的读循环

按照伪代码中显示的设计方法,程序清单 6.1 也可以用 Pascal,BASIC 或 FORTRAN 书写。然而 C 提供了更快捷的形式。下面的结构:

status = scanf("%ld",&num);
while (status == 1)
{
/* loop actions */
status = scanf("%ld",&num);
}

可以用下列形式代替

while (scanf("%ld",&num) == 1)
{
/* loop actiong */
}

第二种形式同时使用了 scanf()的两种不同用法。首先,如果调用成功,函数会把一个值放在 num 中,第二,函数的返回值 (1 或 0,而不是 num 的值)用来控制循环。因为在每次重复过程中都对循环条件进行判断,所以在每次循环中都调用 scanf()来提供新的 num值和新的判断。换句话说, C 的语法特性使你可以用以下的精简版本来代替标准的循环格式:

while getting and testing the value succeeds /* 如果获取和检测成功的值 */
process the value /* 将这个值放进处理进程 */

现在我们更为正式地看一下 while 语句。

6.2 while 语句

以下为 while 循环的一般形式:

while (expression)
statement

statement 部分可以是一个带有分号的简单语句,也可以是花括号的一个复合语句。

迄今为止的例子使用关系表达式作为循环的 expression 部分,也就是说,例子中的 expression 是一个值的对比关系。更一般地,你可以使用任何表达式。如果 expression 为真(或者更一般地说,非零),那么就执行一次 statement 部分,然后再次判断 expression 。 在 expression 变为 假(零)之前要重复这个判断和执行的循环。每次循环都被称为一次迭代 (请参见图 6.1)

-------------------------------------------------------
图 6.1 while 循环的结构:

|
假 | while
下一个语句 <---- count++ < limit <---------
| |
| 真 |
printf ("tra la la la ! \n");---

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

6.2.1 终止 while 循环

这是对 while 循环至关重要的一点:当你构造一个 while 循环时,循环中必须包含能改变判断表达式的值来使表达式工的值最终变为假。否则循环永远不会终止(实际上,你也可以使用 break 和 if 语句来终止循环,这将在后续的章节中介绍)。考虑以下的例子:

index = 1;
while (index < 5)
printf("Good morning !\n");

上面的代码段无限期地打印这个令人愉快的消息。为什么?因为在循环中不能改变 index 的值,这样它就一直为 1,现在看一下这个:

index = 1;
while (--index < 5)
printf("Good morning !\n");

后面的代码段也好不到那里去,它改变了index 的值,但是却朝着错误的方向。至少这个版本最后还可以终止,那要等 index 减小到比系统可以处理的最小的负数还小并变成最大的可能的正数的时候了(第 3章“数据 和 C”中的 toobig.c 程序说明了最大的正数一般如何加 1 就变成一个负数,同样,最小的负数如何减 1 就能产生一个正数)。

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

6.2.2 循环何时终止

要知道只有在计算判断条件的值时才决定是终止循环还是继续进行,这一点很重要。例如,考虑程序清单 6.2 中的程序。

程序清单 6.2 when.c 程序
-------------------------------------------------------------
/* when.c --- 何时退出一个循环 */
#include <stdio.h>
int main (void)
{
int n = 5;
while (n < 7) // 第 7行
{
printf (" n = %d \n",n);
n++; // 第 10行
printf ("Now n = %d\n",n); // 第 11行
}
printf ("The loop has finished .\n");
return 0;
}

运行程序清单 6.2 会产生下列输出:
n = 5
Now n = 6
n = 6
Now n = 7
The loop has finished .

在第二次循环中,变量 n 在第 10行首次获得值 7.然而程序此时并不退出。相反,它结束本循环(第11行),并在对第 7行的判断条件第三次求值时才退出循环(变量 n 在第一次判断时为 5,第二次判断时为 6)。

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

6.2.3 while : 入口条件循环

while 循环是使用入口条件的有条件循环。它被称为有条件是因为语句部分的执行要依赖于判断表达式中的条件,例如 (index <5)。这个表达式是一个入口条件是因为在进入循环体之前必须满足这个条件。在下面的情况中,程序永远不会进入循环体,因为条件一开始就为假:

index = 10;
while (index++ < 5)
printf ("Have a fair day or better .\n");

把第一行改为:

index = 3;

就可以执行这个循环了。

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

6.2.4 语法要点

在使用 while 时要谨记的一点是,只有位于判断条件之后的单个语句(简单语句或复合语句)才是循环的部分。缩进是为了帮助读者而不是计算机。程序清单 6.3 说明了如果你忘记这一点会发生什么。

程序清单 6.3 while1.c 程序
------------------------------------------------------------------
/* while1.c --- 注意花括号的使用 */
/* 拙劣的代码产生了一个无限循环 */
#include <stdio.h>
int main (void)
{
int n = 0;
while (n < 3)
printf ("n is %d \n",n);
n++;
printf ("That's all this program does \n");
return 0;
}

程序清单 6.3 产生下列输出:
n is 0
n is 0
n is 0
n is 0

(......等等,直到你强行关闭这个程序为止)。

尽管这个例子缩进了 n++;语句,但是并没有把它和前面的语句放在一个花括号中。这样就只有紧跟在判断条件之后的打印语句构成了循环部分。变量 n 永远不会得到更新,条件 n < 3 一直保持为真,在你强行关闭这个程序之前它将不断地打印 n is 0 。这是一个无限循环的例子,没有外部干涉它就不会退出。

要记住 while 语句本身在语法上算做一个单独的语句,即使它使用了复合语句。该语句从 while 开始,到第一个分号结束,在使用了复合语句的情况下,到终结花括号结束。

使用分号时也要小心。例如,考虑程序清单 6.4 中的程序。

程序清单 6.4 while2.c 程序
-------------------------------------------------------------
/* while2.c --- 注意分号的使用 */
#include <stdio.h>
int main (void)
{
int n = 0;

while (n++ < 3); /* 第 7 行 */
printf ("n is %d \n",n); /* 第 8 行 */
printf ("That's all this program does. \n");
return 0;
}

程序清单 6.4 产生下列输出:

n is 4
That's all this program does

像我们前面所说的那样,循环在判断条件之后的第一个简单或复合语句处就结束了。在第 7行的判断条件之后马上就有一个分号,循环将在此处终止,因为一个单独的分号也算做一个语句。第 8 行的打印语句就不是循环的一部分,所以 n 在每次循环都增加 1, 而只在退出循环之后才进行打印。

在这个例子中,判断条件后紧跟一个空语句 (null statement),它什么都不做。在 C 中,单独的分号代表空语句。有时候,程序员有意地使用带有空语句的 while 语句,因为所有的工作都在判断过程中进行。例如,假定你想要跳过输入直到第一个不为空格或数字的字符,你可以使用这样的循环:

while (scanf("%d",&num)== 1)
; /* 跳过整数输入 */

只要 scanf()读入一个整数,它就返回 1,循环就会继续。请注意,为了清楚起见,应该把分号(空语句)放在下面的一行而不是在同一行中。这使得在阅读程序时更容易看到空语句,也可以提醒你空语句是有意放在那里的。更好的方法是使用下一章中要讨论的 conthlnue 语句 。

6.3 比较大小: 使用关系运算符和表达式

因为 while 循环经常要依赖于进行比较的判断表达式,所以比较表达式值得我们进一步研究。这样的表达式称为关系表达式(relationatl expression),其中出现运算符称为关系运算符(relational operator)。你已经使用过了一些,表 6.1 列出了 C 中的关系运算符的完整列表。这个表覆盖了数值关系的所有可能性。

表 6.1 关系运算符
--------------------------------------------------------------
运算符 含义
--------------------------------------------------------------
< 小于
--------------------------------------------------------------
<= 小于或等于
--------------------------------------------------------------
== 等于
--------------------------------------------------------------
>= 大于或等于
--------------------------------------------------------------
!= 不等于
--------------------------------------------------------------

关系运算符用来构成在 while 语句和我们将要讨论到的其他 C 语句中使用的关系表达式。这些语句检查表达式为真还是为假。下面是包含了关系表达式实例的三个不相关的语句。它们的意思是显而易见的。

while (number < 6)
{
printf ("Your number is too small \n");
scanf ("%d",&number);
}

while (ch != '$');
{
count++;
scanf ("%c",&ch);
}

while (scanf ("%f",&num) == 1)
sum = sum + num;

在第二个例子中,请注意关系表达式也可以用于字符的比较。进行比较时使用的是机器的字符代码(我们假定为 ASCII)。然而,不能使用关系运算符来比较字符串。第 11 章“字符串和字符串函数”将介绍如何对字符串进行比较。

关系运算符也可以用于浮点数。但要小心,在浮点数比较中只能使用 < 和 >。原因在于舍入误差
可能造成两个逻辑上应该相等的数不相等。例如,3 和 1/3 的乘积应该是 1.0 。但是如果你用 6位小数来表示 1/3,乘积就是0.999999 而不等于 1 。使用在 math.h 头文件中声明的 fabs()函数可以方便地进行浮点数判断。 这个函数返回一个浮点值的绝对值(即没有代数符号的值)。例如,你可以使用类似程序清单 6.5 的方法来判断一个数是否接近一个想要的结果。

程序清单 6.5 cmpflt.c 程序
---------------------------------------------------------
/* cmpflt.c -- 浮点数比较 */
#include <stdio.h>
#include <math.h>
int main (void)
{
const double ANSWER = 3.14159;
double response;
printf ("What is the value of pi?\n");
scanf ("%lf",&response);
while (fabs(response - ANSWER) > 0.0001)
{
printf ("Try again! \n");
scanf ("%lf",&response);
}
printf ("Close enough \n");
getchar();
getchar();
return 0;
}

在用户的答案与正确值的误差小于 0.0001 之前,这个循环反复地请求输入答案:
What is the value of pi?
3.14
Try again!
3.141
Try again!
3.1415
Close enough

每个关系表达式都被判定为真或假 (永远也没也许)!这引起了一个有趣的问题。

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

6.3.1 什么是真

你可以回答这个古老的问题,至少对于 C 是如此。回忆一下, C 的表达式通常具有一个值。像程序清单 6.6 显示的那样,即使对关系表达式也是如此。在这个程序中,你打印了两个关系表达式的值。一个为真,一个为假。

程序清单 6.6 t_and_t.c 程序
------------------------------------------------------
/* t_and_t.c -- C 中的真和假 */
#include <stdio.h>
int main (void)
{
int true_val,false_val;

true_val = (10 > 2); /* 一个真表达式的值 */
false_val = (10 == 2); /* 一个假表达式的值 */
printf ("true = %d; false = %d \n",true_val,false_val);
getchar();
return 0;
}

程序清单 6.6 把两个关系式的值赋给两个变量。直接地说,它把一个真表达式的值赋为 true_val,把一个假表达式的值赋为 false_val。运行这个程序会产生下列简单的输出:

true = 1; false = 0

原来,对 C 来说,一个真表达式的值为 1,而一个假表达式的值为 0 。确实,有些 C 程序使用以下的循环结构,这意味着它会永远运行,因为 1 永远为真:

while (1)
{
......
}

6.3.2 还有什么是真

既然可以使用 1 或 0 来作为 while 语句的判断表达式,那么还可以使用其他数字唉?如果可以,会发生什么?我们试试程序清单 6.7 中的程序来做个实验。

程序清单 6.7 truth.c 程序
-----------------------------------------------------
/* truth.c -- 哪些值为真? */
#include <stdio.h>
int main (void)
{
int n = 3;

while (n)
printf ("%2d is true \n",n--);
printf ("%2d is false \n",n);
n = -3;
while (n)
printf ("%2d is true \n",n++);
printf ("%2d is false \n",n);
getchar();
return 0;
}

下面是结果:
3 is true
2 is true
1 is true
0 is false
-3 is true
-2 is true
-1 is true
0 is false

第一个循环在 n 为 3,2 和 1 时得到执行,而在 n 为 0 时结束。类似地,第二个循环在 n 为 -3,-2 和 -1时得到执行,而在 n 为 0 时结束。更一般地,所有的非零值都被认为是真,只有 0 被认为是假,C 对真的范围放得非常宽!

 可以说只要 while 循环的判断条件的值非零,它就可以执行循环。这使得判断条件是建立在数值的基础上而不是在真/假的基础上。要谨记如果关系表达式为真,它的值就为1,如果为假,它的值就为 0 。因为这样的表达式实际上是数值的。

很多 C 程序员对判断条件的这一属性加以利用。 例如,while(goats != 0)语句可以被 while(goats)代替,因为表达式(goats != 0) 和 (goats)都只有在 goats 的值为 0 时才为 0 或假。
第一种形式可能对那些刚学这种语言的人来说更清楚一些,但是第二种形式是 C 程序员最常用的。你应该努力去熟悉 while(goats) 这样的形式,使它对你来说看上去是自然的。

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

6.3.3 真值的问题

C 对真值的范围放得很宽,这可能引起一些问题。例如,我们对程序清单 6.1 的程序做一些细微的更改,就产生了程序清单 6.8 中的程序。

程序清单 6.8 trouble.c 程序
------------------------------------------------------------------
/* trouble.c -- 误用 = 将导致无限的循环 */
#include <stdio.h>
int main (void)
{
long num;
long sum = 0L;
int status;

printf (" Please enter an integer to be summed ");
printf (" (q to quit)");
status = scanf("%ld",&num);
while (status = 1)
{
sum = num + sum;
printf ("Please enter next integer (q to quit): ");
status = scanf("%ld",&num);
}
printf ("Those integers sum to %ld .\n",sum);
getchar();
return 0;
}

程序清单 6.8 产生了以下的这样的输出;

按非数值之后,将无限循环出错(.....等等,直到你强行关闭这个程序。)

这个麻烦的例子改变了 while 的判断条件,用 status = 1 代替了 status == 1. 前一个表达式是一个赋值语句,它把 status 赋值为 1. 而赋值表达式的值就是其左侧的值,这样 status = 1 的值也为 1 。 因此,实际上这个 while 循环就等于是使用了 while(1),也就是说循环永远不会退出。输入 q, status 被设置为 0 ,但是循环在判断时又把 status 重置为 1 并开始另一次循环。

你可能感到迷惑,因为程序将保持循环,并且用户在输入 q 之后根本没有机会进行更多的输入。当 scanf()末能读取指定形式的输入时,它就留下这个不相容的输入,以供下次进行读取。当 scanf()试把 q 作为整数读取并失败时,它就把 q 留在那里。在下次循环中读取前面留下来的 q 时, scanf()再次失败。所以这个例子不但建立了一个无限循环的例子,它也建立了一个无限失败的循环,这是一个可怕的概念。幸运的计算机目前是尚未具有感情。对计算机来说,无限地执行愚蠢的指令与成功地预测未来 10 年的股票市场没有什么区别。

不要在应该使用 == 的地方使用 = 。的确,有些计算机语言(例如 BASIC)为赋值运算符和关系运算符使用相同的符号,但这两个运算符有很大的差别(请参见图 6.2)。赋值运算符把一个值赋给左边的变量,而关系运算符检查左边与右边的值是否相等,它并不改变左边变量的值(如果左边是一个变量)。

图 6.2
--------------------------------------------------------
canoes == 5 /* == 检查camoes 的值是否是 5 */
cnaoes = 5 /* = 把值 5 赋给 cnaoes */
-------------------------------------------------------

要确保使用正确的运算符。编译器允许你使用错误的形式,产生你不希望的结果(但是太多的人错误地使用 =,以致于今天的大多数编译器都会产生一个警告以提示可能你的意思不是要做这个)。如果进行比较的双方中有一个常量,则可以把它放在表达式的左边,这样做有助于发现错误;

---------------------------------------------------------------------------
5 = canoes 语法错误
5 == canoes 检查 canoes 的值是否为 5
---------------------------------------------------------------------------

" 以后使用 == 时 最好把常量放在表达式的左边,这样做有助于发现错误 如上图 "

关键之处在于为常量赋值是非法的,所以编译器可以把赋值运算符的这种用法识别为语法错误。很多程序员在构建相等判断表达式时都习惯把常量放在前面。

总之,关系运算符被用来构成关系表达式。关系表达式在为真时值为 1, 为假时值为 0 。通常使用关系表达式作为判断条件的语句(例如 while 和 if)可以使用任何表达式作为判断,非零值被认为是“真”,而零值被认为是 “假”。

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

6.3.4 新的 _Bool 类型

在 C 中,表示真/假的变量一直是由 int 类型来表示的。 C99 专门为这种类型的变量添加了_Bool类型。这种类型是以英国数学家 George Boole 的名字来命名的,他开发了用代数表示并解决逻辑问题的系统。在编程领域,表示真或假的变量开始时被称为布尔变量(Boolean variable )。这样 _Bool就是布尔变量的 C 类型名。一个 _Bool 变量只可以具有值 1 (真)或 0 (假)。如果你把一个 _Bool 变量赋为一个非零的数值,变量就被设置为 1. 这说明 C 把任何非零的值都认为是真。

程序清单 6.9 纠正了 程序清单 6.8 中的判断条件,并用 _Bool 变量 input_is_good 来代替 int 变量 status。通常习惯为布尔变量取一个表明真或假值的名字。

程序清单 6.9 boolean.c 程序
-------------------------------------------------------------------
/* boolean.c -- 使用 _Bool 变量 */
#include <stdio.h>
int main (void)
{
long num;
long sum = 0L;
_Bool input_is_good;

printf ("Please enter an integer to be summed .");
printf (" ( q to quit):");
input_is_good = (scanf("%ld",&num) == 1);
while (input_is_good)
{
sum = sum + num;
printf ("Please enter next integer(q to quit) :");
input_is_good = (scanf("%ld",&num) == 1);
}
printf ("Those integers sum to %ld .\n",sum);
getchar();
getchar();
getchar();
return 0;
}

注 BCB 2010 不支持直接声明 _Bool typedef int _Bool; 要先定义一个 _Bool 类型
vc 2005 也一样 不过声明时 bool 便可以了 。 像 BCB 2010 先定义一个 类型也可以

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

注意代码是如何把比较的结果赋值给变量的:

input_is_good = ( scanf ("%ld",&num) == 1 );

这是有意义的,因为 == 运算符的返回值为 1 或 0 。顺便说一句,把 == 表达式括起来的圆括号不是必需的,例如 “input_is_good = (scanf ("%ld",&num) == 1 ;”因为 == 运算符的优先级要比 = 要高,但是它们可以使代码更容易阅读。同时也要注意变量名称的选择使 while 循环判断更容易理解了:

while (input_is_good)

C99 还提供了一个 stdbool.h 头文件。包含这个头文件可以使用 bool 来代替 _Bool,并把 true 和 false 定义成值为 1 和 0 的符号常量。在程序中包含这个头文件可以写出与 C++ 兼容的代码,因为 C++ 把 bool,true 和 false 定义为关键字。

6.3.5 关系运算符的优先级

关系运算符的优先级要低于包括 + 和 - 在内的算术运算符,但是要高于赋值运算符。这意味

x > y + 2 也同时等于 x > (y + 2)

x = y > 2 也同时等于 x = (y > 2)

换句话说,如果 y 大于 2, x 为 1; 否则 x 为 0。 就是并不没有把 y 的值 赋给 x 。

关系运算符比赋值运算符的优先级要高,所以

x_bigger = x > y; 也同时等于 x_bigger = (x > y)

关系运算符本身也分成两组不同的优先级
-------------------------------------------------------------------------
高优先级的组: < <= > >=
-------------------------------------------------------------------------
低优先级的组: == !=
--------------------------------------------------------------------------

像大多数其他的运算符一样,关系运算符从左到右进行结合。这样:

ex != wye == zee 就等于 (ex != eye == zee)

C 首先检查 ex 与 wye 的值是否不相等,然后结果值 1 或 0 (真或假)再与 zee 的值进行比较。我们不希望你使用这种结构,但是有必要对其进行说明。

PS : 总结: while 语句

关键字: while

总体注解: while语句创建了一个在判断表达式变为假 (或零)之前重复执行的循环。while 语句是一个入口条件循环,也就是说,是否执行循环的决定是在进入循环之前就做出的。因此,循环有可能永远不被执行。该形式的 statement 部分可以是一个简单语句或一个复合语句。

形式: while (expression)
statement

在 expression 变为假 (或 零)之前重复执行 statement 部分。

例如:

while (n++ < 100)
printf ("%d,%d \n",n,2*n,n+1); /* 单个语句 */

while (fargo < 100 ) /* 复合语句 */
{
fargo = fargo + step;
step = 2 * step;
}

PS : 总结: 关系运算符和表达式

关系运算符:

每个关系运算符都把它左边的值与它右边的值进行比较。

关系表达式:
一个简单的关系表达式由一个关系运算符及其两侧的操作数组成。如果关系为真,关系表达式的值为 1,如果为假,关系表达式的值为 0 。

例如:

5 > 2 为真, 则该关系表达式的值为 1 。

(2 + a)== a 为假,则该关系表达式的值为 0 。

6.4 不确定循环与计数循环

有些 while 循环的例子是不确定 (indefinite)循环。也就是说,在表达式变为假之前你不能预先知道循环要执行多少次,例如,程序清单 6.1 使用一个交互式的循环来计算整数的和,事先你并不知道会输入多少个整数。其他的例子是计数 (counting)循环,它们循环执行预先确定的次数。程序清单 6.10 是 while 计数循环的一个简短的例子。

程序清单 6.10 sweetie1.c 程序
----------------------------------------------------
/* sweetie1.c -- 一个计数循环 */
#include <stdio.h>
int main (void)
{
const int NUMBER = 22;
int count = 1; /* 初始化 */

while (count <= NUMBER) /* 判断 */
{
printf ("Be my Valentine \n"); /* 动作 */
count++; /* 更新计数 */
}
getchar();
return 0;
}

尽管程序清单 6.10 中使用的形式可以很好地工作,但它并不是这种情况下最好的选择,因为定义循环的动作没有被组织在一起。我们来详细说明这一点。

在建立一个重复执行固定次数的循环时涉及到三个动作;

1. 必须初始化一个计数器。

2. 计数器与某个有限的值进行比较。

3. 每次执行循环,计数器的值都要递增。

while 循环条件执行比较的动作,增量运算符执行递增的动作。在程序清单 6.10 中,递增在循环的结尾处执行。这种选择使得有可能不小心漏掉递增的动作。所以更好的方法是使用 counf++ <== NUMBER 来把判断与更新动作结合在一个表达式中,但使用这种方法时计数器的初始化仍然是在循环之外进行的,这样就有可能忘记初始化。实践告诉了我们有可能发生的事情最后总是会发生的,所以我们来看一种可以避免这些问题的控制语句。

6.5 for 循环

for 循环把所有这三种动作(初始化,测试,更新)都放在一起。通过使用 for 循环,你可以用程序清单 6.11 中的程序来代替前一个程序。

程序清单 6.11 sweetie2.c 程序
----------------------------------------------------------------
// sewwtie2.c --- 一个使用 for 的计数循环
#include <stdio.h>
int main (void)
{
const int NUMBER = 22;
int count;

for (count = 1; count <= NUMBER ; count++)
printf (" Be my Valentine !\n");
getchar();
return 0;
}

在关键字 for 之后的圆括号中包含了由两个分号分开的三个表达式。第一个表达式进行初始化,它在 for 循环开始的时候执行一次。第二个表达式是判断条件,在每次循环之前都要对它进行求值。当表达式为假 (count 大于 NUMBER)时,循环就结束了。第三个表达式进行改变或称为更新,它在每次循环结束时进行计算。程序清单 6.11 使用它来递增 count 的值,但是并没有限制一定要这样使用它。这之后的一个简单或复合语句结束了 for 语句。三个控制表达式中的每一个都是完整的表达式,所以任意一个控制表达式的任何副作用(例如把一个变量的值递增)都在程序求下一个表达式的值之前生效。

简单来说 for 就是里面有三个控制语句,用二个分号来隔开,第一个是初始化,第二个判断,第三个是递增。

我们来看另外一个例子,程序清单 6.12 在一个打印立方表的程序中使用了 for 循环。

程序清单 6.12 for_cube.c 程序
------------------------------------------------------------------
/* for_cube.c -- 使用一个 for 循环产生一个立方表 */
#include <stdio.h>
int main (void)
{
int num;
printf (" n n cubed \n");
for (num = 1; num <= 6; num++)
printf ("%5d %5d \n",num,num*num*num);
getchar();
return 0;
}

程序清单 6.12 打印了从 1 到 6 整数以级它们的立方

n n cubed
1 1
2 8
3 27
4 64
5 125
6 216

for 循环的第一行告诉我们关于循环参数的所有信息: num 的初始值,num 的最终值以及 num 在每次循环的增量。

利用 for 的灵活性

尽管 for 循环看上去类似于 FORTRAN 的 DO 循环, Pascal 的 FOR 循环以及 BASIC 的 FOR...NEXT 循环,但实际上 for 循环比它们任何一种都要灵活得多。这种灵活性来自于在一个 for 语句中如何使用三个表达式。迄今为止的例子使用第一个表达式来初始化计数器,第二个表达式对计数器的,第三个表达式来把计数器的值加 1 。当使用这种方法时, C 的 for 语句与我们提到的其他语句非常相似。但是还有更多其他的可能性,下面是其中的 9 种:

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

1. 你可以使用减量运算符来减小计数器而不是增加它

/* for_down.c */
#include <stdio.h>
int main (void)
{
int secs;

for (secs = 5; secs > 0; secs--)
printf ("%d seconds!\n",secs);
printf ("We have ignition\n");
getchar();
return 0;
}

下面是它的输出:
5 seconds!
4 seconds!
3 seconds!
2 seconds!
1 seconds!
We have ignition

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

2. 如果需要,你可以让计数器依次加 2,加10 等等:

/* for_13s.c */
#include <stdio.h>
int main (void)
{
int n;
for (n = 2; n < 60; n = n + 13) /* 以 13 计数 */
printf ("%d \n",n);
getchar();
return 0;
}

这个例子在每次循环中把 n 增加 13,打印输出如下:
2
15
28
41
54

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

3. 你也可以用字符代替数字来进行计数

/* for_char.c */
#include <stdio.h>
int main (void)
{
char ch;

for (ch = 'a'; ch <= 'z'; ch++)
printf (" The ASCII value for %c is %d \n",ch,ch);
getchar();
return 0;
}

部分输出如下:
The ASCII value for a is 97
The ASCII value for b is 98
The ASCII value for c is 99
The ASCII value for d is 100
........
The ASCII value for w is 119
The ASCII value for x is 120
The ASCII value for y is 121
The ASCII value for z is 122

这段程序可以工作,因为字符是以整数的形式进行存储的,所以这个循环实际上仍然是用整数来计数的。

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

4. 你可以判断迭代次数之外的条件。在 for_cube 程序中,你可以将:

for (num = 1; num <= 6; num++)

用 for (num = 1; num*num*num <= 216; num++) 来代替

如果与限制循环次数相比,你更关心限制立方的大小,就可以使用这种判断条件。

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

5. 你也可以让数量几何增加而不是算术增加,也就是说,不是每次加一个固定的数,而是乘上一个固定数:

/* for_geo.c */
#include <stdio.h>
int main (void)
{
double debt;

for (debt = 100.0; debt < 150.0; debt = debt * 1.1)
printf ("your debt is now $%.2f \n",debt);
getchar();
return 0;
}

这段程序在每次循环中把 debt 的值乘以 1.1 即每次把它增加 10% 。输出看上去是这个样子:

your debt is now $100.00
your debt is now $110.00
your debt is now $121.00
your debt is now $133.10
your debt is now $146.41

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

6. 在第三个表达式中,你可以使用所需的任何合法表达式。无论你使用的是什么,在每次循环中都会得到更新。

/* for_ wild.c */
#include <stdio.h>
int main (void)
{
int x;
int y = 55;

for (x = 1; y <= 75; y = (++x * 5) + 50)
printf (" %10d %10d \n",x,y);
getchar();
return 0;
}

这个循环打印出 x 与代数表达式 ++x *5 +50 的值。输出看上去是这个样子:

1 55
2 60
3 65
4 70
5 75

注意判断中涉及到 y ,而不是 x。 for 循环控制中的三个表达式可以使用不同的变量(注意尽管这个例子是合法的,它并不是好的编程风格。如果不是使用一个代数计算来进行更新,这个程序将会更清楚)。

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

7. 你甚至可以让一个或多个表达式为空(但是不要遗漏分号)。只须确保在循环中包含了一些能使循环结束的语句。

/* for_none.c */
#include <stdio.h>
int main (void)
{
int ans,n;

ans = 2;
for (n = 3; ans <= 25;)
ans = ans * n;
printf ("n = %d ; ans = %d \n",n,ans);
getchar();
return 0;
}

下面是输出 :
n = 3 ; ans = 54

在循环中 n 的值保持为 3 。变量 ans 的值开始为 2 ,然后增加到 6,18,最后为 54 (18小于25所以 for 循环再执行一次)。顺便说一句,中间的那个控制表达式为空会被认为是真,所以下面的循环会永远执行:

for(;;)
printf (" I want some action \n");

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

8. 第一个表达式不必初始化一个变量,它也可以是某种类型的 printf()语句。要记住第一个表达式只在执行循环的其他部分被求值或执行一镒。

/* for_show.c */
#include <stdio.h>
int main (void)
{
int num = 0;

for (printf ("Keep entering numbers \n");num != 6;)
scanf ("%d",&num);
printf ("That's the one I want\n");
getchar();
getchar();
return 0;
}

这段程序只把第一条消息打印一次,然后在你输入 6 之前为断地接收数字:

Keep entering numbers
3
5
8
6
That's the one I want

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

9. 循环中的动作可以改变循环表达式的参数。例如,假定你有一个这样的循环:

for (n = 1; n < 1000; n = n + delta)

如果执行几次循环之后,程序觉得 delta 的值太小或太大,循环中的 if 语句(第 7 章“ C控制语句:分支和跳转”)就可以改变 delta 的大小。在交到式程序中, delta 的值可以在循环运行时由用户进行改变。这种调节有一点危险,例如把 delta 设置为 0 会使你(和循环)停止不前。

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

简言之,因为你拥有选择 for 循环的控制表达式的自由,这使得你在执行固定次数的循环之外还可以做更多的事情。通过使用我们马上要讨论的一些运算符,for 循环的有效性可以得到进一步提高。

PS: 总结 : for 语句

关键字: for

总体注解: for 语句使用由分号分开的三个控制表达式来控制循环过程。 initialize (初始化)表达式只在循环语句执行之前执行一次。然后对 test(检验)表达式求值,如果该表达式为真(或非零)循环就被执行一次。然后计算 update (更新)表达式,接着再次检查 test 表达式。 for 语句是一个入口条件循环,即是否再次执行循环的决定是在循环执行之前做出的。因此,有可能循环一次也不执行。该形式的 statement 部分可以是一个简单语句或一个复合语句。

形式:
for (initialize(初始化);test(检验);update(更新))
statement

在 test 为假(或零)之前重复执行循环。

例如:

for ( n = 0 ; n < 10; n++)
printf (" %d %d \n",n,2*n+1);

6.6 更多赋值运算符: += -= *= /= %=

C 有多个赋值运算符。最基本的一个当然是 = ,它简单地把其右边表达式的值赋给其左边的变量。其他赋值运算符对变量进行更新,每个这样的赋值运算符在使用时都是左边为变量名,右边为一个表达式。变量被赋予一个新的值,这个新值是它原来的值根据右边表达式的值进行调整得到的。确切的调整方式要依赖于运算符,例如:

------------------------------------------------------------------
scores += 20 等于 scores = scores + 20
------------------------------------------------------------------
dimes -= 2 等于 dimes = dimes - 2
------------------------------------------------------------------
bunnies *= 2 等于 bunnies = bunnies * 2
------------------------------------------------------------------
time /= 2.73 等于 time = time / 2.73
------------------------------------------------------------------
reduce %= 3 等于 reduce = reduce % 3
------------------------------------------------------------------

前面的列表中,运算符的右边使用了简单的数。但是这些运算符还可以与更复杂的表达式一起工作,例如:
x = x * y + 12 等于 x = x * (3 * y + 12)

我们讨论的这些赋值运算符具有与 = 同样低的优先级,也就是说低于 + 或 * 的优先级。这种低优先级在上一个一阵子中得到反映,在与 x 进行相乘之前把 12 加到了 3 * y 上。

C 并不要求你使用这些形式。但是它们更加简洁,与更长的形式相比可能会产生效率更高的机器代码。当你想在一个 for 循环语句中塞一些复杂的东西时,这些复合赋值运算符就特别有用了。

6.7 逗号运算符

逗号运算符扩展了 for 循环的灵活性,因为它使你可以在一个 for 循环中使用多个初始化或更新表达式。例如,程序清单 6.13 中的程序打印一类邮资费率(该费用为 第 1 个盎司 37美分,然后每增加 1 盎司 增加 23美分。)

程序清单 6.13 postage.c 程序
--------------------------------------------------------------------------
/* postage.c --- 一类邮资费率 */
#include <stdio.h>
int main (void)
{
const int FIRST_OZ = 37;
const int NEXT_OZ = 23;
int ounces,cost;

printf (" ounces cost \n");
for (ounces = 1,cost = FIRST_OZ; ounces <= 16; ounces++, cost += NEXT_OZ)
printf ("%4d $%4.2f \n",ounces,cost / 100.0);
return 0;
}

输出的前 4 行看上去是这个样子:
ounces cost
1 $ 0.37
2 $ 0.60
3 $ 0.83
4 $ 1.06

这个程序在初始化表达式和更新表达式中使用了逗号运算符。第一个表达式中的逗号使 ounce 和 cost 的值都进行了初始化。逗号的第二次出现使每次循环中 ounces 增加1, cost 增加 23(NEXT_OZ的值)。所有的计算都在 for 循环语句中执行 。

逗号运算符并不只限于 for 循环中使用,但是这是最常使用它的地方。该运算符还具有两个属性。首先,它保证被它分开的表达式按从左到右的次序进行计算(换句话说,逗号是个顺序点,逗号左边产生的所有副作用的都在程序运行到逗号右边之前生效)。因此,ounces 在 cost 之前初始化。在这个例子中顺序是不重要的,但是如果计算 cost 的表达式中包含了 ounces , 它就是重要的了。例如,假定你具有如下的表达式:

ounces++, cost = ounces * FITST_OZ

这将递增 ounces ,并在第二个子表达式中使用 ounces 的新值。作为顺序点的逗号保证左边子表达式的副作用在计算右边的子表达式之前生效。

其次,整个逗号表达式的值是右边成员的值。语句;

x = (y = 3,(z = ++y + 2)+5);

的效果是首先把 y 赋值为 3,把 y 递增为 4,然后把 4 加上 2,把结果 6 赋值给 z,接下来把 z 加 5, 最后把 x 赋值为结果值 11. 这里不讨论为什么有人会这样做。另一方面,假定你不小心在写一个数字时使用了逗号:

houseprice = 249,500;

这并没有语法错误。C 把它解释为一个逗号表达式,houseprice = 249是左子表达式,而 500 是右子表达式。因此整个逗号表达式的值就是右边表达式的值,并且左边的子语句把变量 houseprice 赋值为 249. 这样它的效果与下面的代码相同:

houseprice = 249;
500;

记住任何具有分号的表达式都可以成为一个语句,所以 500; 是一个什么都不做的语句。

另一方面,语句:

houseprice = (249,500);

把 houseprice 赋值为 500, 因为该值是右子表达式的值。

逗号也被用作分隔符,在下面两条语句中:

char ch,date;
printf (" %d %d \n",chimps,chumps);

逗号都是分隔符,而不是逗号运算符。

PS : 总结:新运算符

赋值运算符:

这些运算符使用指定的操作根据其右边的值来更新其左边的变量。

+= 把右边的值加到左边的变量上
-= 从左边的变量中减去右边的值
*= 把左边的变量乘以右边的值
/= 把左边的变量除以右边的值
%= 给出左边的变量除以右边的值之后的余数

例如:

rabbits *= 1.6 等于 rabbits = rabbits * 1.6;

这些复合赋值运算符和普通的赋值运算符有同样的比较的运算优先级,比算术运算符的优先级要低得多。因此,以下的两条语句最终效果相同:

contents *= old_rate + 1.2;

contents = contents * (old_rate + 1.2)

逗号运算符:

逗号运算符把两个表达式链接为一个表达式,并保证最左边的表达式最先计算。它通常被用
在 for 循环的控制表达式中以包含多个信息。整个表达式的值是右边表达式的值。

例如:

for (step = 2, fargo = 0; fargo < 100; step *= 2)
fargo += step;

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

当 Zeno 遇到 for 循环

我们来看一下如何使用 for 循环和逗号运算符来帮助解决一个古老的悖论。希腊哲学家 Zeno 曾经辩论说一支箭永远不能达到它的目标。他说,首先箭要到达目标距离的一半,然后又必须到达剩余距离的一亲,然后还有一半,这样就没有穷尽。 Zeno 说因为这个旅程有无限个部分,所以箭要花费无限的时间才能结束这个旅程。但我们怀疑这个论点中,Zeno 是自愿作为靶子。

我们采取一种宣的方法,假定箭用一秒的时间走完一半距离,然后要用 1/2 秒的时间来走完剩下距离的一半, 1/4 秒的时间来走完再次剩下的距离的一半,等等。可以用以下的无限序列来表示总的时间:

1 + 1/2 + 1/4 + 1/8 + 1/16 +......

程序清单 6.14 中的简短程序求出了前几项的和。

程序清单 6.14 zeno.c 程序
---------------------------------------------------------------------
/* zeno.c ---- 序列的和 */
#include <stdio.h>
int main (void)
{
int t_ct; /* 项计数 */
double time,x;
int limit;

printf (" Enter the number of terms you want :");
scanf ("%d",&limit);
for (time = 0, x = 1, t_ct = 1; t_ct <= limit; t_ct++, x *= 2.0)
{
time += 1.0/x;
printf ("time = %f when terms = %d \n",time,t_ct);
}
getchar();
getchar();
return 0;
}

下面是前 15 项的输出:

Enter the number of terms you want :15
time = 1.000000 when terms = 1
time = 1.500000 when terms = 2
time = 1.750000 when terms = 3
time = 1.875000 when terms = 4
time = 1.937500 when terms = 5
time = 1.968750 when terms = 6
time = 1.984375 when terms = 7
time = 1.992188 when terms = 8
time = 1.996094 when terms = 9
time = 1.998047 when terms = 10
time = 1.999023 when terms = 11
time = 1.999512 when terms = 12
time = 1.999756 when terms = 13
time = 1.999878 when terms = 14
time = 1.999939 when terms = 15

可以看到,尽管不断地添加新的项,总和看起来是变化不大的。数学家们确实证明了当项的数目接近无穷时,总和接近于 2.0,就像这个程序表明的那样。下面是一个证明,假定你用 S 来表示总和:

s = 1 + 1/2 + 1/4 + 1/8 + ....

把 s 除以 2 得到:

s/2 = 1 + 1/2 + 1/4 + 1/8 + ....

从第一个表达式中减去第二个表达式得到:

s - s/2 = 1 + 1/2 - 1/2 + 1/4 - 1/4 ....

除了第一值 1, 每个其他的值都是一正一负地成对出现的,所以这些项都可以消去,只留下:

s/2 = 1

然后两侧同时乘以 2 得到:

s = 2

从中可能汲取的一点启示是在进行复杂计算之前,先看一下数学上是否有更容易的方法来解决它。

程序本身有什么需要注意的呢?它说明你可以在一个表达式中使用多个逗号运算符,这里你初始化了 time ,x 和 count 。 在构建了循环条件之后,程序本身就很简短了。

6.8 退出条件循环 : do while

while 循环和 for 循环都是入口条件循环,在每次执行循环之前先检查判断条件,这样循环中的语句就有可能一次也不执行。 C 也有退出条件循环,判断条件在执行循环之后进行检查,这样就可以保证循环体中的语句至少被执行一次,这被称为 do while 循环。 程序清单 6.15 给出了一个例子。

程序清单 6.15 do_while.c 程序
-----------------------------------------------------
/* do_while.c -- 退出条件循环 */
#include <stdio.h>
int main (void)
{
const int secret_code = 13;
int code_entered;

do
{
printf ("To enter the triskaidekaphobia therapy club, \n");
printf (" pleasw enter the secret code number : ");
scanf ("%d", &code_entered);
} while (code_entered != secret_code);
printf (" Congratulations! You are cured \n");
getchar();
getchar();
return 0;
}

程序清单 6.15 中的程序在用户输入 13 之前反复读取输入值。以下是一个运行的例子;

To enter the triskaidekaphobia therapy club,
pleasw enter the secret code number : 12
To enter the triskaidekaphobia therapy club,
pleasw enter the secret code number : 14
To enter the triskaidekaphobia therapy club,
pleasw enter the secret code number : 13
Congratulations! You are cured

使用了 while 循环的与之等价的程序会长一点,就像程序清单 6.16 中那样。
------------------------------------------------------------

程序清单 6.16 entry.c 程序
-----------------------------------------------------
/* entry.c -- 入口条件循环 */
#include <stdio.h>
int main (void)
{
const int secret_code = 13;
int code_entered;

printf ("To enter the triskaidekaphobia therapy club, \n");
printf (" pleasw enter the secret code number : ");
scanf ("%d", &code_entered);
while (code_entered != secret_code)
{
printf ("To enter the triskaidekaphobia therapy club, \n");
printf (" pleasw enter the secret code number : ");
scanf ("%d", &code_entered);
}
printf (" Congratulations! You are cured \n");
getchar();
getchar();
return 0;

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

下面是 do while 循环的一般形式:

do
statement
while (expression)

statement 部分可以是简单语句或复合语句。请注意 do while 循环本身是一个语句,因此它需要一个结束的分号。

do while 循环至少要被执行一次,因为在循环体被执行之后才进行判断。与之相反, for 或者 while 循环可以一次都不执行,因为它们是在执行之前进行判断。应该把 do while 循环仅用于那些至少需要执行一次的情况。例如一个密码程序要包括一个循环,它的伪代码如下;

do
{
prompt for password
read user input
} while (input not equal to password)

要避免以下伪代码中的这种 do while 结构:

do
{
ask user if he or she wants to continue
some clever stuff
}while (answer is yes)

这里,用户回答 no 之后仍将执行 some clever stuff 部分,因为判断来得太迟了。

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

PS : 总结: do while 语句

关键字: do while

总体注解: do while 语句创建了一个在判断表达式为假(或零)之前重复执行的循环。do while 语句是一个退出条件循环,是否再次执行循环的决定是在执行了一次循环之后做出的。因此循环必须至少被执行一次。该形式的 statement 部分可以是一个简单语句或一个复合语句。

形式:

do
statement
while (expression);

在 expression 为假(或零)之前重复执行 statement 部分。

例如:

do
scanf ("%d", &number);
while (number != 20 );

6.9 选择哪种循环

当你确定需要循环时,应该使用哪一种呢?首先要确定你需要入口条件循环还是退出条件循环。通常是需要入口条件循环。有若干原因使得计算机科学家认为入口条件循环更好一些。首先是因为一般原则是在跳过(或循环)之前进行查看要比之后好;其次是如果在循环开始的地方进行循环判断,程序的可读性更强;最后一点是在很多应用中,如果一开始就不满足判断符,那么跳过整个循环是重要的。

假定你需要一个入口条件循环,应该使用 for 还是 while 循环? 这有些是个人爱好的问题,因为二者可以做的事情是相同的。要使 for 循环看起来像 while 循环,可以去掉它的第一个和第三个表达式。例如,下面两种写法是相同的:

for(;test;)
while (test)
{
body
update;
}

与下面这种形式相同:

for (initialize;test;update)
body;

说到流行的风格,在循环涉及初始化和更新变量时使用 for 循环较为适当,而在其他条件下使用 while循环更好一些。 while 循环对以下的条件来说是很自然的:

while (scanf ("%d",&num) == 1)

而对那些涉及到用索引计数的循环,使用 for 循环是一个更自然的选择。例如:

for (count = 1; count <= 100; count++)

6.10 嵌套循环

嵌套循环(nested loop)是指在另一个循环之内的循环。通常使用嵌套循环来按行按列显示数据。也就是说一个循环处理一行中的所有列,而另一个循环则处理所有的行。程序清单 6.17 是一个简单的例子。

程序清单 6.17 rows1.c 程序
------------------------------------------------------------
/* rows1.c -- 使用嵌套循环 */
#include <stdio.h>
#define ROWS 6
#define CHARS 10
int main (void)
{
int row;
char ch;

for (row = 0; row < ROWS; row++) /* 第10行 */
{
for (ch = 'A'; ch < ('A'+CHARS);ch++) /* 第12行 */
printf ("%c",ch);
printf ("\n");
}
getchar();
return 0;
}

运行这个程序会产生下列输出:

ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ
ABCDEFGHIJ

6.10.1 程序讨论

开始于 第 10 行的 for 循环被称为外部循环,而开始于第 12 行的循环被称为内部循环,因为它位于另一个循环的内部。外部循环开始时 row 的值为 0 ,当 row 到达 6 时结束。因此外部循环要执行 6次,row 的值从 0 变到 5 。每次循环中的第一个语句都是内部的 for 循环。这个循环执行 10 次,在同一行上打印从 A 到 J 的字符。外部循环的第二个语句是 printf("\"); 。这个语句开始一个新行,这样内部循环下次运行的时候,输出就会位于一个新的行上。

请注意在嵌套循环中,内部循环在外部循环的每次单独循环中都完全执行它的所有循环。在上一个例子中,内部循环在一行中打印 10 个字符,而外部循环创建 6 行。

6.10.2 嵌套变化

在前面的例子中,内部循环在外部循环的每个周期都做着同样的事情。通过使内部循环的一部分依赖于外部循环,可以使内部循环在每个周期中的表现不同。例如,程序清单 6.18 稍微修改了上一个程序,使内部循环的开始字符依赖于外部循环的循环次数。它还使用了新的注释风格,并用 cnost 代替了 #define,这有助于你熟悉这两种方法。

程序清单 6.18 rows2.c 程序
---------------------------------------------------------
/* rows2.c -- 使内部循环依赖于外部循环的嵌套循环 */
#include <stdio.h>
int main (void)
{
const int ROWS = 6;
const int CHARS = 6;
int row;
char ch;

for (row = 0; row < ROWS; row++)
{
for (ch = ('A' + row);ch < ('A'+CHARS); ch++)
printf ("%c",ch);
printf ("\n");
}
getchar();
return 0;
}

输出如下:

ABCDEF
BCDEF
CDEF
DEF
EF
F

因为在外部循环的每个周期中都要把 row 的值加到‘A’上,所以 ch 在第一行中都被初始化为字母表中后面的字符。但是判断条件并没有改变,所以每一行依然是以 F 结尾。这导致在第一个新行都打印更少的字符。

6.11 数组

在很多程序中数组都是重要的性能。它们使你可以用一种使得的方式来存储一些相关的信息项。我们将在第 10 章“数组和指针”中详细讨论数组,但是由于数组经常被用在循环中,所以现在先简单介绍一下。

一个数组就是线性存储的一系列相同类型的值,例如 10 个字符或 15 个整数。整个数组有一个单一的名字,单独的项或元素可以使用一个整数索引来进行访问。例如,下列声明:

float debts[20]

声明 debts 是一个具有 20 个元素的数组,其中每个元素都是一个类型为 float 的值。这个数组的第一个元素称为 debts[0],第二个元素称为 debts[1],这样直到 debts[19]。注意数组元素的编号是从 0 而不是 1 开始的。每个元素都可以被赋予一个 float 类型的值。例如,你可以使用以下代码:
debts[5] = 32.54;
debts[6] = 1.2e + 21;

实际上,你可以像使用相同类型的变量那样使用一个数组元素。例如,你可以把一个值读入一个特定的元素:

scanf ("%f",&debts[4]); // 为第 5 个元素读入一个值

一个潜在的易犯错误是:出于执行速度的考虑,C 并不检查你是否使用了正确的下标。例如,以下都是错误的代码:

debts[20] = 88.32; // 没有这个数组元素
debts[33] = 828.12; // 没见有这个数组元素

但编译器并不会发现这样的错误。当程序运行时,这些语句把数据放在可能由其他使用的位置上,因而可能破坏程序的结束甚至使程序崩溃。

数组可以是任意数据类型的数组。

int nannies[22] // 一个存放 22 个整数的数组
char actors[26] // 一个存放 26 个字符的数组
long big [500] // 一个存放 500 个长整数的数组

例如,我们先前提到过的字符串就是一个特别的例子,它被存储在一个字符数组中。一般说来,字符数组就是元素都被赋予字符值的数组。如果字符数组包含了空字符 \0 ,那么字符数组的内容就构成了一个字符串,其中空字符标志着字符串的结尾。(请参见图 6.6)

图 6.6 字符数组和字符串

字符数组,但不是字符串
you can see it.
既是字符数组,又是字符串
you can see it.\0
-------------------------------------------

用于标识数组元素的数字称为下标(subscript),索引(index)或偏移量(offset)。下标必须是整数,而且像前面提到的那样,下标从 0 开始。数组中的元素在内存中是顺序存储的,如图 6.7 所示的那样。

-------------------------------------------
图 6.7 内存中的 char 和 int 数组

int boo[4] (注:每个整数占用 2 个字节)
1980 46 4816 3
boo[0] boo[1] boo[2] boo[3]

char foo[4] (注:1字节的 char)
h e l p
foo[0] foo[1] foo[2] foo[3]
----------------------------------------------

在 for 循环中使用数组

在很多很多的地方要用到数组。程序清单 6.19 是一个相对简单的例子。这个程序读入 10 个高尔夫分数然后进行处理。通过使用数组就可以避免使用 10 用于存储分数不同的变量名。你也可以使用 for 循环进行读入。这个程序下来报告分数的总和,平均值,差点(handicap,它是平均值与标准分之间的差。)

程序清单 6.19 scores_in.c 程序
-----------------------------------------------------------------------
/* scores_int.c --- 使用循环进行数组处理 */
#include <stdio.h>
#define SIZE 10
#define PAR 72
int main (void)
{
int index,score[SIZE];
int sum = 0;
float average;

printf ("enter %d golf scores : \n",SIZE);
for (index = 0; index < SIZE; index++)
scanf ("%d",&score[index]); // 读入 10 分数
printf (" The scores read in are as follows :\n");
for (index = 0; index < SIZE; index++)
printf ("%5d",score[index]); // 验证输入
printf ("\n");
for (index = 0; index < SIZE; index++)
sum += score[index]; // 求它们的和
average = (float) sum / SIZE; // 节省时间的方法
printf (" Sum of scores = %d, average = %.2f\n",sum,average);
printf (" That's a handicap of %.0f \n",average - PAR);
getchar();
getchar();
return 0;
}

现在我们看看程序清单 6.19 是否能工作,接着我们再进行一些解释。下面是输出:

enter 10 golf scores :
102 98 112 108 105 103 99 101 96 102 100
The scores read in are as follows :
102 98 112 108 105 103 99 101 96 102
Sum of scores = 1026, average = 102.60
That's a handicap of 31

它确实工作了,我们来看一些细节。首先,注意到尽管这个例子你输入了 11 个数,但只有 10 个被读入,因为读取循环只读入 10 个值。因为 scanf()跳过空白字符,所以你可以在一行之内输入所有的 10 个数,也可以在每一行只输入一个数,或者你也可以像这个例子一样混合使用新行与空格来分隔输入(因为要对输入进行缓冲,所以只有当你键入回车键的时候这些数字才被发送给程序)。

其次,使用数组和循环要比使用 10 个单独的 scanf()语句和 10 个单独的 printf()语句来读入并验证这 10 分数更方便。for 循环提供了一种简单而直接的方法来使用数组下标。注意到 int 数组的每个元素都被作为一个 int 变量来处理。要读入 int 变量 fue,你可以使用 scanf ("%d",&fue); 。要读入 int 元素 score[index],所以它使用了 scanf ("%d",&score[index]); .

这个例子说明了一些风格问题。首先,使用 #define 指令创建一个指定数组大小的明显常量(SIZE)是一个好主意,你可以在定义数组和设置循环限制时使用这个常量。如果你以后需要把程序扩展为处理 20 个分数,简单地把 SIZE 重新定义为 20 就可以了,不需要改变程序中使用了数组大小的每地方。C99 允许你使用常量值指定数组大小,但是 C90不允许,而 #define 在两种情况下都可以使用。

其次,下面的代码可以很方便地处理一个大小为 SIZE 的数组:

for (index = 0; index < SIZE; index++)

获得正确的数组边界是很重要的。第一个元素具有索引值 0,循环从把 index 设为 0 开始。因为编号是从 0 开始的,所以最后一个元素的索引为 SIZE-1 。也就是说,第 10 个元素为 score[9] 。使用判断条件 index < SIZE 可以实现这一点,它使得循环中使用的最后一个 index 的值为 SIZE-1 。

第三,一个好的编程是使用程序重复输出或“加显”刚刚读入的值。这有助于确保程序处理了你所期望的数据。

最后,注意程序清单 6.19 使用了三个独立的 for 循环。你可能想知道这是否是真正必需的,是否可以在一个循环中合并多个操作?答案是肯定的,你可以做到这一点,那会使程序更加紧凑。但是你应该根据模块化(modularity)的原则进行调整。这个术语所蕴涵的思想是程序应该分为一些单独的单元,每个单元执行一个任务,这会合更容易。也许更重要的一点是:如果程序的不同部分不在一起,那么模块化可以使程序更容易升级或修改。当你了解了函数之后,就可以把每个单元放入一个函数中来增强程序的模块化。

6.12 使用函数返回值的循环例子

本章中的最后一个例子使用一个函数,它计算一个数的整数次幂的结果(要进行严格的数值处理,math.h 库提供了一个名为 pow()的更强大的幂函数,它允许计算浮点数次幂)。在这个练习中的三个主要任务是为计算答案设计算法,在一个返回答案的函数中应用算法,以级提供一个使得的方法来测试该函数。

首先看一下算法。我们通过限制为求正整数次幂来简化函数。这样,如果你想要求 n 的 p 次幂,应该把 n 与自己相乘 p 次。很自然这可以由一个循环来完成。可以设置 变量 pow 为 1 然后反复把它与 n 相乘:

for (i = 1; i <= p; i++)
pow *= n;

回忆一下,*= 运算符把其左边的数乘上其右边的数。在第一次循环后, pow 就是 n 的 1 次幂,也就是 n 。第二次循环后, pow 就是它的先前值(n)乘以 n ,也就是 n 的平方,等等。在这种情形中使用 for 循环是很自然的,因为循环执行预先确定的次数 (在 p 已知后)。

现在你已经有了一个算法,下面应该决定使用会数据类型。指数 p 是一个整数,其类型应该为 int 。为了允许 n 及其幂的值有较大范围,n 和 pow 使用 double 类型。

接下来,我们考虑如何把这些功能放在一起。需要为函数传递两个值,然后让函数返回一个值。要把信息传递给函数,可以使用两个参数,一个 double 和一个 int ,来指定求哪个数的多少次幂。如何安排函数以使它向调用程序返回一个值?写一个具有返回值的函数要做以下事情:

1. 当定义函数时,说明它的返回值类型

2. 使用关键字 return 指示要返回的值。

例如,你可以这样:

double power (double n, int p) // 返回 double 类型的值
{
double pow = 1;
int i;

for (i = 1; i <= p; i++);
pow *= n;
return pow; // 返回 pow 的值
}

要声明函数类型,可以在函数名之前写出类型,就像声明一个变量时那样。关键字 return 使函数把跟在该关键字后面的值返回给调用函数。这里返回了一个变量的值,但是也可以返回表达式的值。例如,以下是一个合法的语句:

return 2 * x + b;

函数将计算该表达式的值并返回之。在调用函数中,可以把返回值赋给另一个变量;可以把它作为一个表达式中的值;可以把它用为另一个函数的参数,例如 printf ( "%f",power(6.28,3));也可以忽略它。

现在我们在程序中使用这个函数。要测试这个函数很方便,只须向它传递一些值来看它是如何反应的。这意味着要建立一个输入循环,很自然的选择是使用 while 循环。可以使用 scanf()来一次读入 2 个值。如果成功地读入了 2 个值,scanf()就是返回值 2,这样你就可以通过把 scanf()的返回值与 2 进行比较来控制循环。还有一点:要在你的程序中使用 power()函数,需要声明它,就像声明一个程序中用的变量一样。程序盖章 6.20 中是这相程序。

程序清单 6.20 power.c 程序
--------------------------------------------------------
/* power.c --- 计算数值的整数次幂 */
#include <stdio.h>
double power (double n, int p); // ANSI 原型
int main (void)
{
double x,xpow;
int exp;

printf (" Enter a number and the positive integer power ");
printf (" to which\nthe number will be raised . Enter q ");
printf (" to quit \n ");
while (scanf ("%lf %d",&x,&exp) == 2 )
{
xpow = power (x,exp); // 函数调用
printf ("%.3g to the power %d is %.5g\n",x,exp,xpow);
printf (" enter next pair of numbers or q to quit\n");
}
printf ("hope you enjoyed this power trip --- bye \n");
getchar();
return 0;
}

double power (double n, int p)
{
double pow = 1;
int i;

for (i = 1; i<= p; i++)
pow *= n;
return pow; // 返回 pow 的值
}

下面是一个运行示例;

Enter a number and the positive integer power to which
the number will be raised . Enter q to quit
1.2 12
1.2 to the power 12 is 8.9161
enter next pair of numbers or q to quit
2
16
2 to the power 16 is 65536
enter next pair of numbers or q to quit
q
hope you enjoyed this power trip --- bye

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

6.12.1 程序讨论

main()程序是一个驱动程序 (driver)的例子。驱动程序是被设计用来测试一个函数的短小的程序。

这里的 while 循环是我们以前使用过的形式的推广。键入 1.2 12 使 scanf()成功地读入两个值并返回 2 ,循环继续进行。因为 scanf()跳过了空白字符,所以就像例子中显示的那样,输入可以在多行进行。但键入 q 会使返回值为 0,因为 q 不能使用 %1f 说明符进行读取。这会使 scanf()返回 0 而结束循环。与之类似,键入 2.8 q 会使返回值为 1, 也会结束循环。

现在我们来看一下与函数相关的一些事情。power()函数在这个程序中出现了三次,第一次出现是这样的:

double power (double n, int p); // ANSI 原型

这个语句声明程序将使用一个名为 power()的函数。开始的关键字 double 表明 power()函数会返回一个类型为 double 的值。编译器需要知道 power()的返回值类型,这样它才能知道需要多少字节的数据以及如何解释它们,这也是你必须声明函数的原因。括号中的 double n, int p 说明
power()接受两个参数,第一个参数应是类型为 double 的值,第二个参数的类型应为 int 。

第二次出现是这样的;

xpow = power (x,exp); // 函数调用

程序在这里调用了这个函数,并传递给它两个值。函数计算 x 的 exp 次幂,然后把结果返回给调用程序,接着返回值又被赋给变量 xpow 。

第三次出现是在函数定义的开始:

double power (double n,int p) // 函数定义

在这里 power()接受由变量 n 和 p 表示的两个参数,一个 double 和一个 int 。请注意在函数定义时,power()后面没有分号,而在函数声明时是有分号的。在函数头之后就是完成 power()所做事情的代码。

回忆一下,函数使用了 for 循环来计算 n 的 p 次幂并把它赋值给 pow 。下面这行使 pow 成为函数的返回值。

return pow; // 返回 pow 的值

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

6.12.2 使用具有返回值的函数

声明函数,调用函数,定义函数,使用 return 关键字,这些就是在定义并使用具有返回值的函数时的基本要素。

在这点上你可能会有一些疑问。例如,既然在使用函数的返回值前要声明函数,为什么使用 scanf()的返回值时无须声明 scanf()?为什么除了在定义中说明 power()的类型为 double 之外,还必须单独地声明这个函数?

我们首先看一下第二个问题。编译器在程序中第一次遇到 power()时,它需要知道 power()是什么类型。而此时编译器还没有遇到 power()的定义,所以它并不知道定义中说明了返回类型为
double 。 为了帮助编译器 power()在其他地方定义而且它的返回值类型为 double 。如果你把 power()函数的定义放在 main()之前,就可以省略向前声明,因为编译器在到达 main()之前已经知道了关于 power()的所有信息。但是这不是标准 C 的风格。因为 main()通常提供一个程序的整体框架,所以最好是首先给出 main()函数。此外,函数经常放在单独的文件中,所以向前声明是必不可少的。

接下来,为什么无须声明 scnaf()?这是因为你已经声明过了。stdio.h 头文件中含有 scanf(),printf()以及其他一些 I/O 函数的函数声明。scanf()的声明说明它的返回类型为 int

6.13 关键概念

循环是一个强大的编程工具。在建立循环时应该特别注意三个方面

· 明确定义结束循环的条件

· 确保在循环判断中使用的值在第一次使用之前已经初始化

· 确保循环在每个周期中更新了判断值。

C 通过数值计算来处理判断条件。结果为 0 表示 假,任何其他值都为真。使用了关系运算符的表达式通常被用来进行判断,它们有些特殊。如果为真,关系表达式的值为 1,为假则为 0 ,这与新的 _Bool 类型所允许的值保持一致。

数组由相同类型的邻近的内存位置组成。你需要谨记数组元素是从 0 开始编号的,这样最后一个元素的下标就是比元素的个数少 1 。C 并不检查你是否使用了合法的下标值,所以这需要由你自己来负责。

使用一个函数需要完成三个单独的步骤:

1. 使用函数原型声明该函数。

2. 在程序中通过函数调用来使用该函数。

3. 定义函数。

原型使编译器可以检查你是否正确地使用了函数,而定义则规定了函数如何工作。现代的编程习惯是把程序的元素分为接口和实现部分,原型和定义就是这样的例子。接口部分描述了如何使用一个特性,这正是原型所做的,而实现部分说明了采取的具体动作,这正是定义所做的。

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

6.14 总结

本章的主要话题是程序控制。C 为实现程序的结构化提供了很多帮助。 while 和 for 语句提供了入口条件循环,for 语句特别适合那些侯岛有初始化和更新的循环。逗号运算符使你可以在一个 for循环中初始化和更新多个变量。在不多的场合中也需要退出条件循环,C 的 do while 语句就是一个退出条件循环。

典型的 while 循环设计看上去就像这样:

get first value // 获取第一个值
while (vale meets test) // 将获取的值检验
{
process the value // 按设计流程处理该值
get next vale // 获取新的值
}

而做同样工作的 for 循环看上去就像这样;

for (get first value; value meets test ; get next value)
process the value

所有这些循环都使用一个判断条件来决定是否执行另一个循环周期。一般地说,如果判断表达式等于一个非零值,循环就继续执行;否则它就结束。判断条件通常是一个关系表达式,即一个由关系运算符构成的表达式。如果关系为真,表达式的值就为 1, 否则就为 0 。 C99 引入了 _Bool 类型的变量,这种变量只能具有值 1 或 0.分别表示真或假。

除了关系运算符,本章还介绍了一些 C 算术赋值运算符,例如 += 和 *= 这些运算符通过对左边的操作数执行算术运算来修改它的值。

接下来我们简单介绍了数组。数组的声明使用方括号,括号中的值说明元素的个数。数组的第一个元素索引编号为 0 ,第二个为 1,一直这样下去。例如,下列声明:

double hippos[20];

创建了一个具有 20 个元素的数组,单个元素从 hippos[0] 到 hippos[19]。可以通过循环方便地使用为数组进行编号的下标。

最后,本章说明了如何编写和使用具有返回值的函数。

6.15 复习题

---------------------------------------------------------------
1.给出每行之后 quack 的值。

int quack = 2;
quack += 5;
quack *= 10;
quack -= 6;
quack /= 8;
quack %= 3;


int quack = 2;
quack = 7;
quack = 70;
quack = 64;
quack = 8;
quack = 2;

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

2. 假定 value 是一个 int 类型的值,以下的循环会产生什么输出?

for (value = 36; value > 0; value /= 2)
printf ("%3d",value);

如果 value 是一个 double 类型的值而不是 int 类型的值,会有什么问题?

答: 36 18 9 4 2 1

如果 value 是 double 类型,那么 value 变得小于 1 时判断条件仍会保持为真。循环会一直执行,真到由于浮点数下溢而产生 0 值。另外,此时 %3d 说明符也是不正确的。

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

3. 表示出以下判断条件

a. x 大于 5
b. scanf()尝试读入一个 double 值(名为 x)并且失败
c. x 的值为 5


a. x > 5;
b. scanf ("%lf",&x) != 1;
c. x == 5;

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

4. 表示出以下判断条件

a. scanf()成功地读入了一个整数
b. x 不等于 5
c. x 大于或等于 20

答:
a. scanf ("%d",&x) == 1;
b. x != 5;
c. x >= 20;

------------------------------------------------------------------------
5. 你怀疑以下的程序可能有问题。你能找出什么错误?
#include <stdio.h>
int mait (void)
{ // 3
int j,j,list(10); // 4 应该是 list[10]

for(i = 1, i <= 10, i++) //6 逗号应该为分号
{ // 6 i 范围应该是从 0 到 9,而不是从 1 到 10
list[i] = 2 *i+3; // 8
for (j = 1, j>=i,j++) // 9 逗号应该为分号
printf ("%d",list[j]); //9 >=应该是 <=。否则 当i为1时,循环永远不会结束。
printf("\n"); 11
}
return 0;
}

答: 正确版本应为:--------------------------------

#include <stdio.h>
int main (void)
{
int i,j,list[10];

for (i = 0; i <= 9; i++)
{
list[i] = 2 * i + 3;
for (j=1; j <= i; j++)
printf ("%d",list[j]);
printf ("\n");
}
getchar();
return 0;
}
-------------------------------------------------------------------------------------

6. 使用嵌套循环编写产生下列图案的程序;
$$$$$$$$
$$$$$$$$
$$$$$$$$
$$$$$$$$

答:

#include <stdio.h>
int main (void)
{
int row,line;

for (row = 1; row <= 4; row++)
{
for (line = 1; line <= 8 ; line++)
printf ("$");
printf ("\n");
}
getchar();
return 0;
}

注: 标准 for 排列的话 列一般放在第一个 for 循环 而行一般放在第二个 for 循环
-------------------------------------------------------------------------------------

7. 以下程序会打印出什么?

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

while (++i < 4)
printf ("Hi!");
do
printf ("Bye!");
while (i++ < 8);
return 0;
}

答: Hi! Hi! Hi! Bye! Bye! Bye! Bye! Bye!

b. #include <stdio.h.
int main (void)
{
int i;
char ch;

for (i = 0,ch = 'A'; i < 4; i++,ch += 2 * i)
printf ("%c",ch);
return 0;
}

答; ACGM

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

8. 假定输入为 “Go west,young man”,以下的程序会产生什么样的输出?(在ASCII序列中,!紧跟在空格字符后面)

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

scanf ("%c",&ch);
while (ch != 'g')
{
printf ("%c",ch);
scanf("%c",&ch);
}
return 0;
}

答: Go west,young

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

scanf ("%c",&ch);
while (ch != 'g')
{
printf ("%c",++ch);
scanf("%c",&ch);
}
return 0;
}

答; Hp!xftu-!zpvo

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

do {
scanf ("%c",&ch);
printf ("%c",ch);
}while (ch != 'g');
return 0;
}

答: Go west,young
-----------------------------------------
d. #include <stdio.h>
int main (void)
{
char ch;

scanf ("%c",&ch);
for (ch = '$'; ch != 'g'; scanf ("%c",&ch));
pruchar(ch);
return 0;
}

答: Go west,youn
-----------------------------------------------------------------------

第 9 题 略

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

10. 考虑以下声明:

double mint[10];

a. 数组名是什么? // mint
b. 在数组中有多少个元素? // 10个
c. 在每个元素中存储着什么类型的值? // double
d. 下面哪个对该数组正确地使用了 scanf()? // 第二个
1. scanf("lf",mint[2])
2. scanf("lf",&mint[2])
3. scanf("lf",&mint)

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

11. Noah 先生喜欢以 2 计数,所以他写了以下的程序来创建一个数组,并用整数2,4,6,8等等来填充它。如果有错误,这个程序的错误是什么?

#include <stdio.h>
#define SIZE 8
int main (void)
{
int by_twos[SIZE];
int index;

for (index = 1; index <= SIZE; index++)
by_twos[index] = 2 * index;
for (index = 1; index <= SIZE;index++)
printf ("%d",by_twos);
printf("\n");
return 0;
}

答: 因为第一个元素的索引为 0 ,所以循环的范围应该从 0 到 SIZE-1, 而不是从 1 到 SIZE。但是这样改变会使第一个元素被赋值为 0 而不是 2 。 所以要这样重写这个循环;

for (index = 0; index <= SIZE; index++)
by_twos[index] = 2 * (index + 1);

类似地,也应该改变第二个循环的限制条件。另外,应该在数组名后使用数组索引:

for (index = 0; index <= SIZE;index++)
printf ("%d",by_twos[index]);

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

12. 你想要写一个返回 long 值的函数。在你的函数定义中应该包含什么?

答 函数应该把返回类型声明为 long,并包含一个返回 long 值的 return 语句

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

13. 定义一个函数。该函数接受一个 int 参数,并以 long 类型返回参数的平方值。

答;

long square (int num)
{
long squ;

squ = (long) num * num;
return squ;
}

long square (int num)
{
return ((long)num) * num;
}

--------------------------------------------------------------------------
14 题 略
---------------------------------------------------------------------------

6.16 编程练习

-------------------------------------------------------------------------------
1.编写一个程序,创建一个具有 26 个元素的数组,并在其中存储 26 个小写字母。并让该程序显示该数组的内容。

解:

#include <stdio.h>
#define SIZE 26
int main (void)
{
char ch[SIZE];
int i;

for (i = 0; i < SIZE; i++)
ch[i] = 'a'+i;
for (i = 0; i< SIZE; i++)
printf ("%c",ch[i]);
getchar();
return 0;

}

注意: 这类程序主要是注意 字符数组应该怎样赋值的问题 如本例中的 ch[i] = 'a'+i;

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

2. 使用嵌套循环产生下列图案:

$
$$
$$$
$$$$
$$$$$

解:

#include <stdio.h>
#define NUM 5
int main (void)
{
int row,line;

for (row =1; row <= NUM; row++)
{
for (line = 0; line < row;line++)
printf ("$");
printf ("\n");
}
getchar();
return 0;

}

注意: 递减的话 内部循环的循环判断次数 要和外部循环的判断次数同一个变量

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

3. 使用嵌套循环产生下列图案:

F
FE
FED
FEDC
FDECB
FDECBA

解:

#include <stdio.h>
int main (void)
{
char row,line,ch; // 定义三个字符变量

ch = 'F'; // 将 变量 F 赋值给 ch
for (row = ch; row >= 'A';row-- ) // 将 ch 赋值给 row;如果row>A row--
{
for (line = ch ; line >= row; line--) // 将 ch 赋值给 line;如果line>row line--
printf ("%c",line);
printf ("\n");

}
getchar();
return 0;

}

注意:内部循环判断的次数必须要以外部循环判断的次数为参照。

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

4. 让程序要求用户输入一个大写字母,使用嵌套循环产生像下面的这样的金字塔图案;

A
ABA
ABCBA
ABCDCBA
ABCDEDCBA

这种图案要扩展到用户输入的字符。例如,前面的图案是在输入 E 时需要产生的。
提示:使用一个循环来处理行,在每次一行中使用三个内部循环,一个处理空格,一个以升序打印字母,一个以降序打印字母。

解:
#include <stdio.h>
#define LETS 26
int main (void)
{
int index,row,line,chr,temp;
char ch;
char lets[LETS];

for (index=0;index<LETS;index++) // 初始化 A-Z 的数组元素
lets[index]='A'+index;
printf ("请输入一个字母 : ");
scanf ("%c",&ch);
temp = ch - 'A'; // 获取递增次数

for (index = 0; index <=temp; index++)
{
for ( row = 0; row <= temp-index; row++)
printf (" ");
for ( line = 0; line <= index; line++)
printf ("%c",lets[line]);
for (chr = line - 2; chr >= 0;chr--)
printf ("%c",lets[chr]);
printf ("\n");
}
getchar();
getchar();
return 0;
}

/* 先按需求作一个总循环 (第一个for),后面的三个 for 均依照第一个 for 作参照而变化。经验之谈。一定要先有个参照物,不然一旦循环多了后就很难理清思路。
*/
------------------------------------------------------------------------------

5. 编写一个程序打印一个表,表的每一行都给出一个整数,它的平方以及它的立方。要示用户输入表的上限与下限。使用一个 for 循环。

解:

#include <stdio.h>
int main (void)
{
double d_squ,d_cub;
int num,upper,lower,temp;

printf (" 请输入一个下限数 :");
scanf ("%d",&lower);
printf ("\n 请输入一个上限数 :");
scanf ("%d",&upper);
while (upper < lower) // 如果输入的下限大于上限 将它们交换值
{
temp = upper;
upper = lower;
lower = temp;
}

printf ("num squ cud \n");
for (num = lower; num <= upper;num++)
{
d_squ = num * num;
d_cub = num * num * num;
printf ("%d %5g %5g \n",num,d_squ,d_cub);
}
getchar();
getchar();
return 0;
}

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

6. 编写一个程序把一个单词讲入一个字符数组,然后反向打印出这个词。
提示:使用 strlen()(第 4 章计算数组中最后一个字符的索引。)

解:

#include <stdio.h>
#define SIZE 100
int main (void)
{
int index,num;
char ch[SIZE];

printf ("请输入一个单词,程序将反向打印它们 :");
scanf ("%s",ch); /* 获取字符串的 格式说明符为 $s 输入直接赋值数组时 */
num = strlen(ch); /* 不用 & 说明符 和 直接打该数组名即可 */

for (index = num-1; index >= 0; index--)
printf ("%c",ch[index]);
system("PAUSE");
return 0;
}
----------------------------------------------------------------------------------------

7. 编写一个程序,要求输入两个浮点数,然后打印出用二者的差值除以二者乘积所得的结果。在用户键入非数字的输入之前程序循环处理每对输入值。

#include <stdio.h>
int main (void)
{
double num1,num2;
double temp;

printf ("请输入两个数( q to quit) : ");
while (scanf("%lf%lf",&num1,&num2) == 2)
{
temp = (num1 - num2 ) / (num1 * num2);
printf ("两个数的差除以二者乘积的结果是 %.2f \n",temp);
}
system("PAUSE");
return 0;

}

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

8. 对练习7 进行修改,让它使用一个函数来返回计算值

解;

#include <stdio.h>
double fun (double x, double y); // 定义有返回值的函数原型,x y 为函数的形参
int main (void)
{
double num1,num2;
double temp;

printf ("请输入两个数( q to quit) : ");
while (scanf("%lf%lf",&num1,&num2) == 2)
{
temp = fun(num1,num2); // num1 num2 传递给函数 为函数的实参
printf ("两个数的差除以二者乘积的结果是 %.2f \n",temp);
}
system("PAUSE");
return 0;

}

double fun (double x, double y)
{
double temp;
temp = (x - y)/(x * y);
return temp; // 返回值给调用函数程序
}

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

9. 编写一个程序,要求用户输入下限整数和一个上限整数,然后依次计算从下限到上限的每一个整数的平方的加和,最后显示结果。程序将不断提示用户输入下限整数和上限整数并显示出答案,直到用户输入的上限整数等于或小于下限整数为止。程序运行的结果示例应该如下所示:

Enter lower and upper integer limits:5 9
the sums of the squares from 25 to 81 is 255
Enter next set of limits: 3 25
The sums of the squares from 9 to 625 is 5520
Enter next set of limits: 5 5
Done

解:

#include <stdio.h>
#define SIZE 8
int main (void)
{
int i[SIZE];
int index;

printf ("please input 8 number : \n");
for (index = 0; index < SIZE; index++)
{
printf ("the answer is :");
scanf ("%s",&i[index]);
}

for (index = SIZE-1; index >= 0; index--)
printf ("%d",i[index]);
system("\nPAUSE");
return 0;
}

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

10. 编写一个程序把 8 个整数读入一个数组中,然后以相反的顺序打印它们。

解:.

#include <stdio.h>
int main (void)
{
int lower,uppwer;
int index,num;

num = 0; /* 记得初始化总和的值 */
printf ("Enter lower and upper integer limits : ");
scanf ("%d %d",&lower,&uppwer);
while (lower < uppwer)
{
for (index = lower; index <= uppwer; index++)
num += index * index;
printf ("the sums of the square from %d to %d is %d \n",
lower*lower,uppwer*uppwer,num);

num = 0; /* 完成一次总和计数的循环后,该变量记得清0 */
printf ("The next set of limits :");
scanf ("%d %d",&lower,&uppwer);
}
printf ("Done");
return 0;
}

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

11. 考虑这两个无限序列:

1.0 + 1.0/2.0 + 1.0/3.0 +1.0/4.0 + ……

1.0 – 1.0/2.0 + 1.0/3.0 – 1.0/4.0 +……

编写一个程序来计算这两个序列不断变化的总和,直到达到某个次数。让用户交互地输入这个次数。看看在20次、100次和500次之后的总和。是否每个序列都看上去要收敛于某个值?提示:奇数个-1相乘的值为-1,而偶数个-1相乘的值为1。

解:

#include<stdio.h>
#define NUM 1.0
int main(void)
{
int time;
double a_all=0,b_all=0;
int index,list;
int temp=1;
printf("Get name \"1.0 + 1.0/2.0 + 1.0/3.0 + 1.0/4.0 +...\" is A\n");
printf("Get name \"1.0 - 1.0/2.0 + 1.0/3.0 -1.0/4.0 +...\" is B\n");
printf("Your want test how many times: ");
scanf("%d",&time);
for(index=1;index<=time;index++){
a_all+=NUM/index;
}
for(list=1;list<=time;list++){
b_all+=NUM/list*temp;
temp*=-1;
}
printf("A is %.3f, B is %.3f",a_all,b_all);
system("PAUSE");
return 0;
}

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

12. 编写一个程序,创建一个 8 个元素的 int 数组,并且把元素分别设置为 2 的 8次 幂,然后打印出他们的值。使用 for 循环来设置值,为了变化,使用 do while 循环来显示这些值

#include <stdio.h>
#define SIZE 8
int main (void)
{
int arr[SIZE];
int num,index;

for (index = 0; index < SIZE; index++)
{
num *= 2;
arr[index] = num;
}
index = 0;
do
{
printf ("%4d \n",arr[index]);
index++;
} while (index < SIZE);
system("\nPAUSE");
return 0;

}
/* 需要注意的是,for 循环时 设计循环 8次, 但赋值给数组元素时,不需要用该次数做为赋值的参照 */
------------------------------------------------------------------------------------------

13. 编写一个程序,创建两个 8 个元素的 double 数组,使用一个循环来让用户键入第一个数组的 8 个元素的值。程序把第二个数组的元素设置为第一个数组累积和。例如,第二个数组的第 4 个元素应该等于第一个数组的前 4个元素的和,第二个数组的第 5 个元素应该等于第一个数组的前 5个元素的和 (使用嵌套循环可以做到这一点。不过利用第二个数组的第 5 个元素等于第二个数组的第 4 个元素加上第一个数组的第 5 个元素这一事实,可以避免嵌套而只使用单个循环来完成这个任务。)最后,使用一个循环来显示两个数组中的内容,第一个数组在一行显示,而第二个数组中的每个元素在第一个数组的对应元素之下进行显示。

解;

#include <stdio.h>
#define SIZE 8
int main (void)
{
double arr[SIZE],arr1[SIZE];
double a_num, b_num;
int index,row;

printf (" Please iuput 8 number : "); /* 用户 iput 模块 */
for (index = 0; index < SIZE; index++)
{
printf ("the answer is :");
scanf ("%lf",&arr[index]); /* 将iput的每个值对应赋给给数组中的元素 */
} /* 注意 doubel 对应的说明符为 %lf */

for (row =0; row < SIZE; row++) /* arr[]和arr1[]相互赋值 */
{
a_num += arr[row]; /* 将 arr [] 中元素的值 赋给 a_num */
arr1[row] = a_num; /* 再用 a_num 赋值给 arr1[] */
}

for (row =0; row < SIZE; row++) /* 逐个显示 arr[]中的元素 */
printf ("%8g",arr[row]); /* %g 说明符为自动匹配类型的 */
printf ("\n"); /* 换行 输出 arr1[]数组中的元素 */
for (row =0; row < SIZE; row++)
printf ("%8g",arr1[row]);
printf ("\n");

system("PAUSE");
return 0;
}

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

14. 编写一个程序读入一行输入,然后反向打印该行。你可把输入存储在一个 char 数组中;假定该行不超过 255 个字符。回忆一下,你可以使用具有 %c 说明符的 scanf()从输入中一次读入一个字符,而且当你按下回车键时会产生换行字符 (\n)。

解:

#include <stdio.h>
#define SIZE 255
int main (void)
{
char temp,ch[SIZE];
int index,row;

printf ("please iput your word :");
scanf ("%s",ch);

row = strlen(ch);
for (index = row; index >= 0; index--) /* 如果打印时要对齐的话 index=row-1 */
printf ("%c",ch[index]); /* 反向打印时是逐个字母打出,所以说明符用 %c */
system("PAUSE");
return 0;
}

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

15. Daphne 以 10% 的单利息投资了 100 美元(也就是说,每年投资赢得的利息等于原始投资的 10%)。 Deirdre 则以每年 5% 的复合利息投资了 100 美元(也就是说,利息是当前结余的 5%其中包括以前的利息)。编写一个程序,计算需要多少年 Deirdre 的投资额才会超过 Daphne,并且显示出那时两个人的投资额 。

解:

#include <stdio.h>
#define NUM 100.0
#define SIG 0.1
#define DOU 0.05
int main (void)
{
double dap = NUM;
double dei = NUM;
int list = 0;

do{

dap += NUM * SIG;
dei +=dei * DOU;
list++;
}while (dap > dei);
printf ("%d 年 Daphne 投资有 %.2lf Deirdre 投资有 %.2lf",list,dap,dei);
system("PAUSE");
return 0;

}

------------------------------------------------------------------------------------------
16. Chukie Lucky 赢了 100 万美元,他把它存入一个每年赢得 8% 的帐户。在每年的最后一天, Chukie 取出 10 万美元。编写一个程序,计算需要多少年 chukie 就会清空他的帐户 。

解:

#include <stdio.h>
#define LI 0.08
#define MON 10.0
#define BASE 100.0
int main (void)
{
double money = BASE;
int age = 0;

while (money > 0)
{
money = money*(1+LI) - MON;
age++;
printf("%2d age: %f\n",age,money);
}
printf ("\n %d 年后 Chuckie 的帐户被清空",age);
system("PAUSE");
return 0;
}
-----------------------------------------------------------------------------------------

C Primer Plus(第五版)6的更多相关文章

  1. C Primer Plus(第五版)1

    这是C Primer Plus(第五版)的第一章,上传上来主要是方便我进行做笔记,写注释,还有我会删掉一些“废话”等. 1.1 C语言的起源 贝尔实验室的 Dennis Ritchie 在1972年开 ...

  2. 推荐《C Primer Plus(第五版)中文版》【worldsing笔记】

      老外写的C书,看了你会有一种哇塞的感觉,这里提供PDF扫描版的下在,包含数内的例程,请大家支持原版!! C Primer Plus(第五版)中文版.pdf  下载地址:http://pan.bai ...

  3. Primer C++第五版 读书笔记(一)

    Primer C++第五版 读书笔记(一) (如有侵权请通知本人,将第一时间删文) 1.1-2.2 章节 关于C++变量初始化: 初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义 ...

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

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

  5. 《C++Primer》第五版习题答案--第三章【学习笔记】

    [C++Primer]第五版[学习笔记]习题解答第三章 ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/10 第三章:字符串,向量和数组 ...

  6. 《C++Primer》第五版习题解答--第四章【学习笔记】

    [C++Primer]第五版习题解答--第四章[学习笔记] ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/11 第四章:表达式 练习4. ...

  7. 《C++Primer》第五版习题答案--第五章【学习笔记】

    <C++Primer>第五版习题答案--第五章[学习笔记] ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/15 第五章:语句 ...

  8. 《C++Primer》第五版习题答案--第六章【学习笔记】

    <C++Primer>第五版习题答案--第六章[学习笔记] ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/16 第六章:函数 ...

  9. C++primer(第五版)Sales_item.h头文件

    C++primer(第五版)1.51练习章节需要有一个Sales_item类,但是给的网站找不到,直接复制下面就好咯: #ifndef SALESITEM_H #define SALESITEM_H ...

  10. 《C++Primer》第五版习题答案--第一章【学习笔记】

    C++Primer第五版习题解答---第一章 ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2022/1/7 第一章:开始 练习1.3 #includ ...

随机推荐

  1. angular.element方法汇总以及AngularJS 动态添加元素和删除元素

    addClass()-为每个匹配的元素添加指定的样式类名after()-在匹配元素集合中的每个元素后面插入参数所指定的内容,作为其兄弟节点append()-在每个匹配元素里面的末尾处插入参数内容att ...

  2. Mozilla Brick:一个Web组件Polyfill库

    Web组件是一个W3C规范,它旨在使Web开发人员能够定义具有非常丰富的视觉效果和高可交互性且易于组合的小组件.Brick库提供了新的自定义HTML标签,从而抽象了用户常用接口模式.在浏览器本身支持类 ...

  3. Android中用双缓存技术,加载网络图片

    最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源 ...

  4. mac安装IE浏览器

    1.首先得下载一个WineBottler for mac. 2.下载完毕之后,打开dmg文件后将WineBottler Combo里面的Wine和WineBottler这两个程序拖拉进应用程序. 3. ...

  5. SET Transaction Isolation Level Read语法的四种情况

    转自:http://www.cnblogs.com/qanholas/archive/2012/01/04/2312152.html 存储过程:SET Transaction Isolation Le ...

  6. 移动端H5页面的设计稿尺寸大小规范-转载自http://www.chinaz.com/design/2015/1103/465670.shtml

    机屏幕尺寸,设计稿应该按照哪一个尺寸作为标准尺寸.现在已经有2K分辨率的手机屏幕了,设计稿是不是也要把宽高跟着最大分辨率来设计.显然不是. 请注意:(以下所有讨论内容和规范均将viewport设定为c ...

  7. [oracle] ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法

    ORACLE 32位数据库正常安装,sqlplus 正常连接数据库但是PL/SQL developer 64位却报出这个错误. 第一反应是缺少32位客户端.下载安装,配置完成后如图所示: 还是报这个错 ...

  8. [原]在Fedora中编译Libevent测试实例

    在我的昨天的博文<[原]我在Windows环境下的首个Libevent测试实例>中介绍了在Windows环境下如何编译一个echo server例子.今天我又试了一下在Linux环境中编译 ...

  9. 【python】浅谈包

    python中的包可以理解为模块的集合.每个包也既可以为单包也可以有多个小包组成. Python中的package定义很简单,其层次结构与目录的层次结构相同,但是每个package必须包含一个__in ...

  10. isa class 帮助确定对象或变量的数据类型

    isa class 帮助确定对象或变量的数据类型