scanf输入数字后输出有问题?

更详细地介绍输入、输出以及缓冲输入和无缓冲输入的区别
如何通过键盘模拟文件结尾条件
如何使用重定向把程序和文件相连接

为了满足用户五花八门的输入,切实考虑其需求


     在涉及计算机的话题时,我们经常会提到输入(input)和输出 (output)。我们谈论输入和输出设备(如键盘、U盘、扫描仪和激光打印 机),讲解如何处理输入数据和输出数据,讨论执行输入和输出任务的函 数。本章主要介绍用于输入和输出的函数(简称I/O函数)。

      最初,输入/输出函数不是C定义的一部分,C把开发这些函数的任务留 给编译器的实现者来完成。在实际应用中,UNIX 系统中的 C 实现为这些函 数提供了一个模型。ANSI C 库吸取成功的经验,把大量的UNIX I/O函数囊 括其中,包括一些我们曾经用过的。由于必须保证这些标准函数在不同的计 算机环境中能正常工作,所以它们很少使用某些特殊系统才有的特性。因 此,许多C供应商会利用硬件的特性,额外提供一些I/O函数。其他函数或函 数系列需要特殊的操作系统支持,如Winsows或Macintosh OS提供的特殊图 形界面。这些有针对性、非标准的函数让程序员能更有效地使用特定计算机 编写程序。本章只着重讲解所有系统都通用的标准 I/O 函数,用这些函数编 写的可移植程序很容易从一个系统移植到另一个系统。处理文件输入/输出的程序也可以使用这些函数。许多程序都有输入验证,即判断用户的输入是否与程序期望的输入匹配。本章将演示一些与输入验证相关的问题和解决方案。

第 7 章中提到过,getchar()和 putchar()每次只处理一个字符。你可能认为这种方法实在太笨拙了,毕竟与我们的阅读方式相差甚远。但是,这种方法很适合计算机。而且,这是绝大多数文本(即,普通文字)处理程序所用 的核心方法。

为了帮助读者回忆这些函数的工作方式,请看程序清单8.1。 该程序获取从键盘输入的字符,并把这些字符发送到屏幕上。程序使用 while 循环,当读到#字符时停止。

自从ANSI C标准发布以后,C就把stdio.h头文件与使用getchar()和 putchar()相关联,这就是为什么程序中要包含这个头文件的原因(其实, getchar()和 putchar()都不是真正的函数,它们被定义为供预处理器使用的 宏,我们在第16章中再详细讨论)。

运行该程序后,与用户的交互如下:

