C | 字符输入/输出和输入验证

目录

一、 单字符I/O:getchar()和putchar()

二、缓冲区

三、结束键盘输入

3.1 文件、流和键盘输入

3.2 文件结尾

四、重定向和文件

4.1 UNIX、Linux和DOS重定向

1.重定向输入

2.重定向输出

3.组合重定向

五、创建更友好的用户界面

5.1 使用缓冲输入

5.2 混合数值和字符输入

六、输入验证

七、菜单浏览


一、 单字符I/O:getchar()和putchar()

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键后,程序才可使用用户输入的字符。

C | 字符输入/输出和输入验证_第1张图片

关于缓冲区的应用:

  • 把若干字符作为一个块进行传输比逐个发送这些字符节约时间。.
  • 如果用户打错字符,可以直接通过键盘修正错误。当最后按下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没有提供调用无缓冲输入的标准方式,这意味着是否能进行无缓冲输入取决于计算机系统

三、结束键盘输入

3.1 文件、流和键盘输入

  • 文件(file)是存储器中储存信息的区域。
  • 底层 I/O(low-level I/O):直接调用操作系统的函数。
  • 由于计算机系统各不相同,所以不可能为普通的底层I/O函数创建标准库,然而从较高层面上,C还可以通过标准I/O包(standard I/O package)来处理文件。
  • 具体的C实现负责处理不同系统的差异,以便用户使用统一的界面。
  • 从概念上看 , C 程序处理的是流而不是直接处理文件。流(stream)是一个实际输入或输出映射的理想化数据流

着重理解C把输入和输出设备视为存储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件。stdin流表示键盘输入,stdout流表示屏幕输出。getchar()、putchar()、printf()和scanf()函数都是标准I/O包的成员,处理这两个流。

3.2 文件结尾

计算机操作系统要以某种方式判断文件的开始和结束。

方法一:在文件末尾放一个特殊的字符标记文件结尾。这些操作系统可以使用内嵌的Ctrl+Z字符来标记文件结尾。这曾经是操作系统使用的唯一标记,不过现在有一些其他的选择所以现代的文本文件不一定有嵌入的Ctrl+Z,但是如果有,该操作系统会将其视为一个文件结尾标记。

C | 字符输入/输出和输入验证_第2张图片

 方法二:储存文件大小的信息。如果文件有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

四、重定向和文件

程序可以通过两种方式使用文件。

  1. 显式使用特定的函数打开文件、关闭文件、读取文件、写入文件,诸如此类。
  2. 设计能与键盘和屏幕互动的程序,通过不同的渠道重定向输入至文件和从文件输出。换言之,把stdin流重新赋给文件。

重定向的一个主要问题与操作系统有关,与C无关。

4.1 UNIX、Linux和DOS重定向

1.重定向输入

文本文件(text file)是内含文本的文件,其中储存的数据是我们可识别的字符。内含机器语言指令的文件(如储存可执行程序的文件)不是文本文件。

echo_eof < words

<符号是UNIX和DOS/Windows的重定向运算符。该运算符使words文件与stdin流相关联,把文件中的内容导入echo_eof程序。文件就是现在的I/O设备。

注意:对于UNIX、Linux和Windows命令提示,<两侧的空格是可选的。一些系统,如AmigaDOS在重定向符号和文件名之间不允许有空格。

2.重定向输出

echo_eof>mywords

>符号是第2个重定向运算符。重定向把stdout从显示设备(即,显示器)赋给mywords文件。如果已经有一个名为mywords的文件,通常会擦除该文件的内容,然后替换新的内容。

C | 字符输入/输出和输入验证_第3张图片

3.组合重定向

制作一份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;
}

五、创建更友好的用户界面

5.1 使用缓冲输入

缓冲输入要求用户按下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!

5.2 混合数值和字符输入

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()返回这个换行符,从而导致我们不希望出现的行为。

你可能感兴趣的:(c,c语言,c++,开发语言)