前言:
现时朋友们对“scanf循环读入”都比较关怀,朋友们都需要了解一些“scanf循环读入”的相关内容。那么小编在网上汇集了一些对于“scanf循环读入””的相关内容,希望同学们能喜欢,咱们快快来学习一下吧!前言:
数组(array) 是按顺序储存的一系列类型相同的值, 如10个char类型的字符或15个int类型的值。 整个数组有一个数组名, 通过整数下标访问数组中单独的项或元素(element) 。 例如, 以下声明:float debts[20];声明debts是一个内含20个元素的数组, 每个元素都可以储存float类型的值。 数组的第1个元素是debts[0], 第2个元素是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个int类型整数的数组 */
char actors[26]; /* 可储存26个字符的数组 */
long big[500]; /* 可储存500个long类型整数的数组 */
用于识别数组元素的数字被称为下标(subscript) 、 索引(indice) 或偏移量(offset) 。 下标必须是整数, 而且要从0开始计数。 数组的元素被依次储存在内存中相邻的位置。
一、在for循环中使用数组
// scores_in.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);
return 0;
}
下面是程序的输出:
Enter 10 golf scores:
99 95 109 105 100
96 98 93 99 97 98
The scores read in are as follows:
99 95 109 105 100 96 98 93 99 97
Sum of scores = 991, average = 99.10
That's a handicap of 27.
程序运行没问题, 我们来仔细分析一下。 首先, 注意程序示例虽然打印了11个数字, 但是只读入了10个数字, 因为循环只读了10个值。 由于scanf()会跳过空白字符, 所以可以在一行输入10个数字, 也可以每行只输入一个数字, 或者像本例这样混合使用空格和换行符隔开每个数字(因为输入是缓冲的, 只有当用户键入Enter键后数字才会被发送给程序) 。该程序示例演示了一些较好的编程风格。 第一, 用#define 指令创建的明示常量(SIZE) 来指定数组的大小。 这样就可以在定义数组和设置循环边界时使用该明示常量。 如果以后要扩展程序处理20个分数, 只需简单地把SIZE重新定义为20即可, 不用逐一修改程序中使用了数组大小的每一处。
第二, 下面的代码可以很方便地处理一个大小为SIZE的数组:for (index = 0; index < SIZE; index++)
设置正确的数组边界很重要。 第1个元素的下标是0, 因此循环开始时把index设置为0。 因为从0开始编号, 所以数组中最后一个元素的下标是SIZE -1。 也就是说, 第10个元素是score[9]。 通过测试条件index < SIZE来控制循环中使用的最后一个index的值是SIZE - 1。第三, 程序能重复显示刚读入的数据。 这是很好的编程习惯, 有助于确保程序处理的数据与期望相符。最后, 注意该程序使用了3个独立的for循环。
这是否必要? 是否可以将其合并成一个循环? 当然可以, 读者可以动手试试, 合并后的程序显得更加紧凑。 但是, 调整时要注意遵循模块化(modularity) 的原则。 模块化隐含的思想是: 应该把程序划分为一些独立的单元, 每个单元执行一个任务。 这样做提高了程序的可读性。 也许更重要的是, 模块化使程序的不同部分彼此独立, 方便后续更新或修改程序。 在掌握如何使用函数后, 可以把每个执行任务的单元放进函数中, 提高程序的模块化。
二、分支和跳转
(1)if语句
我们从一个有if语句的简单示例开始学习, 请看程序清单7.1。 该程序读取一列数据, 每个数据都表示每日的最低温度(℃) , 然后打印统计的总天数和最低温度在0℃以下的天数占总天数的百分比。 程序中的循环通过scanf()读入温度值。 while循环每迭代一次, 就递增计数器增加天数, 其中的if语句负责判断0℃以下的温度并单独统计相应的天数。
程序 colddays.c程序
// colddays.c -- 找出0℃以下的天数占总天数的百分比
#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");
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()在读到非数字字符时会返回0。 temperature的类型是float而不是int, 这样程序既可以接受-2.5这样的值, 也可以接受8这样的值。while循环中的新语句如下:
if (temperature < FREEZING)
cold_days++;
if 语句指示计算机, 如果刚读取的值(remperature) 小于 0, 就把cold_days 递增 1; 如果temperature不小于0, 就跳过cold_days++;语句, while循环继续读取下一个温度值。接着, 该程序又使用了两次if语句控制程序的输出。 如果有数据, 就打印结果; 如果没有数据, 就打印一条消息(稍后将介绍一种更好的方法来处理这种情况) 。
为避免整数除法, 该程序示例把计算后的百分比强制转换为 float类型。 其实, 也不必使用强制类型转换, 因为在表达式100.0 * cold_days /all_days中, 将首先对表达式100.0 * cold_days求值, 由于C的自动转换类型规则, 乘积会被强制转换成浮点数。 但是, 使用强制类型转换可以明确表达转换类型的意图, 保护程序免受不同版本编译器的影响。 if语句被称为分支语句(branching statement) 或选择语句(selection statement) , 因为它相当于一个交叉点, 程序要在两条分支中选择一条执行。 if语句的通用形式如下:
if ( expression )
statement
如果对expression求值为真(非0) , 则执行statement; 否则, 跳过statement。 与while循环一样, statement可以是一条简单语句或复合语句。 if语句的结构和while语句很相似, 它们的主要区别是: 如果满足条件可执行的话, if语句只能测试和执行一次, 而while语句可以测试和执行多次通常, expression 是关系表达式, 即比较两个量的大小(如, 表达式 x >y 或 c == 6) 。 如果expression为真(即x大于y, 或c == 6) , 则执行statement。 否则, 忽略statement。 概括地说, 可以使用任意表达式, 表达式的值为0则为假。
statement部分可以是一条简单语句, 如本例所示, 或者是一条用花括号括起来的复合语句(或块) :
if (score > big)
printf("Jackpot!\n"); // 简单语句
if (joe > ron)
{
joecash++;
printf("You lose, Ron.\n");
}
注意, 即使if语句由复合语句构成, 整个if语句仍被视为一条语句。
(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为真(非0) , 则执行statement1; 如果expression为假或0, 则执行else后面的statement2。 statement1和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语句用于选择是否执行一个行为, 而else if语句用于在两个行为之间选择。
(3)介绍getchar()和putchar()
到目前为止, 学过的大多数程序示例都要求输入数值。 接下来, 我们看看输入字符的示例。 相信读者已经熟悉了如何用 scanf()和 printf()根据%c 转换说明读写字符, 我们马上要讲解的示例中要用到一对字符输入/输出函数: getchar()和putchar()。getchar()函数不带任何参数, 它从输入队列中返回下一个字符。 例如,下面的语句读取下一个字符输入, 并把该字符的值赋给变量ch:ch = getchar();
该语句与下面的语句效果相同:
scanf("%c", &ch);
putchar()函数打印它的参数。 例如, 下面的语句把之前赋给ch的值作为
字符打印出来:
putchar(ch);
该语句与下面的语句效果相同:
printf("%c", ch);
由于这些函数只处理字符, 所以它们比更通用的scanf()和printf()函数更快、 更简洁。 而且, 注意 getchar()和 putchar()不需要转换说明, 因为它们只处理字符。 这两个函数通常定义在 stdio.h头文件中(而且, 它们通常是预处理宏, 而不是真正的函数, 第16章会讨论类似函数的宏) 。接下来, 我们编写一个程序来说明这两个函数是如何工作的。 该程序把一行输入重新打印出来, 但是每个非空格都被替换成原字符在ASCII序列中的下一个字符, 空格不变。 这一过程可描述为“如果字符是空白, 原样打印; 否则, 打印原字符在ASCII序列中的下一个字符”。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); // 打印换行符
return 0;
}
( 如果编译器警告因转换可能导致数据丢失, 不用担心。讲到EOF时再解释。 )
下面是该程序的输入示例:
CALL ME HAL.
DBMM NF IBM/
把程序中的循环和该例中的循环作比较。 前者使用scanf()返回的状态值判断是否结束循环, 而后者使用输入项的值来判断是否结束循环。 这使得两程序所用的循环结构略有不同: 程序中在循环前面有一条“读取语句”, 程序中在每次迭代的末尾有一条“读取语句”。 不过, C的语法比较灵活, 读者也可以模仿程序, 把读取和测试合并成一个表达式。 也就是说, 可以把这种形式的循环:
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的值与换行符作比较。 表达式ch =getchar()两侧的圆括号使之成为!=运算符的左侧运算对象。 要对该表达式求值, 必须先调用getchar()函数, 然后把该函数的返回值赋给 ch。 因为赋值表达式的值是赋值运算符左侧运算对象的值, 所以 ch = getchar()的值就是 ch的新值, 因此, 读取ch的值后, 测试条件相当于是ch != '\n'(即, ch不是换行符) 。这种独特的写法在C编程中很常见, 应该多熟悉它。 还要记住合理使用圆括号组合子表达式。 上面例子中的圆括号都必不可少。 假设省略ch =getchar()两侧的圆括号:
while (ch = getchar() != '\n')
!=运算符的优先级比=高, 所以先对表达式getchar() != '\n'求值。 由于这是关系表达式, 所以其值不是1就是0(真或假) 。 然后, 把该值赋给ch。 省略圆括号意味着赋给ch的值是0或1, 而不是 getchar()的返回值。 这不是我们的初衷。下面的语句:
putchar(ch + 1); /* 改变其他字符 */
再次演示了字符实际上是作为整数储存的。 为方便计算, 表达式ch + 1中的ch被转换成int类型, 然后int类型的计算结果被传递给接受一个int类型参数的putchar(), 该函数只根据最后一个字节确定显示哪个字符。
标签: #scanf循环读入