字符输入/输出和输入验证
在本章中您将学习到下列内容:
1.有关输入、输出及缓冲和非缓冲输入之间的区别;
2.从键盘模拟文件结尾的方法;
3.如何重定向将您的程序与文件连接
4.创建更友好的用户界面
在涉及计算机的话题时,我们经常会提到输入和输出。我们谈论输入和输出设备(如键盘。U盘、扫描仪、和激光打印机),讲解如何处理输入数据和输出数据(简称I/O函数)***。
I/O函数(如printf()scanf() getchar() putchar() 等)负责把信息传送到程序中。前几章简单介绍过这些函数,本章将详细介绍他们的概念,还会介绍如何设计与用户交互的界面。
最初,输入/输出函数不是C定义的一部分,C把开发这些函数的任务留给编译器的实现者来完成。在实际应用中,UNIX系统(这个系统指什么)中的C实现为这些函数提供了一个模型。ANSI C库吸取成功的经验,把大量的UNIX I/O函数囊括其中***,包括一些我们曾经用过的。由于必须保证这些标准函数在不同的计算机环境中能正常工作,所以他们很少使用某些特殊系统才有的特性。因此,***许多C供应商会利用硬件的特性,额外提供一些I/O函数。***其他函数或函数系列需要特殊的操作系统支持,如Winsows或Macintosh OS提供的特殊图形界面。这些有针对性、非标准的函数让程序员能更有效的使用特定计算机编写程序。本章只着重讲解所有系统都通用的I/O函数,用这些函数编写的可移植程序,很容易从一个系统移植到另一个系统。处理文件输入/输出的程序也可以使用这些函数。
许多程序都有输入验证,即判断用户的输入是否与程序期望的输入匹配。本章将演示一些与输入验证相关的问题和解决方案。
这章所说的输入输出不再单纯指的是键盘,而是磁盘或文件等
8.1 单字符I/O:getchar()和putchar()
getchar ( ) 和 putchar ( ) 每次只处理一个字符 很笨拙,但适合计算机,而且,这是绝大多数文本处理程序所用的核心方法
自从ANSI C标准发布以后,C就把stdio.h头文件与使用getchar()和 putchar()相关联,这就是为什么程序中要包含这个头文件的原因(其实, getchar()和 putchar()都不是真正的函数,它们被定义为供预处理器使用的 宏
为何输入的字符能直接显示在屏幕上?如果用一个特殊 字符(如,#)来结束输入,就无法在文本中使用这个字符,是否有更好的 方法结束输入?要回答这些问题,首先要了解 C程序如何处理键盘输入,尤 其是缓冲和标准输入文件的概念。
在没有 敲 Enter 之前,就没有回显****
8.2 缓冲区
像这样回显用户输入的字符后立即重复打印该字符 是属于无缓冲(或直接)输入,即正在等待的程序可立即使用输入的字符。
对于该例,大部分系统在用户按下Enter键之前不会重复打印刚输入的字 符,这种输入形式属于缓冲输入。
用户输入的字符被收集并储存在一个被称 为缓冲区(buffer)的临时存储区,按下Enter键后,程序才可使用用户输入 的字符。
为什么要有缓冲区?
首先,把若干字符作为一个块进行传输比逐个发送 这些字符***节约时间***。其次,如果用户打错字符,可以直接通过键盘***修正错误***。当最后按下Enter键时,传输的是正确的输入。
虽然缓冲输入好处很多,但是某些交互式程序也需要无缓冲输入。例 如,在游戏中,你希望按下一个键就执行相应的指令。因此,缓冲输入和无 缓冲输入都有用武之地。
缓冲分为两类:完全缓冲I/O和行缓冲I/O
完全缓冲输入指的是当缓冲 区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入 中。缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节。
行缓 冲I/O指的是在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所 以在按下Enter键后才刷新缓冲区
那么,使用缓冲输入还是无缓冲输入?ANSI C和后续的C标准都规定输 入是缓冲的,不过最初K&R把这个决定权交给了编译器的编写者。读者可 以运行echo.c程序观察输出的情况,了解所用的输出类型。
ANSI C决定把缓冲输入作为标准的原因是:
一些计算机不允许无缓冲 输入。如果你的计算机允许无缓冲输入,那么你所用的C编译器很可能会提 供一个无缓冲输入的选项。例如,许多IBM PC兼容机的编译器都为支持无 缓冲输入提供一系列特殊的函数,其原型都在conio.h头文件中。这些函数包 括用于回显无缓冲输入的getche()函数和用于无回显无缓冲输入的getch()函数 (回显输入意味着用户输入的字符直接显示在屏幕上,无回显输入意味着击 键后对应的字符不显示)。UNIX系统使用另一种不同的方式控制缓冲。在 UNIX系统中,可以使用ioctl()函数(该函数属于UNIX库,但是不属于C标 准)指定待输入的类型,然后用getchar()执行相应的操作。在ANSI C中,用 setbuf()和setvbuf()函数(详见第13章)控制缓冲,但是受限于一些系统的内 部设置,这些函数可能不起作用。总之,ANSI没有提供调用无缓冲输入的 标准方式,这意味着是否能进行无缓冲输入取决于计算机系统。
8.3 结束键盘输入
在echo.c程序(程序清单8.1)中,只要输入的字符中不含#,那么程序 在读到#时才会结束。但是, #也是一个普通的字符,有时不可避免要用 到。应该用一个在文本中用不到的字符来标记输入完成,这样的字符不会无 意间出现在输入中,在你不希望结束程序的时候终止程序***。C 的确提供了这 样的字符,不过在此之前,先来了解一下C处理文件的方式。***
8.3.1 文件、流和键盘输入
文件(file)是存储器中储存信息的区域
通常,文件都保存在某种永 久存储器中(如,硬盘、U盘或DVD等)。毫无疑问,文件对于计算机系统 相当重要。例如,你编写的C程序就保存在文件中,用来编译C程序的程序 也保存在文件中。后者说明,某些程序需要访问指定的文件。当编译储存在 名为 echo.c 文件中的程序时,编译器打开echo.c文件并读取其中的内容。当 编译器处理完后,会关闭该文件。其他程序,例如文字处理器,不仅要打 开、读取和关闭文件,还要把数据写入文件。
C 是一门强大、灵活的语言,有许多用于打开、读取、写入和关闭文件 的库函数。
***从较低层面上
,C可以使用主机操作系统的基本文件工具直接处 理文件,这些直接调用操作系统的函数被称为底层 I/O (low-level I/O)。 由于计算机系统各不相同,所以不可能为普通的底层I/O函数创建标准库, ANSI C也不打算这样做。***
从较高层面上,
C还可以通过标准I/O包 (standard I/O package)来处理文件。这涉及创建用于处理文件的标准模型 和一套标准I/O函数。在这一层面上,具体的C实现负责处理不同系统的差 异,以便用户使用统一的界面。
上面讨论的差异指的是什么?
例如,不同的系统储存文件的方式不同。 有些系统把文件的内容储存在一处,而文件相关的信息储存在另一处;有些 系统在文件中创建一份文件描述。在处理文件方面,有些系统使用单个换行 符标记行末尾,而其他系统可能使用回车符和换行符的组合来表示行末尾。 有些系统用最小字节来衡量文件的大小,有些系统则以字节块的大小来衡 503
量。如果使用标准 I/O 包,就不用考虑这些差异。因此,可以用 if (ch == )检查换行符。即使系统实际用的是回车符和换行符的组合来标记行 末尾,I/O函数会在两种表示法之间相互转换。
从概念上看,C程序处理的是流而不是直接处理文件。流(stream)是 一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的 输入,由属性更统一的流来表示。于是,打开文件的过程就是把流与文件相 关联,而且读写都通过流来完成。
本章着重理解C把输入和输出设备视为存 储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的 文件。stdin流表示键盘输入,stdout流表示屏幕输出。getchar()、putchar()、 printf()和scanf()函数都是标准I/O包的成员,处理这两个流。
以上讨论的内容说明,**可以用处理文件的方式来处理键盘输入。**例如, 程序读文件时要能检测文件的末尾才知道应在何处停止。因此,**C 的输入函 数内置了文件结尾检测器。既然可以把键盘输入视为文件,那么也应该能使 用文件结尾检测器结束键盘输入。下面我们从文件开始,学习如何结束文件。
8.3.2 文件结尾
计算机操作系统要以某种方式判断文件的开始和结束。
***检测文件结尾的 一种方法是:标志位
,在文件末尾放一个特殊的字符标记文件结尾***。CP/M、IBM- DOS和MS-DOS的文本文件曾经用过这种方法。
如今,这些操作系统可以使 用内嵌的Ctrl+Z字符来标记文件结尾。这曾经是操作系统使用的唯一标记, 不过现在有一些其他的选择,例如记录文件的大小。所以现代的文本文件不 一定有嵌入的Ctrl+Z,但是如果有,该操作系统会将其视为一个文件结尾标 记。
程序:
/*该程序获取从键盘输入的字符,并把这些字符发送到屏幕上。程序
使用while 循环,当读到#字符时停止*/
#include "stdio.h"
int main(void)
{
int ch;
while((ch = getchar()) != EOF)
{
putchar(ch);
}
return 0;
}
操作系统使用的另一种方法是储存文件大小的信息
如果文件有3000字 节,程序在读到3000字节时便达到文件的末尾。MS-DOS 及其相关系统使用 这种方法处理二进制文件,因为用这种方法可以在文件中储存所有的字符, 包括Ctrl+Z。新版的DOS也使用这种方法处理文本文件。UNIX使用这种方法 处理所有的文件。
无论操作系统实际使用何种方法检测文件结尾,在C语言中,用 getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写)。scanf()函数检测到文件结尾时也返回EOF。通常, EOF定义 在stdio.h文件中:
#define EOF (-1)
为什么是-1?
因为getchar()函数的返回值通常都介于0~127,这些值对 应标准字符集。但是,如果系统能识别扩展字符集,该函数的返回值可能在 0~255之间。无论哪种情况,-1都不对应任何字符,所以,该值可用于标记 文件结尾。 某些系统也许把EOF定义为-1以外的值,但是定义的值一定与输入字符 所产生的返回值不同。如果包含stdio.h文件,并使用EOF符号,就不必担心 EOF值不同的问题。这里关键要理解EOF是一个值,标志着检测到文件结 尾,并不是在文件中找得到的符号
那么,如何在程序中使用EOF?
把getchar()的返回值和EOF作比较。如 果两值不同,就说明没有到达文件结尾。也就是说,可以使用下面这样的表 达式:while ((ch = getchar()) != EOF)
如果正在读取的是键盘输入不是文件会怎样?绝大部分系统(不是全 部)都有办法***通过键盘模拟文件***结尾条件。
不用定义EOF,因为stdio.h中已经定义过了。 不用担心EOF的实际值,因为EOF在stdio.h中用#define预处理指令定 义,可直接使用,不必再编写代码假定EOF为某值。
变
变量ch的类型从char变为int,因为char类型的变量只能表示0~255的无 符号整数,但是EOF的值是-1。还好,getchar()函数实际返回值的类型是 int,所以它可以读取EOF字符。如果实现使用有符号的char类型,也可以把 ch声明为char类型 (可以定义为unsigned char 也是可以的),但最好还是用更通用的形式。 由于getchar()函数的返回类型是int,如果把getchar()的返回值赋给char类 型的变量,一些编译器会警告可能丢失数据。 ch是整数不会影响putchar(),该函数仍然会打印等价的字符
使用该程序进行键盘输入,要设法输入EOF字符。不能只输入字符 EOF,也不能只输入-1(输入-1会传送两个字符:一个连字符和一个数字 1)。正确的方法是,必须找出当前系统的要求
。例如,在大多数UNIX和 Linux系统中,在一行开始处按下Ctrl+D会传输文件结尾信号。许多微型计算 机系统都把一行开始处的Ctrl+Z识别为文件结尾信号,一些系统把任意位置 的Ctrl+Z解释成文件结尾信号。
每次按下Enter键,系统便会处理缓冲区中储存的字符,并在下一行打 印该输入行的副本。这个过程一直持续到以UNIX风格模拟文件结尾(按下 Ctrl+D)。在PC中,要按下Ctrl+Z。
我们暂停一会。既然echo_eof.c程序能把用户输入的内容拷贝到屏幕 上,那么考虑一下该程序还可以做什么。假设以某种方式把一个文件传送给 它,然后它把文件中的内容打印在屏幕上,当到达文件结尾发现EOF信号时 停止。或者,假设以某种方式把程序的输出定向到一个文件,然后通过键盘 输入数据,用echo_eof.c 来储存在文件中输入的内容。假设同时使用这两种 方法:把输入从一个文件定向到echo_eof.c中,并把输出发送至另一个文 件,然后便可以使用echo_eof.c来拷贝文件。
这个小程序有查看文件内容、 创建一个新文件、拷贝文件的潜力,没想到一个小程序竟然如此多才多艺! 关键是要控制输入流和输出流,这是我们下一个要讨论的主题。
注意 模拟EOF和图形界面
***模拟EOF的概念是在使用文本界面的命令行环境中产生的。***在这种环境 中,用户通过击键与程序交互,由操作系统生成EOF信号。但是在一些实际 应用中,却不能很好地转换成图形界面(如Windows和Macintosh),这些用 户界面包含更复杂的鼠标移动和按钮点击。程序要模拟EOF的行为依赖于编译器和项目类型。例如,Ctrl+Z可以结束输入或整个程序,这取决于特定的 设置。
8.4 重定向和文件
输入和输出涉及函数、数据和设备。
输入函数 getchar()。输出设备(我们假设)是键盘,输入数据流由字符组 成。假设你希望输入函数和数据类型不变,仅改变程序查找数据的位置。那 么,程序如何知道去哪里查找输入?
在默认情况下,
C程序使用标准I/O包查找标准输入作为输入源。
。这就是 前面介绍过的stdin流,它是把数据读入计算机的常用方式。它可以是一个过 时的设备,如磁带、穿孔卡或电传打印机,或者(假设)是键盘,甚至是一 些先进技术,如语音输入。
然而,现代计算机非常灵活,可以让它到别处查 找输入。尤其是,
可以让一个程序从文件中查找输入,而不是从键盘。
程序可以通过两种方式使用文件
第 1 种方法是,显式使用特定的函数 打开文件、关闭文件、读取文件、写入文件,诸如此类。
第2种方法是,设计能与键盘和屏幕互动的程序,通过不同的渠道重定向输入至文件和从文件输出。
换言之,把stdin流重新赋给文 件。继续使用getchar()函数从输入流中获取数据,但它并不关心从流的什么 位置获取数据。虽然这种重定向的方法在某些方面有些限制,但是用起来比 较简单,而且能让读者熟悉普通的文件处理技术。
重定向的一个主要问题与操作系统有关,与C无关
尽管如此,许多C 环境中(包括UNIX、Linux和Windows命令提示模式)都有重定向特性,而 且一些C实现还在某些缺乏重定向特性的系统中模拟它。在UNIX上运行苹果 OS X,可以用UNIX命令行模式启动Terminal应用程序。接下来我们介绍 UNIX、Linux和Windows的重定向
重定向这个地方的代码我是在cmd中写的
1.用记事本写自己的代码并且改后缀名,新手不建议这样写,,记事本写如果出现错误,接下来更改的步骤有点麻烦
2.我用的是vc6.0版本编译器,所以在环境变量中的系统变量配置方面出现了很多问题
3.接下来就是利用vc的c编译器在cmd中先用命令 cl main.c 生成 exe 文件,然后直接执行exe文件就可以运行该程序,重定向的话,根据第一步建立的txt文本文件中的内容实现重定向
方法就是:
注意:是大于号,很神奇的是如果是小于号,就实现了程序向文本文件的重定向,就是程序中写入什么内容,文本文件中就会有什么内容
amazing
这块还需要注意的是,对.c文件尽量不要使用rename (容易出现错误) 不是必须更改的话,还是算了吧!
8.4.1 UNIX、Linux和DOS重定向
UNIX(运行命令行模式时)、Linux(ditto)和Window命令行提示(模 仿旧式DOS命令行环境)都能重定向输入、输出。
重定向输入让程序使用文件而不是键盘来输入,重定向输出让程序输出至文件而不是屏幕。(这说的应该就是上面大于号和小于号重定向的区别)
1.重定向输入
假设已经编译了echo_eof.c 程序,并把可执行版本放入一个名为 echo_eof(或者在Windows系统中名为echo_eof.exe)的文件中。运行该程 序,输入可执行文件名: echo_eof 该程序的运行情况和前面描述的一样,获取用户从键盘输入的输入。现 在,假设你要用该程序处理名为words的文本文件。文本文件(text file)是 内含文本的文件,其中储存的数据是我们可识别的字符。文件的内容可以是 一篇散文或者C程序。内含机器语言指令的文件(如储存可执行程序的文 件)不是文本文件。由于该程序的操作对象是字符,所以要使用文本文件。 只需用下面的命令代替上面的命令即可:
echo_eof < words
<符号是UNIX和DOS/Windows的重定向运算符。
该运算符使words文件 与stdin流相关联,把文件中的内容导入echo_eof程序。echo_eof程序本身并 不知道(或不关心)输入的内容是来自文件还是键盘,它只知道这是需要导 入的字符流,所以它读取这些内容并把字符逐个打印在屏幕上,直至读到文 件结尾。因为C把文件和I/O设备放在一个层面,所以文件就是现在的I/O设备。
注意 重定向 对于UNIX、Linux和Windows命令提示,<两侧的空格是可选的
。一些系 统,如AmigaDOS(那些喜欢怀旧的人使用的系统),支持重定向,但是在 重定向符号和文件名之间不允许有空格。
$是UNIX和Linux的标准提示 符
2.重定向输出
现在假设要用echo_eof把键盘输入的内容发送到名为mywords的文件 中。然后,输入以下命令并开始输入: echo_eof>mywords
>符号是第2个重定向运算符 (这就是上面那个开始你符号错了的问题)
。它创建了一个名为mywords的新文件,然 后把echo_eof的输出(即,你输入字符的副本)重定向至该文件中。重定向 把stdout从显示设备(即,显示器)赋给mywords文件。如果已经有一个名为 mywords的文件,通常会擦除该文件的内容,然后替换新的内容(但是,许 多操作系统有保护现有文件的选项,使其成为只读文件)。所有出现在屏幕 的字母都是你刚才输入的,其副本储存在文件中。在下一行的开始处按下 Ctrl+D(UNIX)或Ctrl+Z(DOS)即可结束该程序。如果不知道输入什么内 容,可参照下面的示例。这里,我们使用UNIX提示符$。记住在每行的末尾 单击Enter键,这样才能把缓冲区的内容发送给程序。
按下Ctrl+D或Ctrl+Z后,程序会结束,你的系统会提示返回。程序是否 起作用了?UNIX的ls命令或Windows命令行提示模式的dir命令可以列出文件 名,会显示mywords文件已存在。可以使用UNIX或Linux的cat或DOS的type命 令检查文件中的内容,或者再次使用echo_eof,这次把文件重定向到程序:
3.组合重定向
现在,假设你希望制作一份mywords文件的副本,并命名为savewords。
只需输入以下命令即可: echo_eof < mywords > savewords 下面的命令也起作用,因为命令与重定向运算符的顺序无关: echo_eof > savewords < mywords 注意:在一条命令中,输入文件名和输出文件名不能相同。 512
echo_eof < mywords > mywords…<–错误 原因是> mywords在输入之前已导致原mywords的长度被截断为0。
在UNIX、Linux或Windows/DOS系统中使用两个重定向运算符 (<和>)时,要遵循以下原则。
重定向运算符连接一个可执行程序(包括标准操作系统命令)和一个数 据文件,
不能用于连接一个数据文件和另一个数据文件,也不能用于连接一 个程序和另一个程序。
使用重定向运算符不能读取多个文件的输入,也不能把输出定向至多个 文件。
通常,文件名和运算符之间的空格不是必须的,除非是偶尔在UNIX shell、Linux shell或Windows命令行提示模式中使用的有特殊含义的字符。例 如,我们用过的echo_eof<words。
以上介绍的都是正确的例子,下面来看一下错误的例子,addup和count 是两个可执行程序,fish和beets是两个文本文件: fish > beets ←违反第1条规则 addup < count ←违反第1条规则 addup < fish < beets ←违反第2条规则 count > beets fish ←违反第2条规则
**UNIX、Linux或Windows/DOS 还有>>运算符,该运算符可以把数据添加 到现有文件的末尾,而 | 运算符能把一个文件的输出连接到另一个文件的输 入。**欲了解所有相关运算符的内容,请参阅 UNIX 的相关书籍,如UNIX Primer Plus,Third Edition(Wilson、Pierce和Wessler合著)。
重定位让你能使用键盘输入程序文件。要完成这一任务,程序要测试文 件的末尾。
例如,第 7 章演示的统计单词程序(程序清单7.7),计算单词 个数直至遇到第1个|字符。把ch的char类型改成int类型,把循环测试中的|替 换成EOF,便可用该程序来计算文本文件中的单词量
4.注释 (暂时了解即可)
重定向是一个命令行概念,因为我们要在命令行输入特殊的符号发出指 令。如果不使用命令行环境,也可以使用重定向。首先,一些集成开发环境 提供了菜单选项,让用户指定重定向。其次,对于 Windows系统,可以打开 命令提示窗口,并在命令行运行可执行文件。Microsoft Visual Studio的默认 设置是把可执行文件放在项目文件夹的子文件夹,称为Debug。文件名和项 目名的基本名相同,文件名的扩展名为.exe。默认情况下,Xcode在给项目 命名后才能命名可执行文件,并将其放在Debug文件夹中。在UNIX系统中, 可以通过Terminal工具运行可执行文件。从使用上看,Terminal比命令行编译 器(GCC或Clang)简单。 如果用不了重定向,可以用程序直接打开文件。程序清单8.3演示了一 个注释较少的示例。我们学到第13章时再详细讲解。待读取的文件应该与可 执行文件位于同一目录。
程序清单8.3 file_eof.c程序
// file_eof.c --打开一个文件并显示该文件
#include <stdio.h>
#include <stdlib.h> // 为了使用exit() int main() {int ch; FILE * fp; 514
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");
exit(1); // 退出程序 }// getc(fp)从打开的文件中获取一个字符
while ((ch = getc(fp)) != EOF)
putchar(ch);
fclose(fp); // 关闭文件 return 0; }
小结:如何重定向输入和输出
绝大部分C系统都可以使用重定向,可以通过操作系统重定向所有程 序,或只在C编译器允许的情况下重定向C程序。假设prog是可执行程序 名,file1和file2是文件名。 把输出重定向至文件:>
prog >file1 把输入重定向至文件:< prog <file2 组合重定向: prog file1 prog >file1 <file2 这两种形式都是把file2作为输入、file1作为输出。 留白: 一些系统要求重定向运算符左侧有一个空格,右侧没有空格。而其他系 统(如,UNIX)允许在重定位运算符两侧有空格或没有空格。