目录
一、 单字符I/O:getchar()和putchar()
二、缓冲区
三、结束键盘输入
3.1 文件、流和键盘输入
3.2 文件结尾
四、重定向和文件
4.1 UNIX、Linux和DOS重定向
1.重定向输入
2.重定向输出
3.组合重定向
五、创建更友好的用户界面
5.1 使用缓冲输入
5.2 混合数值和字符输入
六、输入验证
七、菜单浏览
getchar()和 putchar()每次只处理一个字符。
getchar()和 putchar()都不是真正的函数,它们被定义为供预处理器使用的宏。
/* echo.c -- 重复输入 */
#include
int main(void)
{
char ch;
while ((ch = getchar()) != '#')
putchar(ch);
getchar();
getchar();
return 0;
}
运行结果:
hi,gogogo
hi,gogogo
bye#
bye
要解决的问题:
无缓冲(或直接)输入:回显用户输入的字符后立即重复打印该字符,即正在等待的程序可立即使用输入的字符。
缓冲输入:在用户按下Enter键之前不会重复打印刚输入的字符。用户输入的字符被收集并储存在一个被称为缓冲区(buffer)的临时存储区,按下Enter键后,程序才可使用用户输入的字符。
关于缓冲区的应用:
无缓冲区的应用:
某些交互式程序也需要无缓冲输入。例如,在游戏中,你希望按下一个键就执行相应的指令。
缓冲分为两类:完全缓冲I/O和行缓冲I/O。
完全缓冲输入指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是 512 字节和4096字节。
行缓冲I/O指的是在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下Enter键后才刷新缓冲区。
许多IBM PC兼容机的编译器都为支持无缓冲输入提供一系列特殊的函数,其原型都在conio.h头文件中。这些函数包括用于回显无缓冲输入的getche()函数和用于无回显无缓冲输入的getch()函数(回显输入意味着用户输入的字符直接显示在屏幕上,无回显输入意味着击键后对应的字符不显示)。
/* echo.c -- 回显无缓冲输入 */
#include
int main(void)
{
char ch;
while ((ch = getche()) != '#')
putchar(ch);
getchar();
return 0;
}
//或者
/* echo.c -- 回显无缓冲输入 */
#include
#include
int main(void)
{
char ch;
while ((ch = _getche()) != '#')
putchar(ch);
getchar();
return 0;
}
/* echo.c -- 无回显无缓冲输入 */
#include
#include
int main(void)
{
char ch;
while ((ch = _getch()) != '#')
putchar(ch);
getchar();
return 0;
}
在ANSI C中,用setbuf()和setvbuf()函数控制缓冲,但是受限于一些系统的内部设置,这些函
数可能不起作用。总之,ANSI没有提供调用无缓冲输入的标准方式,这意味着是否能进行无缓冲输入取决于计算机系统。
着重理解C把输入和输出设备视为存储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件。stdin流表示键盘输入,stdout流表示屏幕输出。getchar()、putchar()、printf()和scanf()函数都是标准I/O包的成员,处理这两个流。
计算机操作系统要以某种方式判断文件的开始和结束。
方法一:在文件末尾放一个特殊的字符标记文件结尾。这些操作系统可以使用内嵌的Ctrl+Z字符来标记文件结尾。这曾经是操作系统使用的唯一标记,不过现在有一些其他的选择所以现代的文本文件不一定有嵌入的Ctrl+Z,但是如果有,该操作系统会将其视为一个文件结尾标记。
方法二:储存文件大小的信息。如果文件有3000字节,程序在读到3000字节时便达到文件的末尾。用这种方法可以在文件中储存所有的字符,包括Ctrl+Z。
在C语言中,用getchar() 读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写)。scanf()函数检测到文件结尾时也返回EOF。通常,EOF定义在stdio.h文件中:
#define EOF (-1)
如果包含stdio.h文件,并使用EOF符号,就不必担心EOF值不同的问题。这里关键要理解EOF是一个值,标志着检测到文件结尾,并不是在文件中找得到的符号。
/* echo_eof.c -- 重复输入,直到文件结尾 */
#include
int main(void)
{
int ch;
while ((ch = getchar()) != EOF)
putchar(ch);
getchar();
return 0;
}
//#include
//int main(void)
//{
// int ch;
// while ((scanf("%c",&ch)) != EOF)//VC要输入连续三个ctrl+z(处于一行的开头)才算结束
// printf("%c",ch);
//
// getchar();
// return 0;
//}
运行结果:
hahaha
hahaha
^Z
程序可以通过两种方式使用文件。
重定向的一个主要问题与操作系统有关,与C无关。
文本文件(text file)是内含文本的文件,其中储存的数据是我们可识别的字符。内含机器语言指令的文件(如储存可执行程序的文件)不是文本文件。
echo_eof < words
<符号是UNIX和DOS/Windows的重定向运算符。该运算符使words文件与stdin流相关联,把文件中的内容导入echo_eof程序。文件就是现在的I/O设备。
注意:对于UNIX、Linux和Windows命令提示,<两侧的空格是可选的。一些系统,如AmigaDOS在重定向符号和文件名之间不允许有空格。
echo_eof>mywords
>符号是第2个重定向运算符。重定向把stdout从显示设备(即,显示器)赋给mywords文件。如果已经有一个名为mywords的文件,通常会擦除该文件的内容,然后替换新的内容。
制作一份mywords文件的副本,并命名为savewords。只需输入以下命令即可:
echo_eof < mywords > savewords
下面的命令也起作用,因为命令与重定向运算符的顺序无关:
echo_eof > savewords < mywords
注意:在一条命令中,输入文件名和输出文件名不能相同。
在UNIX、Linux或Windows/DOS系统中使用两个重定向运算符(<和>)时,要遵循以下原则。
UNIX、Linux或Windows/DOS 还有>>运算符,该运算符可以把数据添加到现有文件的末尾,而 | 运算符能把一个文件的输出连接到另一个文件的输入。
注释:
重定向是一个命令行概念,因为我们要在命令行输入特殊的符号发出指令。如果不使用命令行环境,也可以使用重定向。
如果用不了重定向,可以用程序直接打开文件。待读取的文件应该与可执行文件位于同一目录。
// file_eof.c --打开一个文件并显示该文件
#include
#include // 为了使用exit()
int main() {
int ch;
FILE * fp;
char fname[50]; // 储存文件名
printf("Enter the name of the file: ");
scanf("%s", fname);
fp = fopen(fname, "r"); // 打开待读取文件
if (fp == NULL) // 如果失败
{
printf("Failed to open file. Bye\n");
system("pause");
exit(1); // 退出程序
}
// getc(fp)从打开的文件中获取一个字符
while ((ch = getc(fp)) != EOF)
putchar(ch);
fclose(fp); // 关闭文件
system("pause");
return 0;
}
缓冲输入要求用户按下Enter键发送输入。这一动作也传送了换行符,程序必须妥善处理这个麻烦的换行符。
一个不太可的程序:
/* guess.c -- 一个拖沓且错误的猜数字程序 */
#include
int main(void)
{
int guess = 1;
printf("Pick an integer from 1 to 100. I will try\
to guess ");
printf("it.\nRespond with a y if my guess is right\
and with");
printf("\nan n if it is wrong.\n");
printf("Uh...is your number %d?\n", guess);
while (getchar() != 'y') /* 获取响应,与 y 做对比 */
printf("Well, then, is it %d?\n", ++guess);
printf("I knew I could do it!\n");
system("pause");
return 0;
}
运行结果:
Pick an integer from 1 to 100. I will tryto guess it.
Respond with a y if my guess is rightand with
an n if it is wrong.
Uh...is your number 1?
n
Well, then, is it 2?
Well, then, is it 3?
no
Well, then, is it 4?
Well, then, is it 5?
Well, then, is it 6?
y
I knew I could do it!
一种解决方案是,使用while循环丢弃输入行最后剩余的内容,包括换行符。这种方法的优点是,能把no和no way这样的响应视为简单的n。
修正一下:
/* guess.c -- 修正 */
#include
int main(void)
{
int guess = 1;
printf("Pick an integer from 1 to 100. I will try\
to guess ");
printf("it.\nRespond with a y if my guess is right\
and with");
printf("\nan n if it is wrong.\n");
printf("Uh...is your number %d?\n", guess);
while (getchar() != 'y') /* 获取响应,与 y 做对比 */
{
printf("Well, then, is it %d?\n", ++guess);
while (getchar() != '\n')
continue; /* 跳过剩余的输入行 */
}
printf("I knew I could do it!\n");
system("pause");
return 0;
}
运行结果:
Pick an integer from 1 to 100. I will tryto guess it.
Respond with a y if my guess is rightand with
an n if it is wrong.
Uh...is your number 1?
n
Well, then, is it 2?
no
Well, then, is it 3?
nnnn
Well, then, is it 4?
asasa
Well, then, is it 5?
y
I knew I could do it!
这的确是解决了换行符的问题。但是,该程序还是会把f被视为n。我们用if语句筛选其他响应。
再修一下:
/* guess.c -- 优化 */
#include
int main(void)
{
int guess = 1;
char response;
printf("Pick an integer from 1 to 100. I will try\
to guess ");
printf("it.\nRespond with a y if my guess is right\
and with");
printf("\nan n if it is wrong.\n");
printf("Uh...is your number %d?\n", guess);
while ((response=getchar()) != 'y') /* 获取响应,与 y 做对比 */
{
if (response=='n')
printf("Well, then, is it %d?\n", ++guess);
else
printf("Sorry, I understand only y or n.\n");
while (getchar() != '\n')
continue; /* 跳过剩余的输入行 */
}
printf("I knew I could do it!\n");
system("pause");
return 0;
}
运行结果:
Pick an integer from 1 to 100. I will tryto guess it.
Respond with a y if my guess is rightand with
an n if it is wrong.
Uh...is your number 1?
n
Well, then, is it 2?
no
Well, then, is it 3?
sfsfsssdsdf
Sorry, I understand only y or n.
nononono
Well, then, is it 4?
y
I knew I could do it!
getchar()读取每个字符,包括空格、制表符和换行符;而 scanf()在读取数字时则会跳过空格、制表符和换行符。
一个有问题的程序:
/* showchar1.c -- 有较大 I/O 问题的程序 */
#include
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");
system("pause");
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');/* 结束一行并开始新的一行 */
}
}
运行结果:
Enter a character and two integers:
a 2 2
aa
aa
Enter another character and two integers;
Enter a newline to quit.
Bye.
scanf()函数把换行符留在输入队列中。和 scanf()不同,getchar()不会跳过换行符,所以在进入下一轮迭代时,你还没来得及输入字符,它就读取了换行符,然后将其赋给ch。而ch是换行符正式终
止循环的条件。
改一下:
#include
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);
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");
system("pause");
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');/* 结束一行并开始新的一行 */
}
}
运行结果:
Enter a character and two integers:
a 2 3asdadsada
aaa
aaa
Enter another character and two integers;
Enter a newline to quit.
Bye.
// checking.c -- 输入验证
#include
#include
// 验证输入是一个整数
long get_long(void);
// 验证范围的上下限是否有效
bool bad_limits(long begin, long end,
long low, long high);
// 计算a~b之间的整数平方和
double sum_squares(long a, long b);
int main(void)
{
const long MIN = -10000000L; // 范围的下限
const long MAX = +10000000L; // 范围的上限
long start; // 用户指定的范围最小值
long stop; // 用户指定的范围最大值
double answer;
printf("This program computes the sum of the"
"squares of "
"integers in a range.\nThe lower bound should"
"not "
"be less than -10000000 and\nthe upper bound "
"should not be more than +10000000.\nEnter the "
"limits (enter 0 for both limits to quit):\n"
"lower limit: ");
start = get_long();
printf("upper limit: ");
stop = get_long();
while (start != 0 || stop != 0)
{
if (bad_limits(start, stop, MIN, MAX))
printf("Please try again.\n");
else
{
answer = sum_squares(start, stop);
printf("The sum of the squares of the integers ");
printf("from %ld to %ld is %g\n",
start, stop, answer);
}
printf("Enter the limits (enter 0 for both "
"limits to quit):\n");
printf("lower limit: ");
start = get_long();
printf("upper limit: ");
stop = get_long();
}
printf("Done.\n");
system("pause");
return 0;
}
long get_long(void)
{
long input;
char ch;
while (scanf("%ld", &input) != 1)
{
while ((ch = getchar()) != '\n')
putchar(ch); // 处理错误输入
printf(" is not an integer.\nPlease enter an ");
printf("integer value, such as 25, -178, or 3: ");
}
return input;
}
double sum_squares(long a, long b)
{
double total = 0;
long i;
for (i = a; i <= b; i++)
total += (double)i * (double)i;
return total;
}
bool bad_limits(long begin, long end,
long low, long high)
{
bool not_good = false;
if (begin > end)
{
printf("%ld isn't smaller than %ld.\n", begin, end);
not_good = true;
}
if (begin < low || end < low)
{
printf("Values must be %ld or greater.\n", low);
not_good = true;
}
if (begin > high || end > high)
{
printf("Values must be %ld or less.\n", high);
not_good = true;
}
return not_good;
}
运行结果:
This program computes the sum of thesquares of integers in a range.
The lower bound shouldnot be less than -10000000 and
the upper bound should not be more than +10000000.
Enter the limits (enter 0 for both limits to quit):
lower limit: 10
upper limit: 20
The sum of the squares of the integers from 10 to 20 is 2585
Enter the limits (enter 0 for both limits to quit):
lower limit: a
a is not an integer.
Please enter an integer value, such as 25, -178, or 3: 20
upper limit: 10
20 isn't smaller than 10.
Please try again.
Enter the limits (enter 0 for both limits to quit):
lower limit: 2
upper limit: 3
The sum of the squares of the integers from 2 to 3 is 13
Enter the limits (enter 0 for both limits to quit):
lower limit: 0
upper limit: 0
Done.
getchar()和使用%c的scanf()接受所有的字符。
/* menuette.c -- 菜单程序 */
#include
char get_choice(void);
char get_first(void);
int get_int(void);
void count(void);
int main(void)
{
int choice;
void count(void);
while ((choice = get_choice()) != 'q')
{
switch (choice)
{
case 'a': printf("Buy low, sell high.\n");
break;
case 'b': putchar('\a'); /* ANSI */
break;
case 'c': count();
break;
default: printf("Program error!\n");
break;
}
}
printf("Bye.\n");
system("pause");
return 0;
}
void count(void)
{
int n, i;
printf("Count how far? Enter an integer:\n");
n = get_int();
for (i = 1; i <= n; i++)
printf("%d\n", i);
//while (getchar() != '\n')
// continue;
}
char get_choice(void)
{
int ch;
printf("Enter the letter of your choice:\n");
printf("a. advice b. bell\n");
printf("c. count q. quit\n");
ch = get_first();
while ((ch < 'a' || ch > 'c') && ch != 'q')
{
printf("Please respond with a, b, c, or q.\n");
ch = get_first();
}
return ch;
}
char get_first(void)
{
int ch;
ch = getchar();
if (ch != '\n')
{
while (getchar() != '\n')
continue;
}
else
ch = getchar();
return ch;
}
int get_int(void)
{
int input;
char ch;
while (scanf("%d", &input) != 1)
{
while ((ch = getchar()) != '\n')
putchar(ch); // 处理错误输出
printf(" is not an integer.\nPlease enter an ");
printf("integer value, such as 25, -178, or 3: ");
}
return input;
}
运行结果:
Enter the letter of your choice:
a. advice b. bell
c. count q. quit
a
Buy low, sell high.
Enter the letter of your choice:
a. advice b. bell
c. count q. quit
b
Enter the letter of your choice:
a. advice b. bell
c. count q. quit
c
Count how far? Enter an integer:
3
1
2
3
Enter the letter of your choice:
a. advice b. bell
c. count q. quit
q
Bye.
先选c的话,如果输入3作为响应,scanf()会读取3并把换行符留在输入队列中。下次调用 get_choice()将导致get_first()返回这个换行符,从而导致我们不希望出现的行为。