读者可能好奇,为何输入的字符能直接显示在屏幕上?如果用一个特殊 字符(如,#)来结束输入,就无法在文本中使用这个字符,是否有更好的 方法结束输入?要回答这些问题,首先要了解 C程序如何处理键盘输入,尤其是缓冲和标准输入文件的概念。

8.2 缓冲区 如果在老式系统运行程序清单8.1,你输入文本时可能显示如下:

lliikkee aa # 以上行为是个例外。像这样回显用户输入的字符后立即重复打印该字符 是属于无缓冲(或直接)输入,即正在等待的程序可立即使用输入的字符。 对于该例,大部分系统在用户按下Enter键之前不会重复打印刚输入的字 符,这种输入形式属于缓冲输入。用户输入的字符被收集并储存在一个被称 为缓冲区(buffer)的临时存储区,按下Enter键后,程序才可使用用户输入 的字符。图8.1比较了这两种输入。

 为什么要有缓冲区?首先,把若干字符作为一个块进行传输比逐个发送这些字符节约时间。其次,如果用户打错字符,可以直接通过键盘修正错 误。当最后按下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没有提供调用无缓冲输入的 标准方式,这意味着是否能进行无缓冲输入取决于计算机系统。在这里要对 使用无缓冲输入的朋友说声抱歉,本书假设所有的输入都是缓冲输入

说明:缓冲输入,按下‘enter’键刷新缓冲区,进入输入队列。当输入队列不接受时,输入字符在队列中等待,默认为下一次等待输入的字符,这是为什么要用【while(getchar()!='\n')】或者【scanf("%*s")】吸收多余输入字符的原因

在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实现负责处理不同系统的差 异,以便用户使用统一的界面。

上面讨论的差异指的是什么?例如,不同的系统储存文件的方式不同。 有些系统把文件的内容储存在一处,而文件相关的信息储存在另一处;有些 系统在文件中创建一份文件描述。在处理文件方面,有些系统使用单个换行 符标记行末尾,而其他系统可能使用回车符和换行符的组合来表示行末尾。 有些系统用最小字节来衡量文件的大小,有些系统则以字节块的大小来衡 量。

如果使用标准 I/O 包,就不用考虑这些差异。因此,可以用 if (ch == )检查换行符。即使系统实际用的是回车符和换行符的组合来标记行 末尾,I/O函数会在两种表示法之间相互转换。

从概念上看,C程序处理的是流而不是直接处理文件。流(stream)是 一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的输入,由属性更统一的流来表示。于是,打开文件的过程就是把流与文件相 关联,而且读写都通过流来完成。

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

以上讨论的内容说明,可以用处理文件的方式来处理键盘输入。例如, 程序读文件时要能检测文件的末尾才知道应在何处停止。因此,C 的输入函数内置了文件结尾检测器。既然可以把键盘输入视为文件,那么也应该能使 用文件结尾检测器结束键盘输入。下面我们从文件开始,学习如何结束文件。

说明:C语言把键盘输入,显示输出看作是对文件流stdin,stdout的操作。

计算机操作系统要以某种方式判断文件的开始和结束。检测文件结尾的 一种方法是,在文件末尾放一个特殊的字符标记文件结尾。CP/M、IBMDOS和MS-DOS的文本文件曾经用过这种方法。如今,这些操作系统可以使用内嵌的Ctrl+Z字符来标记文件结尾。这曾经是操作系统使用的唯一标记, 不过现在有一些其他的选择,例如记录文件的大小。所以现代的文本文件不 一定有嵌入的Ctrl+Z,但是如果有,该操作系统会将其视为一个文件结尾标记。图8.2演示了这种方法。 

 操作系统使用的另一种方法是储存文件大小的信息。如果文件有3000字节,程序在读到3000字节时便达到文件的末尾。MS-DOS 及其相关系统使用 这种方法处理二进制文件,因为用这种方法可以在文件中储存所有的字符, 包括Ctrl+Z。新版的DOS也使用这种方法处理文本文件。UNIX使用这种方法 处理所有的文件。

无论操作系统实际使用何种方法检测文件结尾,在C语言中,用 getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写)。scanf()函数检测到文件结尾时也返回EOF。通常, EOF定义 在stdio.h文件中:

为什么是-1?因为getchar()函数的返回值通常都介于0~127,这些值对 应标准字符集。但是,如果系统能识别扩展字符集,该函数的返回值可能在 0~255之间。无论哪种情况,-1都不对应任何字符,所以,该值可用于标记 文件结尾。

某些系统也许把EOF定义为-1以外的值,但是定义的值一定与输入字符 所产生的返回值不同。如果包含stdio.h文件,并使用EOF符号,就不必担心 EOF值不同的问题。这里关键要理解EOF是一个值,标志着检测到文件结 尾,并不是在文件中找得到的符号。

那么,如何在程序中使用EOF?把getchar()的返回值和EOF作比较。如果两值不同,就说明没有到达文件结尾。也就是说,可以使用下面这样的表 达式:

如果正在读取的是键盘输入不是文件会怎样?绝大部分系统(不是全 部)都有办法通过键盘模拟文件结尾条件。了解这些以后,读者可以重写程 序清单8.1的程序,如程序清单8.2所示。

不用定义EOF,因为stdio.h中已经定义过了。

不用担心EOF的实际值,因为EOF在stdio.h中用#define预处理指令定 义,可直接使用,不必再编写代码假定EOF为某值。

变量ch的类型从char变为int,因为char类型的变量只能表示0~255的无 符号整数,但是EOF的值是-1。还好,getchar()函数实际返回值的类型是 int,所以它可以读取EOF字符。如果实现使用有符号的char类型,也可以把 ch声明为char类型,但最好还是用更通用的形式。

由于getchar()函数的返回类型是int,如果把getchar()的返回值赋给char类 型的变量,一些编译器会警告可能丢失数据。

