尽管许多人有时会编写一些外观光鲜却实用性不强的程序,但幸运的是,C语言提供了丰富的工具,能够显著提升输入操作的便捷性和处理流程的效率。然而,掌握这些工具也带来了新的挑战,例如理解printf和scanf的便捷形式putchar和getchar等。
采用缓冲输入的方式虽然为用户提供了在将输入内容发送给程序前进行编辑的便利,但在处理这些字符时,它也给程序员带来了额外的负担。正如前面示例所展示的问题,缓冲输入要求用户必须按下Enter键来发送输入,这一动作会同时传递一个换行符,程序需要妥善处理这个潜在的麻烦。让我们以一个猜谜游戏为例来具体说明。在这个游戏中,用户需要选择一个数字,而程序则尝试猜测用户所选的数字。虽然这个程序所采用的算法可能显得有些单调乏味,但我们的关注点主要在于输入和输出部分。下面是猜谜游戏初始版本的代码,稍后我们将对其进行改进。
以下是该程序的运行演示:
请从1到100中选择一个整数。我将尝试猜出它。
如果我的猜测正确,请回答’y’;如果错误,请回答’n’。
嗯…你的数字是1吗?
n
那么,是2吗?
还是3?
n
那么,是4?

是5?
y
我就知道我能猜对!
暂且不论这个程序算法的不足之处,我们先来选择一个数字。请注意,每次输入’n’时,程序都会打印两条消息。这是因为在读取’n’作为用户否定数字1的输入后,程序还会读取一个换行符,将其作为用户否定数字2的输入。
一种解决方法是使用while循环来丢弃输入行末尾剩余的内容,包括换行符。这种方法的优点在于,可以将类似’no’和’no-way’的响应视为简单的’n’,而第一个版本会将这些响应视为两个独立的响应。下面是使用循环修正这个问题的代码:
以下是修正后的程序运行示例:
这种方法确实解决了换行符的问题。然而,该程序仍然会将’f’视为’n’。我们可以通过添加if语句来筛选其他响应。首先,我们添加一个char类型的变量来存储用户的响应:
: char response;
修改后的循环如下:
现在,程序的运行示例如下:
在编写交互式程序时,我们应当预见用户可能会输入错误,并设计程序来处理这些错误输入。当用户出错时,程序应通知用户重新输入。当然,无论你的提示多么清晰,总会有一些人误解并抱怨程序设计得不够好。
假设程序要求使用getchar()处理字符输入,并使用scanf()处理数值输入,这两个函数都能很好地完成任务,但它们不能混用。因为getchar()会读取每个字符,包括空格、制表符和换行符;而scanf()在读取数字时会跳过这些字符。
我们通过程序清单8.5来解释这种情况可能导致的问题。该程序读取一个字符和两个数字,然后根据输入的两个数字指定的行数和列数打印该字符。
注意,该程序以int类型读取字符(这样做可以检测EOF),但却以char类型将字符传递给display()函数。因为char比int类型占用空间小,一些编译器会给出类型转换的警告。可以忽略这些警告,或者使用下面的强制类型转换来消除警告:
在该程序中,main()函数负责获取数据,而display()函数负责打印数据。下面是该程序的一个运行示例,看看会出现什么问题:
输入一个字符和两个整数:
c 2 3
ccc
ccc
输入另一个字符和两个整数;
按回车键退出。
再见。
该程序开始时运行良好。你输入’c 2 3’,程序打印出字符’c’,占据2行3列。然后,程序提示输入第二组数据,但在你输入数据之前程序就退出了!这是怎么回事?又是换行符在作祟,这次是输入行中紧随在3后面的换行符。scanf()函数将这个换行符保留在输入队列中。与scanf()不同,getchar()不会跳过换行符,所以在进入下一轮迭代时,你还没有来得及输入字符,它就读取了换行符,并将其赋值给ch。而ch是终止循环的换行符条件。
为了解决这个问题,程序需要跳过输入结束与下一轮输入开始之间的所有换行符或空格。此外,如果程序在getchar()测试时终止而不是在scanf()阶段终止,效果会更好。修改后的版本如下。
while循环实现了丢弃scanf()输入后面所有字符(包括换行符)的功能,为循环的下一轮读取做好了准备。该程序的运行示例如下:
在if语句中使用一个break语句,可以在scanf()的返回值不等于2时终止程序,即如果一个或两个输入值不是整数或者遇到文件结尾就终止程序。
n
