输入和输出

时间:2023-02-16 20:57:40

前面我们已经简单介绍了一些常用的I/O函数,下面我们对这个话题再深入一点。最初,输入/输出函数不是C定义的一部分,C把开发这些函数的任务留给了编译器的作者。在实际应用中,为了保证标准函数在不同的计算机环境中能正常工作,所以它们很少使用某些特殊系统才使用的特性,许多C供应商会根据硬件的特性,额外提供一些I/O函数,这些有针对性、非标准的函数让程序员能更有效地使用特定计算机编写程序。

在getchar()和putchar()那篇文章中,介绍的程序以换行符为输入的结尾,但是当我们的输入中需要输入换行符时,就可能提前结束输入,我们如何使用更好的方法结束输入。让我们先来了解C是如何处理键盘输入,缓冲区和标准输入文件的概念。

1.缓冲区

前面我们提到过输出缓冲区,缓冲区就是存储输入或输出的临时存储区,为什么要使用缓冲区呢?首先,将若个字符作为一个块进行传输比逐个发送这些字符节约时间(涉及操作系统);其次,如果用户打错字符,可以直接通过键盘修正错误。我们先来看看有缓冲区和无缓冲区是如何输入(输出)信息的。

输入和输出

虽然缓冲输入的好处很多,但是某些交互式程序也需要无缓冲输入。比如,你打游戏时,放技能需要立即释放,不可能程序判断你后面还需不需要接其它技能或者其它操作。

缓冲分为两类:完全缓冲I/O和行缓冲I/O。完全缓冲I/O指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中,缓冲区的大小取决于系统,常见的大小是512B和4096B。行缓冲I/O指的是在出现换行符时刷新缓冲区,键盘输入通常是行缓冲输入。

2.结束键盘输入

在上述程序中,可能会出现一个普通字符导致输入提前结束,应该用一个在文本中用不到的字符来标记输入完成,C语言提供了这样的字符。

2.1文件、流和键盘输入

文件是存储器中存储信息的区域。通常,文件都保存在某种永久存储器中。文件对于操作系统非常重要,例如,你编写的程序就需要存储在文件中,你管理储存区就是通过文件来进行操作。

C中就有许多用于打开、读取、写入和关闭文件的库函数。C可以使用主机操作系统给的基本文件工具直接处理文件,这些直接调用操作系统的函数被称为底层I/O,由于计算机系统各不相同,所以不可能为底层I/O函数创建标准库。C还可以通过标准I/O包来处理文件,这涉及创建用于处理文件的标准模型和一套标准I/O函数,在这一层面,具体的C实现负责不同系统的差异,以便于用户使用统一的界面。

至于差异指的是什么,例如,不同的系统存储文件的方式不同,有些系统把文件内容和文件相关的信息存储在不同的地方,有些系统在文件中创建一份文件描述(文件系统涉及的内容);在处理文件方面,有些系统使用单个换行符来标记行末尾,而有些系统可能使用回车符和换行符的组合来表示行末尾等等。但如果使用标准I/O包,就不用考虑这些差异。

从概念上看,C程序处理的是流而不是直接处理文件。流是一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的输入,由属性更为统一的流来表示。于是,打开文件的过程就是把流与文件相关联,而且读写都通过流来完成。C把输入和输出设备视为存储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件,stdin流表示键盘输入,stdout流表示屏幕输出,前面介绍的四个函数都是标准I/O包的成员,用来处理这两个流。

2.2文件结尾

计算机操作系统要以某种方式判断文件的开始和结束。检测文件结尾的一种方式是,在文件末尾放一个特殊的字符标记文件结尾Ctrl+Z等,现代文本文件不一定嵌入的Ctrl+Z,但是如果有,该系统操作会将其视为文件结尾标记;另一种方式是存储文件大小的信息,例如,如果文件有400字节,程序在读到400字节时便达到文件的末尾,因为用这种方法在文件中可以存储所有的字符,包括Ctrl+Z

无论操作系统实际使用何种方法检测文件末尾,在C语言中,用getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file),getchar()函数实际返回值的类型是int,所以它可以读取EOF字符,scanf函数检测到文件时也返回EOF,通常EOF定义在stdio.h文件中:​​#define EOF (-1)​​。

为什么将其设置为-1,因为getchar()函数的返回值通常介于0~127,这些值对应标准字符集。但是,如果系统能够识别扩展字符集,该函数的返回值可能在0~255之间。无论哪种情况,-1都不对应任何字符。某些系统也许把EOF定义为-1以外的值,但是定义的值一定与输入字符所产生的返回值不同。