ch是整数不会影响putchar(),该函数仍然会打印等价的字符。 //char字符型,int整型范围更大

使用该程序进行键盘输入,要设法输入EOF字符。不能只输入字符 EOF,也不能只输入-1(输入-1会传送两个字符:一个连字符和一个数字 1)。正确的方法是,必须找出当前系统的要求。例如,在大多数UNIX和 Linux系统中,在一行开始处按下Ctrl+D会传输文件结尾信号。许多微型计算 机系统都把一行开始处的Ctrl+Z识别为文件结尾信号,一些系统把任意位置

下面是在UNIX系统下运行echo_eof.c程序的缓冲示例:

每次按下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可以结束输入或整个程序,这取决于特定的设置。

说明:因为对文件末尾的操作比较多,所以专门做个小结:

1.文件末尾的概念是来源于对文件的操作必须有个结尾,其意义是到了文件到了末尾无法再被写入数据或者读出数据。

2.C语言对文件末尾的标志是EOF,往往在头文件里有定义#define EOF -1

3.在语句中如何表示文件末尾,和文件指针相关。首先声明文件指针FILE *fp=("文件名filename",“打开模式”),

识别文件末尾:1)从文件中读取字符时(键盘读取(标准输入)或其他文件读取),对于标准输入文件stdin,到了文件末尾(ch=getchar())==EOF;对于其他文件末尾(ch=getc(fp))==EOF;文件末尾的字符标志为EOF;如果用文件指针标识文件末尾,字符读写的文件末尾检测,都可以用if(!feof(fp))表示未到文件末尾,feof(fp)等于0表示到文件末尾

2)读取字符串fgets()函数---其返回值是字符指针,如果到了文件末尾返回值是空指针NULL。写入字符串的文件,如果想加文件末尾判断,对文件指针进行一些操作判断能否够长度写入。

4.标准输入文件末尾的产生,即通过键盘输入文件时,用 Ctrl+Z

8.5 创建更友好的用户界面

大部分人偶尔会写一些中看不中用的程序。还好,C提供了大量工具让 输入更顺畅,处理过程更顺利。不过,学习这些工具会导致新的问题。本节的目标是,指导读者解决这些问题并创建更友好的用户界面,让交互数据输入更方便,减少错误输入的影响。

缓冲输入用起来比较方便,因为在把输入发送给程序之前,用户可以编 辑输入。但是,在使用输入的字符时,它也会给程序员带来麻烦。前面示例中看到的问题是,缓冲输入要求用户按下Enter键发送输入。这一动作也传送了换行符,程序必须妥善处理这个麻烦的换行符。我们以一个猜谜程序为 例。用户选择一个数字,程序猜用户选中的数字是多少。该程序使用的方法 单调乏味,先不要在意算法,我们关注的重点在输入和输出。在编写交互式程序时,应该事先预料到用户可能会输入错误,然后设计程序处理用户的错误输入。在用户出错时通知用户再次输入。 当然,无论你的提示写得多么清楚,总会有人误解,然后抱怨这个程序 设计得多么糟糕。

8.5.2 混合数值和字符输入

假设程序要求用 getchar()处理字符输入,用 scanf()处理数值输入,这两 个函数都能很好地完成任务,但是不能把它们混用。因为 getchar()读取每个 字符,包括空格、制表符和换行符;而 scanf()在读取数字时则会跳过空格、 制表符和换行符。

我们通过程序清单8.5来解释这种情况导致的问题。该程序读入一个字 符和两个数字,然后根据输入的两个数字指定的行数和列数打印该字符。

注意,该程序以 int 类型读取字符(这样做可以检测 EOF),但是却以 char 类型把字符传递给display()函数。因为char比int小,一些编译器会给出 类型转换的警告。可以忽略这些警告,或者用下面的强制类型转换消除警告:

在该程序中,main()负责获取数据,display()函数负责打印数据。下面 是该程序的一个运行示例,看看有什么问题:

该程序开始时运行良好。你输入c 2 3,程序打印c字符2行3列。然后, 程序提示输入第2组数据,还没等你输入数据程序就退出了!这是什么情 况?又是换行符在捣乱,这次是输入行中紧跟在 3 后面的换行符。scanf()函 数把这个换行符留在输入队列中。和 scanf()不同,getchar()不会跳过换行 符,所以在进入下一轮迭代时,你还没来得及输入字符,它就读取了换行 符,然后将其赋给ch。而ch是换行符正式终止循环的条件。

