第六章 C控制语句:循环
6.1 再探while循环
summing.c程序:根据用户从键盘输入的整数进行求和。程序利用了scanf()的返回值来结束循环。
#include <stdio.h>
int main(void)
{
long num;
long sum = 0L; //把sum初始化为0
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 enter next integer (q to quit): ");
status = scanf("%ld",&num);
}
printf("Those integers sum to %ld.\n", sum);
return 0;
}
运行结果:
6.1.1 程序注释
要让程序正常运行,每次循环都要获取num的一个新值,并重置status。程序利用scanf()的两个不同的特性来完成。首先,使用scanf()读取num的一个新值;然后,检查scanf()的返回值判断是否成功获取值。
scanf()返回成功读取项的数量。如果scanf()成功读取一个整数,就把该数存入num并返回1,随后返回值将被赋给status。这样做同时更新了num和status的值,while循环进入下一次迭代。如果用户输入的不是数字(如, q),scanf()会读取失败并返回0。此时,status的值就是0,循环结束。因为输入的字符q不是数字,所以它会被放回输入队列中(实际上,不仅仅是 q,任何非数值的数据都会导致循环终止,但是提示用户输入q退出程序比提示用户输入一个非数字字符要简单)。
如果 scanf()在转换值之前出了问题(例如,检测到文件结尾或遇到硬件问题),会返回一个特殊值EOF(其值通常被定义为-1)。这个值也会引起循环终止。
该程序的结构。总结如下:
把sum初始化为0
提示用户输入数据
读取用户输入的数据
当输入的数据为整数时,
输入添加给sum,
提示用户进行输入,
然后读取下一个输入
输入完成后,打印sum的值
这叫作伪代码,是一种用简单的句子表示程序思路的方法,有助于设计程序的逻辑。确定程序的逻辑无误之后,再把伪代码翻译成实际的编程代码。使用伪代码的好处之一是,可以把注意力集中在程序的组织和逻辑上,不用在设计程序时还要分心如何用编程语言来表达自己的想法。
总之,因为while循环是入口条件循环,程序在进入循环体之前必须获取输入的数据并检查status的值,所以在 while 前面要有一个 scanf()。要让循环继续执行,在循环内需要一个读取数据的语句,这样程序才能获取下一个status的值,所以在while循环末尾还要有一个scanf(),它为下一次迭代做好了准备。可以把下面的伪代码作为while循环的标准格式:
获得第1个用于测试的值
当测试为真时
处理值
获取下一个值
6.1.2 C风格读取循环
根据伪代码的设计思路,summing.c程序,下面的代码:
status = scanf("%ld", &num);
while (status == 1)
{
/* 循环行为 */
status = scanf("%ld", &num);
}
可以用这些代码替换:
while (scanf("%ld", &num) == 1)
{
/循环行为/
}
第二种形式同时使用scanf()的两种不同的特性。首先,如果函数调用成功,scanf()会把一个值存入num。然后,利用scanf()的返回值(0或1,不是num的值)控制while循环。因为每次迭代都会判断循环的条件,所以每次迭代都要调用scanf()读取新的num值来做判断。换句话说,C的语法特性让你可以用下面的精简版本替换标准版本:
当获取值和判断值都成功
处理该值
6.2 while语句
while循环的通用形式如下:
while ( expression )
statement
statement部分可以是以分号结尾的简单语句,也可以是用花括号括起来的复合语句。
6.2.1 终止while循环
while循环有一点非常重要:在构建while循环时,必须让测试表达式的值有变化,表达式最终要为假。否则,循环就不会终止。
6.2.2 何时终止循环
要明确一点:只有在对测试条件求值时,才决定是终止还是继续循环。
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;
}
运行结果:
程序分析:
在第2次循环时,变量n在第10行首次获得值7。但是,此时程序并未退出,它结束本次循环(第11行),并在对第7行的测试条件求值时才退出循环(变量n在第1次判断时为5,第2次判断时为6)。
6.2.3 while:入口条件循环
while循环是使用入口条件的有条件循环。所谓“有条件”指的是语句部分的执行取决于测试表达式描述的条件,如(index < 5)。该表达式是一个入口条件(entry condition),因为必须满足条件才能进入循环体。
6.2.4 语法要点
使用while时,要牢记一点:只有在测试条件后面的单独语句(简单语句或复合语句)才是循环部分。
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;
}
运行结果:屏幕上会一直输出以上内容,除非强行关闭这个程序。
程序说明:
只有直接跟在测试条件后面的一条语句是循环的一部分。变量n的值不会改变,条件n < 3一直为真。该循环会一直打印n is 0,除非强行关闭程序。这是一个无限循环的例子,没有外部干涉就不会退出。
即使while语句本身使用复合语句,在语句构成上,它也是一条单独的语句。该语句从while开始执行,到第1个分号结束。在使用了复合语句的情况下,到右花括号结束。
while2.c 程序:注意分号的位置
#include <stdio.h>
int main(void)
{
int n = 0;
while (n++ < 3); /* 第7行 */
printf("n is %d\n", n); /* 第8![在这里插入图片描述](https://img-blog.csdnimg.cn/2020042822483464.png)
行 */
printf("That's all this program does\n");
return 0;
}
运行结果:
程序说明:
该程序中第7行的测试条件后面直接跟着一个分号,循环在此进入下一轮迭代,因为单独一个分号被视为一条语句。虽然n的值在每次循环时都递增1,但是第8行的语句不是循环的一部分,因此只会打印一次循环结束后的n值。
在该例中,测试条件后面的单独分号是空语句,它什么也不做。有时,会故意使用带空语句的while语句,因为所有的任务都在测试条件中完成了,不需要在循环体中做什么。
例如,假设你想跳过输入到第1个非空白字符或数字,可以这样写:
while (scanf("%d", &num) == 1)
; /* 跳过整数输入 */
只要scanf()读取一个整数,就会返回1,循环继续执行。
6.3 用关系运算符和表达式比较大小
注意:比较浮点数时,尽量只使用<和>。因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等。例如,3乘以1/3的积是1.0。如果用把1/3表示成小数点后面6位数字,乘积则是.999999,不等于1。
使用fabs()函数(声明在math.h头文件中)可以方便地比较浮点数,该函数返回一个浮点值的绝对值(即,没有代数符号的值)。
cmpflt.c程序:浮点数比较
#include <math.h>
#include <stdio.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");
return 0;
}
运行结果:
程序说明:
循环会一直提示用户继续输入,除非用户输入的值与正确值之间相差0.0001。
6.3.1 什么是真
对C而言,表达式为真的值是1,表达式为假的值是0。
t_and_f.c程序:打印两个关系表达式的值,一个为真,一个为假
/* t_and_f.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);
return 0;
}
运行结果:
程序说明:
把两个关系表达式的值分别赋给两个变量,即把表达式为真的值赋给true_val,表达式为假的值赋给false_val。
6.3.2 其他真值
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);
return 0;
}
运行结果:
程序说明:
执行第1个循环时,n分别是3、2、1,当n等于0时,第1个循环结束;执行第2个循环时,n分别是-3、-2和-1,当n等于0时,第2个循环结束。一般而言,所有的非零值都视为真,只有0被视为假。
许多C程序员都会很好地利用测试条件的这一特性。例如,用while(goats)替换while (goats !=0),因为表达式goats != 0和goats都只有在goats的值为0时才为0或假。
6.3.3 真值的问题
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 = sum + num;
printf("Please enter next integer (q to quit): ");
status = scanf("%ld", &num);
}
printf("Those integers sum to %ld.\n", sum);
return 0;
}
运行程序:屏幕上会一直显示最后的提示内容,除非强行关闭程序。
程序说明:
把status == 1替换成status = 1。后者是一个赋值表达式语句,所以 status 的值为 1。while (status = 1)实际上相当于while (1),也就是说,循环不会退出。虽然用户输入q,status被设置为0,但是循环的测试条件把status又重置为1,进入了下一次迭代。
读者可能不太理解,程序的循环一直运行着,用户在输入q后完全没机会继续输入。如果scanf()读取指定形式的输入失败,就把无法读取的输入留在输入队列中,供下次读取。当scanf()把q作为整数读取时失败了,它把 q留下。在下次循环时,scanf()从上次读取失败的地方(q)开始读取,scanf()把q作为整数读取,又失败了。这是一个无限失败的循环。
赋值运算符把一个值赋给它左侧的变量;而关系相等运算符检查它左侧和右侧的值是否相等,不会改变左侧变量的值(如果左侧是一个变量)。
C语言不允许给常量赋值,编译器会把赋值运算符的这种用法作为语法错误标记出来。许多经验丰富的程序员在构建比较是否相等的表达式时,都习惯把常量放在左侧。
6.3.4 新的_Bool类型
表示真或假的变量被称为布尔变量,所以_Bool是C语言中布尔变量的类型名。_Bool类型的变量只能储存1(真)或0(假)。如果把其他非零数值赋给_Bool类型的变量,该变量会被设置为1。这反映了C把所有的非零值都视为真。
boolean.c程序:使用_Bool类型的变量 variable
#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);
return 0;
}
运行结果:
程序说明:
程序中把比较的结果赋值给_Bool类型的变量input_is_good:input_is_good = (scanf("%ld", &num) == 1);这样做没问题,因为==运算符返回的值不是1就是0。
C99提供了stdbool.h头文件,该头文件让bool成为_Bool的别名,而且还把true和false分别定义为1和0的符号常量。
6.3.5 优先级和关系运算符
关系运算符的优先级比算术运算符(包括+和-)低,比赋值运算符高。例如,x > y + 2和x > (y+ 2)相同,x = y > 2和x = (y > 2)相同。
关系运算符之间有两种不同的优先级。
高优先级组: < <= > >=
低优先级组: == !=
6.4 不确定循环和计数循环
不确定循环,指在测试表达式为假之前,预先不知道要执行多少次循环。
计数循环,这类循环在执行循环之前就知道要重复执行多少次。
sweetie1.c程序:一个计数循环
#include <stdio.h>
int main(void)
{
const int NUMBER = 22;
int count = 1; // 初始化
while (count <= NUMBER) // 测试
{
printf("Be my Valentine!\n"); // 行为
count++; // 更新计数
}
return 0;
}
运行结果:
程序说明:
在创建一个重复执行固定次数的循环中涉及了3个行为:
1.必须初始化计数器;
2.计数器与有限的值作比较;
3.每次循环时递增计数器。
6.5 for循环
for循环把初始化、测试和更新组合在一处。
sweetie2.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");
return 0;
}
运行结果:
程序说明:
for后面的圆括号中有3个表达式,分别用两个分号隔开。第1个表达式是初始化,只会在for循环开始时执行- -次。第2个表达式是测试条件,在执行循环之前对表达式求值。如果表达式为假,循环结束。第3个表达式执行更新,在每次循环结束时求值。程序这个表达式递增count的值, 更新计数。完整的for语句还包括后面的简单语句或复合语句。
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);
return 0;
}
运行程序:
程序说明:打印整数1~6及其对应的立方
6.5.1 利用for的灵活性
灵活性源于如何使用for循环中的3个表达式。以前面程序示例中的for循环为例,第1个表达式给计数器赋初值,第2个表达式表示计数器的范围,第3个表达式递增计数器。除此之外,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");
return 0;
}
运行结果:
2.可以让计数器递增x
for_13s.c 程序:
#include <stdio.h>
int main(void)
{
int n; // 从2开始,每次递增13
for (n = 2; n < 60; n = n + 13)
printf("%d \n", n);
return 0;
}
运行结果:
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);
return 0;
}
运行结果:
程序说明:该程序能正常运行是因为字符在内部是以整数形式储存的,因此该循环实际上仍是用整数来计数。
4.除了测试迭代次数外,还可以测试其他条件。
在for_cube程序中,可以把for (num= 1; num<= 6; num++)替换成:for (num= 1; numnumnum <= 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);
return 0;
}
运行结果:
程序说明:每次循环都把debt乘以1.1,即debt的值每次都增加10%
6.第3个表达式可以使用任意合法的表达式。无论是什么表达式,每次迭代都会更新该表达式的值。
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);
return 0;
}
运行结果:
程序说明:测试涉及y,而不是x。for循环中的3个表达式可以是不同的变量。
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);
return 0;
}
运行结果:
程序说明:该循环保持n的值为3。变量ans开始的值为2,然后递增到6和18,最终是54。
省略第2个表达式被视为真,所以下面的循环会一直运行:
for( ; ; )
printf(“I want some action\n”);
8.第1个表达式不一定是给变量赋初值,也可以使用printf()。记住,在执行循环的其他部分之前,只对第1个表达式求值一次或执行一次。
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");
return 0;
}
运行结果:该程序打印第1行的句子一次,在用户输入6之前不断接受数字
9.循环体中的行为可以改变循环头的表达式
for (n = 1; n < 10000; n = n + delta)
如果程序经过几次迭代后发现delta太小或太大,循环中的if语句可以改变delta的大小。在交互式程序中,用户可以在循环运行时才改变 delta 的值。这样做也有危险的一面,例如,把delta设置为0就没用了。
6.6 其他赋值运算符:+=、-=、*=、/=、%=
其他赋值运算符都用于更新变量,其用法都是左侧是一个变量名,右侧是一个表达式。例如:
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 相同
6.7 逗号运算符
逗号运算符扩展了for循环的灵活性,以便在循环头中包含更多的表达式。
postage.c程序:打印一类邮件资费
#include <stdio.h>
int main(void)
{
const int FIRST_OZ = 46; //2013邮资
const int NEXT_OZ = 20; //2013邮资
int ounces, cost;
printf(" ounces cost\n");
for (ounces = 1, cost = FIRST_OZ; ounces <= 4; ounces++, cost += NEXT_OZ)
printf("%5d $%4.2f\n", ounces, cost / 100.0);
return 0;
}
运行结果:
程序说明:
初始化表达式中的逗号使ounces和cost都进行了初始化,更新表达式中的逗号使每次迭代ounces递增1、cost递增20 (NEXT_ Z的值是20)。
逗号运算符有两个其他性质:
首先,被逗号分隔的表达式从左往右求值。换言之,逗号是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生。
其次,整个逗号表达式的值是石侧项的值。例如,下面语句x=(y=3, (z=++y+ 2) + 5);的效果是:先把3赋给y,递增y为4, 然后把4加2之和(6)赋给z,接着加上5,最后把结果11赋给x。
逗号运算符:把两个表达式连接成一个表达式,并保证最左边的表达式最先求值。逗号运算符通常在for循环头的表达式中用于包含更多的信息。整个逗号表达式的值是逗号右侧表达式的值。
6.7.1 当 Zeno 遇到 for 循环
我们看看 for 循环和逗号运算符如何解决古老的悖论。希腊哲学家 Zeno 曾经提出箭永远不会达到它的目标。首先,他认为箭要到达目标距离的一半,然后再达到剩余距离的一半,然后继续到达剩余距离的一半,这样就无穷无尽。Zeno认为箭的飞行过程有无数个部分,所以要花费无数时间才能结束这一过程。
我们采用一种定量的方法,假设箭用1秒钟走完一半的路程,然后用1/2秒走完剩余距离的一半,然后用1/4秒再走完剩余距离的一半,等等。可以用下面的无限序列来表示总时间:1 + 1/2 + 1/4 + 1/8 + 1/16 +…
zeno.c程序:求序列的和
#include <stdio.h>
int main(void)
{
int t_ct; // 项计数
double time, power_of_2;
int limit;
printf("Enter the number of terms you want: ");
scanf("%d", &limit);
for (time = 0, power_of_2 = 1, t_ct = 1; t_ct <= limit; t_ct++, power_of_2 *= 2.0)
{
time += 1.0 / power_of_2;
printf("time = %f when terms = %d.\n", time, t_ct);
}
return 0;
}
运行结果:
程序说明:当项的数目接近无穷时,总和无限接近2.0。
6.8 出口条件循环:do while
入口条件循环:while循环和for循环,即在循环的每次迭代之前检查测试条件,所以有可能根本不执行循环体中的内容。
出口条件循环:do while循环,即在循环的每次迭代之后检查测试条件,这保证了至少执行循环体中的内容一次。
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("please enter the secret code number: ");
scanf("%d", &code_entered);
}while(code_entered != secret_code);
printf("Congratulationa! You are cured!\n");
return 0;
}
运行结果:
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("please enter the secret code number: ");
scanf("%d", &code_entered);
while(code_entered != secret_code)
{
printf("To enter the triskaidekaphobia therapy club,\n");
printf("please enter the secret code number: ");
scanf("%d", &code_entered);
}
printf("Congratulationa! You are cured!\n");
return 0;
}
运行结果:
do while循环的通用形式:
do
statement
while ( expression );
statement可以是一条简单语句或复合语句。注意,do while循环以分号结尾。
6.9 如何选择循环
首先,确定是需要入口条件循环还是出口条件循环。通常,入口条件循环用得比较多,有几个原因。其一,一般原则是在执行循环之前测试条件比较好。其二,测试放在循环的开头,程序的可读性更高。当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更好。
6.10 嵌套循环
嵌套循环指在一个循环内包含另一个循环。嵌套循环常用于按行和列显示数据,也就是说,一个循环处理一行中的所有列,另一个循环处理所有的行。
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++) /* 第11行 */
printf("%c", ch);
printf("\n");
}
return 0;
}
运行结果:
6.10.1 程序分析
第10行为外层循环,第12行为内层循环。外层循环从row为0开始循环,到row为6时结束。因此,外层循环要执行6次,row的值从0变为5。每次迭代要执行的第1条语句是内层的for循环,该循环要执行10次,在同一行打印字符A~J。内层循环一行打印10个字符,外层循环创建6行。
嵌套循环中的内层循环在每次外层循环迭代时都执行完所有的循环。
6.10.2 嵌套变式
通过外层循环控制内层循环,在每次外层循环迭代时内层循环完成不同的任务。
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");
}
return 0;
}
运行程序:
程序说明:
因为每次迭代都要把row的值与‘A’相加,所以ch在每一行都被初始化为不同的字符。然而,测试条件并没有改变,所以每行依然是以F结尾,这使得每一行打印的字符都比上一行少一个。
6.11 数组简介
数组是按顺序储存的一系列类型相同的值。数组的元素被依次储存在内存中相邻的位置
整个数组有一个数组名,通过整数下标访问数组中单独的项或元素。用于识别数组元素的数字被称为下标、索引或偏移量。下标必须是整数,而且要从0开始计数。
例如:float debts[20];
声明debts是一个内含20个元素的数组,每个元素都可以储存float类型的值。数组的第1个元素是debts[0],第2个元素是debts[1],以此类推,直到debts[19]。注意,数组元素的编号从0开始,不是从1开始。可以给每个元素赋float类型的值。例如,debts[5] = 32.54;
这里要注意一个潜在的陷阱:考虑到影响执行的速度,C 编译器不会检查数组的下标是否正确。下面的代码,都不正确:
debts[20] = 88.32; // 该数组元素不存在
debts[33] = 828.12; // 该数组元素不存在
编译器不会查找这样的错误。当运行程序时,这会导致数据被放置在已被其他数据占用的地方,可能会破坏程序的结果甚至导致程序异常中断。
6.11.1 在for循环中使用数组
scores_in.c程序:读取10个高尔夫分数,打印总分、平均分、平均分与标准分的差值
#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);
return 0;
}
运行结果:
程序说明:
首先,注意程序示例虽然打印了11个数字,但是只读入了10个数字,因为循环只读了10个值。由于scanf()会跳过空白字符,所以可以在一行输入10个数字,也可以每行只输入一个数字,或者像本例这样混合使用空格和换行符隔开每个数字(因为输入是缓冲的,只有当用户键入Enter键后数字才会被发送给程序)。
下面的代码可以很方便地处理一个大小为SIZE的数组:
for (index = 0; index < SIZE; index++)
设置正确的数组边界很重要。第1个元素的下标是0,因此循环开始时把index设置为0。因为从0开始编号,所以数组中最后一个元素的下标是SIZE -1。也就是说,第10个元素是score[9]。通过测试条件index < SIZE来控制循环中使用的最后一个index的值是SIZE - 1。
6.12 使用函数返回值的循环示例
该示例有3个主要任务:设计算法、在函数中表示算法并返回计算结果、提供一个测试函数的便利方法。
power.c程序:计算数的整数幂(math.h库提供了一个更强大幂函数pow(),可以使用浮点指数)
#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");
return 0;
}
double power(double n, int p) // 函数定义
{
double pow = 1;
int i;
for (i = 1; i <= p; i++)
pow *= n;
return pow; // 返回pow的值
}
运行结果:
6.12.1 程序分析
main()是一个驱动程序,即被设计用来测试函数的小程序。
power()函数在程序中出现了3次:
首次:double power(double n, int p); // ANSI函数原型
这是power()函数的原型,它声明程序将使用一个名为power()的函数。开头的关键字double表明power()函数返回一个double类型的值。编译器要知道power()函数返回值的类型,才能知道有多少字节的数据,以及如何解释它们。这就是为什么必须声明函数的原因。
第2次出现是:xpow = power(x,exp); // 函数调用
程序调用power(),把两个值传递给它。该函数计算x的exp次幂,并把计算结果返回给主调函数。在主调函数中,返回值将被赋给变量xpow。
第3次出现是:double power(double n, int p) // 函数定义
注意,函数定义的末尾没有分号,而函数原型的末尾有分号。
6.12.2 使用带返回值的函数
声明函数、调用函数、定义函数、使用关键字return,都是定义和使用带返回值函数的基本要素。
问:为什么在定义中说明了power()的返回类型为double,还要单独声明这个函数?
答:编译器在程序中首次遇到power()时,需要知道power()的返回类型。此时,编译器尚未执行到power()的定义,并不知道函数定义中的返回类型是double。因此,必须通过前置声明预先说明函数的返回类型。前置声明告诉编译器,power()定义在别处,其返回类型为double。如果把power()函数的定义置于main()的文件顶部,就可以省略前置声明,因为编译器在执行到main()之前已经知道power()的所有信息。但是,这不是C的标准风格。因为main()通常只提供整个程序的框架,最好把 main()放在所有函数定义的前面。另外,通常把函数放在其他文件中,所以前置声明必不可少。
问:既然在使用函数返回值之前要声明函数,那么为什么在使用scanf()的返回值之前没有声明scanf()?
答:stdio.h 头文件中包含了scanf()、printf()和其他I/O函数的原型。scanf()函数的原型表明,它返回的类型是int。
6.13 关键概念
- 在创建循环时,要特别注意以下3个方面:
注意循环的测试条件要能使循环结束;
确保循环测试中的值在首次使用之前已初始化;
确保循环在每次迭代都更新测试的值。 - 使用函数涉及3个步骤:
通过函数原型声明函数;
在程序中通过函数调用使用函数;
定义函数。 - 函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代的编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所做的;实现部分描述了具体的行为,这正是函数定义所做的。
- C编译器不会检查数组下标值是否有效。