C_Primer_Plus08.io
字符输入/输出和输入验证
- 要点
更详细地介绍输入、输出以及缓冲输入和无缓冲输入的区别
通过键盘模拟文件结尾条件
使用重定向把程序和文件相连接
创建更友好的用户界面
最初,输入/输出不是C定义的一部分,C 把开发这些函数的任务留给了编译器的实现者来完成。UNIX 系统中的 C 实现为这些函数提供了一个模型。ANSI C 库吸取成功的经验,把大量的 UNIX I/O 函数囊括其中。由于必须保证这些标准函数在不同计算机环境中能正常工作,所以它们很少使用某些特殊系统才有的特性。所以,许多 C 供应商会利用硬件或操作系统的特性,额外提供一些 I/O 函数。
单字符 I/O: getchar() 和 putchar()
getchar() 和 putchar() 方法每次只处理一个字符,对人来说显得太笨拙,然而确很适合计算机,而且这是大多数文本处理程序使用的核心方法。
getchar() 和 putchar() 都不是真正的函数,他们被定义为供预处理器使用的宏。
程序例:
#include <stdio.h>
int main(void){
char ch;
while ('#' != (ch = getchar())){
putchar(ch);
}
return 0;
}
output:
Hello, there. I would [enter]
Hello, there. I would
like a #2 bag of potatoes.[enter]
like a
缓冲区
上述程序在运行后输入一串字符,遇到回车才执行循环,然后把一行内容打印到屏幕上,是因为有缓冲机制。用户输入的字符被收集并存储在一个被称为缓冲区(buffer)的临时存储区,按回车后,程序才可使用用户输入的字符。
对于老式系统,则没有缓冲机制,称为无缓冲输入。
为什么要有缓冲区?
- 把若干字符作为一个块进行传输比逐个传输节约时间
- 如有输入错误,可以修正
使用无缓冲输入的情况:比如在游戏中,按一个键就执行相应的指令体验更好。所以有缓冲输入和无缓冲输入都有用武之地。
缓冲分两类:完全缓冲 I/O 和 行缓冲 I/O。完全缓冲是指当缓冲区被完全填满时才刷新缓冲区(内容发送到目的地),通常出现在文件传输中。缓冲区的大小取决于系统,常见的是 512B 和 4096B。行缓冲是指出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入。
结束键盘输入
文件、流和键盘输入
C 在读写文件时,有两种可行的方法。从较底层面上,C 可以使用主机操作系统的基本文件工具直接处理文件,这些直接调用系统的函数被称为底层 I/O (low-level I/O) 。由于计算机系统各不相同,不可能为普通的底层 I/O 函数创建统一的标准库。从较高层面上,C 还可以通过标准 I/O 包(standard I/O package) 来处理文件,这涉及创建用于处理文件的标准模型和一套标准 I/O 函数。
不同的操作系统对文件的处理不同。首先,存储方式的不同,有些系统把文件内容存储在一处,而文件的相关信息存储在另一处;有些系统在文件中创建一份文件描述。在处理文件方面,有些系统使用单个换行符标记末尾,而其他系统可能使用回车符和换行符的组合来表示行末尾。有些系统用最小字节来衡量文件的大小,有些系统用字节块大小来衡量。
如果使用标准 I/O 包,就不用考虑这些差异。因此可以用 if ('\n' == ch) 来检查换行符。即使系统实际使用回车符和换行符的组合来标记行末尾,I/O 函数会在两种表示方法之间相互转换。
从概念上看,C 程序处理的是流而不是直接处理文件。流(stream)是一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的输入,由属性更统一的流来表示。所以打开文件的过程就是把流和文件相关联的过程,而且读写都由流来完成。
本章中,可以把键盘和显示设备视为 C 程序自动打开的文件。stdin 流表示键盘输入,stdout 表示屏幕输出流。getchar(), putchar(), printf() 和 scanf() 函数都是标准 I/O 包的成员,用于处理这两个流。
文件结尾
检测文件结尾的一种方法是,在文件末尾放一个特殊字符标记文件结尾。CP/M, IBM-DOS 和 MS-DOS 的文本文件曾使用内嵌的 Ctrl+Z 字符标记文件结尾,这种方法至今仍起作用,如果文件中有 Ctrl+Z,系统认为文件结束。但现在有了一些其他的方法,比如记录文件大小,所以现在的文本文件中不一定有嵌入的 Ctrl+Z. UNIX 系统则使用记录文件大小的方法处理所有的文件。
不论操作系统使用哪种方法检测文件结尾,C 语言中,用 getchar() 读取文件检测到文件结尾时将返回一个特殊的值 EOF (end of file)。EOF 定义在 stdio.h 文件中:
#define EOF (-1)
为什么是 -1,因为 getchar() 函数的返回值通常都是 0-127,对应标准字符集,但是如果系统能识别扩展字符集,该函数的返回值可能在 0-255之间,不论哪种情况,-1 都不对应任何字符。
某些系统可能把 EOF 定义为 -1 以外的值,但无论如何都不同文件中的字符。所以,如果包含 stdio.h 文件,并使用 EOF 符号,就不用担心 EOF 值不同的问题。
程序例:
#include <stdio.h>
int main(void){
int ch;
while (EOF != (ch = getchar())){
putchar(ch);
}
return 0;
}
注意,上面程序中的 ch 定义为 int 类型,若仍然使用 char 类型,则它会把负数转成正数,与 EOF 判断时会不准确。getchar() 函数的返回值类型是 int,如果把它的返回值赋给 char 类型变量,一些编译器会警告可能丢失数据。
另外,运行该程序试图输入 EOF 字符时,不能只输入字符 'EOF' 或 '-1',正确的做法是,必须找出当前系统的要求,比如,大多数 Unix 和 Linux 系统中,在一行开始处按下 Ctrl+D 会传输文件结尾信号,而许多微型计算机系统把一行开始处的 Ctrl+Z 识别为文件结尾信号,有些系统把任意位置的 Ctrl+Z 解释成文件结尾信号。
改变这个程序的输入或输出的流,可以实现读写文件、复制文件的功能。
重定向和文件
默认情况下,使用 getchar() 函数时,C 程序使用标准 I/O 包查找标准输入作为输入源,即 stdin流。它可以使磁带、穿孔卡或电传打印机,或者是键盘、文件,甚至是语音输入。
程序通过两种方式使用文件。显式地使用特定的函数打开文件、关闭文件、读文件、写文件(这种方法以后再介绍)。另一种是设计能与键盘和屏幕互动的程序,通过不同的渠道重定向输入至文件和从文件输出。即把 stdin 流重新赋给文件。这样在使用 getchar() 函数时,它并不关心从流的什么位置获取数据,虽然这种重定向的方法在某些方面有些限制,但用起来比较简单。
重定向的一个主要问题与操作系统有关,与 C 无关。尽管如此,许多 C 的环境中(Unix, linux, windows 命令模式)都有重定向特性,甚至有些 C 实现还在缺少重定向特性的系统中模拟它。
Unix,Linux 和 DOS 重定向
假设程序编译后的可执行文件为 echo_eof (windows为 echo_eof.exe)
将文本文件 words 内容输出到屏幕:
echo_eof < words
echo_eof 并不知道输入的内容来自文件还是键盘
将文本文件 words 复制到 savewords:
echo_eof < words > savewords
# 或:(命令与重定向运算符的顺序无关)
echo_eof > savewords < words
- 输入、输出文件名不能相同
- 重定向运算符连接一个可执行程序和一个文件,不能用于文件之间或程序之间
- 重定向运算符不能读取多个文件的输入,也不能输出到过个文件
- 通常,文件名和运算符之间的空格不是必须的
不使用重定向
打开文件:
/* eof of file */
#include <stdio.h>
#include <stdlib.h>
int main(void){
char ch;
char filename[50];
FILE * fp;
printf("Enter a file name:\n");
scanf("%s", filename);
fp = fopen(filename, "r");
if (NULL == fp){
printf("Failed to open file %s. Bye\n", filename);
exit(1);
}
while (EOF != (ch = getc(fp))){
putchar(ch);
}
fclose(fp);
return 0;
}
绝大部分 C 系统都可以使用重定向,可以通过操作系统重定向所有程序,或只在 C 编译器允许的情况下重定向 C 程序。
有些系统要求重定向运算符左侧有一个空格,右侧没有空格,而其他系统允许在重定向运算符两侧有空格或没有空格。
创建更友好的用户界面
处理缓冲输入中的换行符
使用缓冲输入时,用户需要按下回车发送输入,但这样做换行符作为一个字符发送给了程序,所以程序应当能够妥善处理这个麻烦的换行符。以猜字谜程序为例:
#include <stdio.h>
int main(void){
int guess = 1;
printf("Pick an integer from 1 to 100. I will try to guess it.\n"
"Respond with a y if my guess is right and with an n\n"
"if it is wrong.\n");
printf("Un...is your number %d?\n", guess);
while ('y' != getchar()){
printf("Well, then, is it %d?\n", ++guess);
}
printf("I knew I could do it!\n");
return 0;
}
output:
Pick an integer from 1 to 100. I will try to guess it.
Respond with a y if my guess is right and with an n
if it is wrong.
Un...is your number 1?
n
Well, then, is it 2?
Well, then, is it 3?
n
Well, then, is it 4?
Well, then, is it 5?
y
I knew I could do it!
由于程序没有处理换行符,while循环中每次循环了两次,其中一次是 回车符。对程序进行修改:
#include <stdio.h>
int main(void){
int guess = 1;
char ch;
printf("Pick an integer from 1 to 100. I will try to guess it.\n"
"Respond with a y if my guess is right and with an n\n"
"if it is wrong.\n");
printf("Un...is your number %d?\n", guess);
while ('y' != (ch = getchar())){
if ('\n' == ch){
continue;
}
printf("Well, then, is it %d?\n", ++guess);
}
printf("I knew I could do it!\n");
return 0;
}
output:
Pick an integer from 1 to 100. I will try to guess it.
Respond with a y if my guess is right and with an n
if it is wrong.
Un...is your number 1?
n
Well, then, is it 2?
n
Well, then, is it 3?
y
I knew I could do it!
上面的修改修正了回车符的问题,但如果一行中输入多个字符,则会打印多行,应把一行中第一个字符之后的所有字符跳过。而且,如果输入除了 n 和 y 以外的字符,会被当做n处理。修改后的程序:
#include <stdio.h>
int main(void){
int guess = 1;
char ch;
printf("Pick an integer from 1 to 100. I will try to guess it.\n"
"Respond with a y if my guess is right and with an n\n"
"if it is wrong.\n");
printf("Un...is your number %d?\n", guess);
while ('y' != (ch = getchar())){
if ('n' == ch){
printf("Well, then, is it %d?\n", ++guess);
}else{
printf("Sorry, I understand only y or n.\n");
}
while ('\n' != getchar()){
continue;
}
}
printf("I knew I could do it!\n");
return 0;
}
output:
Pick an integer from 1 to 100. I will try to guess it.
Respond with a y if my guess is right and with an n
if it is wrong.
Un...is your number 1?
n
Well, then, is it 2?
no sir
Well, then, is it 3?
ny
Well, then, is it 4?
holly
Sorry, I understand only y or n.
y
I knew I could do it!
混合数值和字符输入
假设程序要求用 getchar() 处理字符输入,用 scanf() 处理数值输入,这两个函数都能很好地完成任务,但不能把他们混用。因为 getchar() 读取每个字符,包括空格、制表符和换行符,而 scanf() 在读取数字时会跳过空格、制表符和换行符。所以程序要做好换行符的处理。
程序例:
#include <stdio.h>
void display(char ch, int row, int col);
int main(void){
char ch;
int row, col;
printf("Enter a character and two integers:\n");
while ((ch = getchar()) != '\n'){
if (scanf("%d %d", &row, &col) != 2){
printf("Invalid input. ");
break;
}
display(ch, row, col);
while (getchar() != '\n'){
continue;
}
printf("Enter another character and another two integers:\n");
printf("Enter a newline to quit.\n");
}
printf("Bye.\n");
return 0;
}
void display(char ch, int row, int col){
for (int i = 0, j = 0; i < row; ++i){
for (j = 0; j < col; ++j){
printf("%c", ch);
}
printf("\n");
}
printf("\n");
}
output:
Enter a character and two integers:
a 2 3
aaa
aaa
Enter another character and another two integers:
Enter a newline to quit.
b 3 4
bbbb
bbbb
bbbb
Enter another character and another two integers:
Enter a newline to quit.
Bye.
输入验证
用户并不一定按照程序设定的输入合法字符。比如需要输入正数的时候用户输入了负数,甚至输入了非法字符,程序仍然需要处理这些意外情况,可以用这种写法:
long n;
while (scanf("%ld", &a) == 1 && n >= 0){
// process
}
这样当用户输入非正数的字符时,会跳过处理。然而也可以让程序友好一些,提示用户再次输入正确类型的值。这样就需要处理有问题的输入。此时,scanf() 没有成功读取的字符会留在输入队列中,可以利用 getchar() 函数逐字读取输入字符,从而跳过错误字符:
long n;
char ch;
while (scanf("%ld", &a) != 1 || n < 0){
while ((ch = getchar()) != '\n'){
putchar(ch); // 处理错误输入
}
printf(" is not an integer.\n"
"Please enter an integer: ");
}
// continue process with n
程序例:
依次输入两个数字,计算介于两个数之间的所有数的平方和。要求先输入较小数,再输入较大数,且两个数不能超过上下界限。当两个输入的数都是0时退出程序。
该程序展示了模块化编程,不同模块之间功能分离。
/* check input & calc sum squares between two numbers */
#include <stdio.h>
#include <stdbool.h>
long get_long(void);
bool checkLimits(long low, long high, long min, long max);
long sum_squares(long low, long high);
int main(void){
char ch;
const long MIN = -10000000L;
const long MAX = +10000000L;
long low;
long high;
long result;
printf("This program calculates the sum of the squares of "
"integers in a range.\nThe lower bound should not be "
"less than %ld and\nthe upper bound should not be "
"more than %ld.\nEnter the limits (enter 0 for both "
"to quit):\n"
"lower limit: ", MIN, MAX);
low = get_long();
printf("upper limit: ");
high = get_long();
while (low != 0 && high != 0){
if (!checkLimits(low, high, MIN, MAX)){
printf("Retry.\n");
}else{
result = sum_squares(low, high);
printf("sum sqaures between %ld and %ld is %ld.\n", low, high, result);
}
printf("Enter the limits (enter 0 for both limits to quit):\n");
printf("lower limit: ");
low = get_long();
printf("upper limit: ");
high = get_long();
}
printf("Bye.\n");
return 0;
}
long get_long(void){
long input;
while (scanf("%ld", &input) == 0){
printf("Invalid input, retry:\n");
while (getchar() != '\n'){
continue;
}
}
return input;
}
bool checkLimits(long low, long high, long min, long max){
if (low < min || high < min){
printf("low and high must no smaller than %ld.\n", min);
return false;
}
if (low > max || high > max){
printf("low and high must no larger than %ld.\n", max);
return false;
}
if (low > high){
printf("%ld isn't smaller than %ld.\n", low, high);
return false;
}
return true;
}
long sum_squares(long low, long high){
long result;
for (; low <= high; ++low){
result += low^2;
}
return result;
}
output:
This program calculates the sum of the squares of integers in a range.
The lower bound should not be less than -10000000 and
the upper bound should not be more than 10000000.
Enter the limits (enter 0 for both to quit):
lower limit: 10
upper limit: 20
sum sqaures between 10 and 20 is 173.
Enter the limits (enter 0 for both limits to quit):
lower limit: 100
upper limit: 2000000
sum sqaures between 100 and 2000000 is 2000000995152.
Enter the limits (enter 0 for both limits to quit):
lower limit: -1000000000000
upper limit: 2
low and high must no smaller than -10000000.
Retry.
Enter the limits (enter 0 for both limits to quit):
lower limit: 0
upper limit: 0
Bye.
菜单浏览 - 模块化
将某个特定功能委托给另一个函数。
#include <stdio.h>
char get_choice(void);
void count(void);
int main(void){
char choice;
while ((choice = get_choice()) != 'q'){
switch (choice){
case 'a':
printf("Buy low, sell high.\n");
break;
case 'b':
putchar('b');
break;
case 'c':
count();
break;
default:
printf("Program error.\n");
break;
}
}
printf("Bye.\n");
return 0;
}
char get_choice(void){
char ch;
printf("Enter your choice: \n");
printf("a. advice b. bell\n"
"c. count q. quit\n");
ch = getchar();
while ((ch > 'c' || ch < 'a') && ch != 'q'){
printf("Please respond with a, b, c or q.\n");
while (getchar() != '\n'){
continue;
}
ch = getchar();
}
while (getchar() != '\n'){
continue;
}
return ch;
}
void count(void){
int n;
char ch;
printf("Count how far? Enter an integer:\n");
while (scanf("%d", &n) != 1){
while ((ch = getchar()) != '\n'){
putchar(ch);
}
printf(" is not an integer.\nPlease enter an integer "
"value, such as 25, -126, or 9: ");
}
while (getchar() != '\n'){
continue;
}
for (int i = 1; i < n; ++i){
printf("%d\n", i);
}
}
output:
Enter your choice:
a. advice b. bell
c. count q. quit
a
Buy low, sell high.
Enter your choice:
a. advice b. bell
c. count q. quit
b
bEnter your choice:
a. advice b. bell
c. count q. quit
c
Count how far? Enter an integer:
n
n is not an integer.
Please enter an integer value, such as 25, -126, or 9: 4
1
2
3
Enter your choice:
a. advice b. bell
c. count q. quit
d
Please respond with a, b, c or q.
q
Bye.
ex08:
编写一个程序,显示一个提供加法、减法、乘法、除法的菜单。获得用户选择的选项后,程序提示用户输入两个数字,然后执行用户刚才选择的操作。该程序只接受菜单提供的选项。程序使用float类型的变量储存用户输入的数字,如果用户输入失败,则允许再次输入。进行除法运算时,如果用户输入0作为第2个数(除数),程序应提示用户重新输入一个新值。
#include <stdio.h>
int get_choice(void);
float get_float(void);
void calc(int choice);
int main(void){
int choice;
while ((choice = get_choice()) != 'q'){
switch (choice){
case 'a':
case 'b':
case 'm':
case 'd':
calc(choice);
break;
default:
printf("Program error.\n");
break;
}
}
printf("Bye.\n");
return 0;
}
int get_choice(void){
int choice;
printf("Enter your choice:\n");
printf("a. add b. subtract\n"
"m. multiply d. divide\n"
"q. quit\n");
choice = getchar();
while (choice != 'a' && choice != 'b'
&& choice != 'm' && choice != 'd'
&& choice != 'q'){
printf("Please respond with a, b, m, d, or q.\n");
while (getchar() != '\n'){
continue;
}
choice = getchar();
}
while (getchar() != '\n'){
continue;
}
return choice;
}
float get_float(void){
float input;
char ch;
while (scanf("%f", &input) != 1){
while ((ch = getchar()) != '\n'){
putchar(ch);
}
printf(" is not a float.\n"
"Please input a float, such as 1.23, -0.56 or 3: ");
}
while (getchar() != '\n'){
continue;
}
return input;
}
void calc(int calc){
float first;
float second;
char sign;
float result;
printf("Enter first number: ");
first = get_float();
printf("Enter Second number: ");
second = get_float();
if ('d' == calc){
while (second == 0.){
printf("Enter a number other than 0: ");
second = get_float();
}
}
switch (calc){
case 'a':
sign = '+';
result = first + second;
break;
case 'b':
sign = '-';
result = first - second;
break;
case 'm':
sign = '*';
result = first * second;
break;
case 'd':
sign = '.';
result = first / second;
break;
default:
printf("Program error.\n");
break;
}
printf("%.2f %c %.2f = %.2f\n", first, sign, second, result);
}
output:
Enter your choice:
a. add b. subtract
m. multiply d. divide
q. quit
a
Enter first number: 10
Enter Second number: 3
10.00 + 3.00 = 13.00
Enter your choice:
a. add b. subtract
m. multiply d. divide
q. quit
d
Enter first number: 9
Enter Second number: 0
Enter a number other than 0: 5
9.00 . 5.00 = 1.80
Enter your choice:
a. add b. subtract
m. multiply d. divide
q. quit
e
Please respond with a, b, m, d, or q.
q
Bye.

浙公网安备 33010602011771号