要解决这个问题,程序要跳过一轮输入结束与下一轮输入开始之间的所有换行符或空格。另外,如果该程序不在getchar()测试时,而在scanf()阶段 终止程序会更好。修改后的版本如程序清单8.6所示。

while循环实现了丢弃scanf()输入后面所有字符(包括换行符)的功能, 为循环的下一轮读取做好了准备。该程序的运行示例如下:

在if语句中使用一个break语句,可以在scanf()的返回值不等于2时终止 程序,即如果一个或两个输入值不是整数或者遇到文件结尾就终止程序。

说明:程序中红色部分注释为我的改动;按照while的作用,在输入不正确时给出提示(没写进去,可以添加’printf("Please reinput all");'书中程序含义是两个整数输入不正确就直接跳出循环往下执行了(break;),我改动后的程序要求重新输入。

// 对三个参数的输入处理,下面一个程序是对一个参数单独处理

在实际应用中,用户不一定会按照程序的指令行事。用户的输入和程序 期望的输入不匹配时常发生,这会导致程序运行失败。作为程序员,除了完 成编程的本职工作,还要事先预料一些可能的输入错误,这样才能编写出能 检测并处理这些问题的程序。

例如,假设你编写了一个处理非负数整数的循环,但是用户很可能输入 一个负数。你可以使用关系表达式来排除这种情况:

另一类潜在的陷阱是,用户可能输入错误类型的值,如字符 q。排除这 种情况的一种方法是,检查scanf()的返回值。回忆一下,scanf()返回成功读 取项的个数。因此,下面的表达式当且仅当用户输入一个整数时才为真:

结合上面的while循环,可改进为:

while循环条件可以描述为“当输入是一个整数且该整数为正时”。

对于最后的例子,当用户输入错误类型的值时,程序结束。然而,也可 以让程序友好些,提示用户再次输入正确类型的值。在这种情况下,要处理 有问题的输入。如果scanf()没有成功读取,就会将其留在输入队列中。这里 要明确,输入实际上是字符流。可以使用getchar()函数逐字符地读取输入, 甚至可以把这些想法都结合在一个函数中,如下所示:

//对1个参数的输入处理,包含了吸收,重现,提示。

说明:这是友好的用户输入写法,当输入不符合要求时,重现了输入字符。和上一个程序接受了3个参数相比,这种写法适合1个参数,因为他显示的是按照字符来显示一个字符串,如果超过1个参数情况不确定。而且调用函数必定得到一个正确的输入值。

该函数要把一个int类型的值读入变量input中。如果读取失败,函数则进 入外层while循环体。然后内层循环逐字符地读取错误的输入。注意,该函 数丢弃该输入行的所有剩余内容。还有一个方法是,只丢弃下一个字符或单 词,然后该函数提示用户再次输入。外层循环重复运行,直到用户成功输入 整数,此时scanf()的返回值为1。

在用户输入整数后,程序可以检查该值是否有效。考虑一个例子,要求 用户输入一个上限和一个下限来定义值的范围。在该例中,你可能希望程序 检查第1个值是否大于第2个值(通常假设第1个值是较小的那个值),除此 之外还要检查这些值是否在允许的范围内。例如,当前的档案查找一般不会 接受 1958 年以前和2014年以后的查询任务。这个限制可以在一个函数中实 现。

假设程序中包含了stdbool.h 头文件。如果当前系统不允许使用_Bool, 把bool替换成int,把true 替换成 1,把 false 替换成 0 即可。注意,如果输入 无效,该函数返回 true,所以函数名为bad_limits():

程序清单8.7使用了上面的两个函数为一个进行算术运算的函数提供整 数,该函数计算特定范围内所有整数的平方和。程序限制了范围的上限是 ,下限是-。

// 验证输入是一个整数 // 验证范围的上下限是否有效 // 计算a~b之间的整数平方和

虽然checking.c程序的核心计算部分(sum_squares()函数)很短,但是输 入验证部分比以往程序示例要复杂。接下来分析其中的一些要素,先着重讨论程序的整体结构。 程序遵循模块化的编程思想,使用独立函数(模块)来验证输入和管理显示。程序越大,使用模块化编程就越重要。

main()函数管理程序流,为其他函数委派任务它使用 get_long()获取 值while 循环处理值badlimits()函数检查值是否有效sum_squres()函数 处理实际的计算:

说明:该程序体现了C语言的模块化编程思想

在编写处理错误输入的代码时(如程序清单8.7),应该很清楚C是如何 处理输入的。考虑下面的输入:

在我们眼中,这就像是一个由字符、整数和浮点数组成的字符串。但是 对 C程序而言,这是一个字节流。第1个字节是字母i的字符编码,第2个字 节是字母s的字符编码,第3个字节是空格字符的字符编码,第4个字节是数 字2的字符编码,等等。所以,如果get_long()函数处理这一行输入,第1个 字符是非数字,那么整行输入都会被丢弃,包括其中的数字,因为这些数字 只是该输入行中的其他字符:

虽然输入流由字符组成,但是也可以设置scanf()函数把它们转换成数 值。例如,考虑下面的输入:

如果在scanf()函数中使用%c转换说明,它只会读取字符4并将其储存在 char类型的变量中。如果使用%s转换说明,它会读取字符4和字符2这两个字 符,并将其储存在字符数组中。如果使用%d转换说明,scanf()同样会读取 两个字符,但是随后会计算出它们对应的整数值:4×10+2,即42,然后将 表示该整数的二进制数储存在 int 类型的变量中。如果使用%f 转换说明, 539 scanf()也会读取两个字符,计算出它们对应的数值42.0,用内部的浮点表示 法表示该值,并将结果储存在float类型的变量中。

简而言之,输入由字符组成,但是scanf()可以把输入转换成整数值或浮点数值。使用转换说明(如%d或%f)限制了可接受输入的字符类型,而 getchar()和使用%c的scanf()接受所有的字符。

说明:通过键盘输入的都是字符,通过scanf()函数的转换说明(前面说转换说明就是翻译说明),计算机将输入字符识别成不同类型的数据。

许多计算机程序都把菜单作为用户界面的一部分。菜单给用户提供方便 的同时,却给程序员带来了一些麻烦。我们看看其中涉及了哪些问题。

菜单给用户提供了一份响应程序的选项。假设有下面一个例子:

理想状态是,用户输入程序所列选项之一,然后程序根据用户所选项完 成任务。作为一名程序员,自然希望这一过程能顺利进行。因此,第1个目 标是:当用户遵循指令时程序顺利运行;第2个目标是:当用户没有遵循指 令时,程序也能顺利运行。显而易见,要实现第 2 个目标难度较大,因为很 难预料用户在使用程序时的所有错误情况。

现在的应用程序通常使用图形界面,可以点击按钮、查看对话框、触摸 图标,而不是我们示例中的命令行模式。但是,两者的处理过程大致相同: 给用户提供选项、检查并执行用户的响应、保护程序不受误操作的影响。除 了界面不同,它们底层的程序结构也几乎相同。但是,使用图形界面更容易 通过限制选项控制输入。

//图形界面输入能更好控制用户输入,但是C语言没有啥界面

我们来更具体地分析一个菜单程序需要执行哪些任务。它要获取用户的 响应,根据响应选择要执行的动作。另外,程序应该提供返回菜单的选项。 C 的 switch 语句是根据选项决定行为的好工具,用户的每个选择都可以对应 一个特定的case标签。使用while语句可以实现重复访问菜单的功能。因此, 我们写出以下伪代码:

当你决定实现这个程序时,就要开始考虑如何让程序顺利运行(顺利运 行指的是,处理正确输入和错误输入时都能顺利运行)。例如,你能做的是 让“获取选项”部分的代码筛选掉不合适的响应,只把正确的响应传入 switch。这表明需要为输入过程提供一个只返回正确响应的函数。结合while 循环和switch语句,其程序结构如下:

定义get_choice()函数只能返回'a'、'b'、'c'和'q'。get_choice()的用法和 getchar()相同,两个函数都是获取一个值,并与终止值(该例中是'q')作比 较。我们尽量简化实际的菜单选项,以便读者把注意力集中在程序结构上。 稍后再讨论 count()函数。default 语句可以方便调试。如果get_choice()函数没 能把返回值限制为菜单指定的几个选项值,default语句有助于发现问题所在。

下面的伪代码是设计这个函数的一种方案:

下面是一个简单而笨拙的实现

缓冲输入依旧带来些麻烦,程序把用户每次按下 Return 键产生的换行符视为错误响应。为了让程序的界面更流畅,该函数应该跳过这些换行符。  //没有解决换行符的问题

这类问题有多种解决方案。一种是用名为get_first()的新函数替换 getchar()函数读取一行的第1个字符并丢弃剩余的字符。这种方法的优点 是,把类似act这样的输入视为简单的a,而不是继续把act中的c作为选项c的 一个有效的响应。我们重写输入函数如下:

8.7.3 混合字符和数值输入

前面分析过混合字符和数值输入会产生一些问题,创建菜单也有这样的 问题。例如,假设count()函数(选择c)的代码如下

//以上代码不好的地方在没有吸收包含enter在内的多余字符,当输入有误时把错误输入留在输入队列里

如果输入3作为响应,scanf()会读取3并把换行符留在输入队列中。下次 调用 get_choice()将导致get_first()返回这个换行符,从而导致我们不希望出现的行为。

重写 get_first(),使其返回下一个非空白字符而不仅仅是下一个字符, 即可修复这个问题。我们把这个任务留给读者作为练习。

另一种方法是,在 count()函数中清理换行符,如下所示:

该函数借鉴了程序清单8.7中的get_long()函数,将其改为get_int()获取int 类型的数据而不是long类型的数据。回忆一下,原来的get_long()函数如何检 查有效输入和让用户重新输入。

程序清单8.8演示了菜单程序的最终版本。

下面是该程序的一个运行示例:

要写出一个自己十分满意的菜单界面并不容易。但是,在开发了一种可 行的方案后,可以在其他情况下复用这个菜单界面。

学完以上程序示例后,还要注意在处理较复杂的任务时,如何让函数把任务委派给另一个函数这样让程序更模块化。

C程序把输入作为传入的字节流。getchar()函数把每个字符解释成一个 字符编码。scanf()函数以同样的方式看待输入,但是根据转换说明,它可以 把字符输入转换成数值。许多操作系统都提供重定向,允许用文件代替键盘 输入,用文件代替显示器输出。

程序通常接受特殊形式的输入。可以在设计程序时考虑用户在输入时可 能犯的错误,在输入验证部分处理这些错误情况,让程序更强健更友好。

对于一个小型程序,输入验证可能是代码中最复杂的部分。处理这类问 题有多种方案。例如,如果用户输入错误类型的信息,可以终止程序,也可 以给用户提供有限次或无限次机会重新输入。

许多程序使用 getchar()逐字符读取输入。通常,系统使用行缓冲输入, 即当用户按下 Enter 键后输入才被传送给程序。按下Enter键也传送了一个换 行符,编程时要注意处理这个换行符。ANSI C把缓冲输入作为标准。

通过标准I/O包中的一系列函数,以统一的方式处理不同系统中的不同 文件形式,是C语言的特性之一。getchar()和 scanf()函数也属于这一系列。 当检测到文件结尾时,这两个函数都返回 EOF(被定义在stdio.h头文件 中)。在不同系统中模拟文件结尾条件的方式稍有不同。在UNIX系统中, 在一行开始处按下Ctrl+D可以模拟文件结尾条件;而在DOS系统中则使用

许多操作系统(包括UNIX和DOS)都有重定向的特性,因此可以用文件代替键盘和屏幕进行输入和输出。读到EOF即停止读取的程序可用于键盘 输入和模拟文件结尾信号,或者用于重定向文件。

混合使用 getchar()和 scanf()时,如果在调用 getchar()之前,scanf()在输 入行留下一个换行符,会导致一些问题。不过,意识到这个问题就可以在程 序中妥善处理。

编写程序时,要认真设计用户界面。事先预料一些用户可能会犯的错 误,然后设计程序妥善处理这些错误情况。

}

我要回帖

更多关于 c语言scanf连续输入 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信