11-混合数值和字符输入
8.5.2 混合数值和字符输入
假设程序要求用 getchar() 处理字符输入,用 scanf() 处理数值输入,这两个函数都能很好地完成任务,但是不能把它们混用。因为 getchar() 读取每个字符,包括空格、制表符和换行符;而 scanf() 在读取数字时则会跳过空格、制表符和换行符。
我们通过程序清单8.5来解释这种情况导致的问题。该程序读入一个字符和两个数字,然后根据输入的两个数字指定的行数和列数打印该字符。
程序清单8.5 showchar1.c 程序
/* showchar1.c -- 有较大 I/O 问题的程序 */
#include <stdio.h>
void display(char cr, int lines, int width);
int main(void)
{
int ch; /* 待打印字符 */
int rows, cols; /* 行数和列数 */
printf("Enter a character and two integers:\n");
while ((ch = getchar()) != '\n')
{
scanf("%d %d", &rows, &cols);
display(ch, rows, cols);
printf("Enter another character and two integers;\n");
printf("Enter a newline to quit.\n");
}
printf("Bye.\n");
return 0;
}
void display(char cr, int lines, int width)
{
int row, col;
for (row = 1; row <= lines; row++)
{
for (col = 1; col <= width; col++)
putchar(cr);
putchar('\n'); /* 结束一行并开始新的一行 */
}
}
注意,该程序以 int 类型读取字符(这样做可以检测 EOF ),但是却以 char 类型把字符传递给 display() 函数。因为 char 比 int 小,一些编译器会给出类型转换的警告。可以忽略这些警告,或者用下面的强制类型转换消除警告:
display((char)ch, rows, cols);
在该程序中, main() 负责获取数据, display() 函数负责打印数据。下面是该程序的一个运行示例,看看有什么问题:
Enter a character and two integers:
c 2 3
ccc
ccc
Enter another character and two integers;
Enter a newline to quit.
Bye.
该程序开始时运行良好。你输入 c 2 3 ,程序打印 c 字符2行3列。然后,程序提示输入第2组数据,还没等你输入数据程序就退出了!这是什么情况?又是换行符在捣乱,这次是输入行中紧跟在3后面的换行符。 scanf() 函数把这个换行符留在输入队列中。和 scanf() 不同, getchar() 不会跳过换行符,所以在进入下一轮迭代时,你还没来得及输入字符,它就读取了换行符,然后将其赋给 ch 。而 ch 是换行符正式终止循环的条件。
要解决这个问题,程序要跳过一轮输入结束与下一轮输入开始之间的所有换行符或空格。另外,如果该程序不在 getchar() 测试时,而在 scanf() 阶段终止程序会更好。修改后的版本如程序清单8.6所示。
程序清单8.6 showchar2.c 程序
/* showchar2.c -- 按指定的行列打印字符 */
#include <stdio.h>
void display(char cr, int lines, int width);
int main(void)
{
int ch; /* 待打印字符*/
int rows, cols; /* 行数和列数 */
printf("Enter a character and two integers:\n");
while ((ch = getchar()) != '\n')
{
if (scanf("%d %d", &rows, &cols) != 2)
break;
display(ch, rows, cols);
while (getchar() != '\n')
continue;
printf("Enter another character and two integers;\n");
printf("Enter a newline to quit.\n");
}
printf("Bye.\n");
return 0;
}
void display(char cr, int lines, int width)
{
int row, col;
for (row = 1; row <= lines; row++)
{
for (col = 1; col <= width; col++)
putchar(cr);
putchar('\n'); /* 结束一行并开始新的一行 */
}
}
while 循环实现了丢弃 scanf() 输入后面所有字符(包括换行符)的功能,为循环的下一轮读取做好了准备。该程序的运行示例如下:
Enter a character and two integers:
c 1 2
cc
Enter another character and two integers;
Enter a newline to quit.
! 3 6
!!!!!!
!!!!!!
!!!!!!
Enter another character and two integers;
Enter a newline to quit.
Bye.
在 if 语句中使用一个 break 语句,可以在 scanf() 的返回值不等于 2 时终止程序,即如果一个或两个输入值不是整数或者遇到文件结尾就终止程序。