27-文件尾条件
5.5.4 文件尾条件
程序清单5.17表明,使用诸如#等符号来表示输入结束很难令人满意,因为这样的符号可能就是合法输入的组成部分,其他符号(如@和%)也如此。如果输入来自于文件,则可以使用一种功能更强大的技术——检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。
乍一看,读取文件中的信息似乎同cin和键盘输入没什么关系,但其实存在两个相关的地方。首先,很多操作系统(包括Unix、Linux和Windows命令提示符模式)都支持重定向,允许用文件替换键盘输入。例如,假设在Windows中有一个名为gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以在命令提示符模式下输入下面的命令:
gofish <fishtale
这样,程序将从fishtale文件(而不是键盘)获取输入。<符号是Unix和Windows命令提示符模式的重定向运算符。
其次,很多操作系统都允许通过键盘来模拟文件尾条件。在Unix中,可以在行首按下Ctrl+D来实现;在Windows命令提示符模式下,可以在任意位置按Ctrl+Z和Enter。有些C++实现支持类似的行为,即使底层操作系统并不支持。键盘输入的EOF概念实际上是命令行环境遗留下来的。然而,用于Mac的Symantec C++模拟了UNIX,将Ctrl+D视为仿真的EOF。Metrowerks Codewarrior能够在Macintosh和Windows环境下识别Ctrl+Z。用于PC的Microsoft Visual C++、Borland C++ 5.5和GNU C++ 都能够识别行首的Ctrl + Z,但用户必须随后按下回车键。总之,很多PC编程环境都将Ctrl+Z视为模拟的EOF,但具体细节(必须在行首还是可以在任何位置,是否必须按下回车键等)各不相同。
如果编程环境能够检测EOF,可以在类似于程序清单5.17的程序中使用重定向的文件,也可以使用键盘输入,并在键盘输入中模拟EOF。这一点似乎很有用,因此我们来看看究竟如何做。
检测到EOF后,cin将两位(eofbit和failbit)都设置为1。可以通过成员函数eof()来查看eofbit是否被设置;如果检测到EOF,则cin.eof()将返回bool值true,否则返回false。同样,如果eofbit或failbit被设置为1,则fail()成员函数返回true,否则返回false。注意,eof()和fail()方法报告最近读取的结果;也就是说,它们在事后报告,而不是预先报告。因此应将cin.eof()或cin.fail()测试放在读取后,程序清单5.18中的设计体现了这一点。它使用的是fail(),而不是eof(),因为前者可用于更多的实现中。
注意: 有些系统不支持来自键盘的模拟EOF;有些系统对其支持不完善。cin.get()可以用来锁住屏幕,直到可以读取为止,但是这种方法在这里并不适用,因为检测EOF时将关闭对输入的进一步读取。然而,可以使用程序清单5.14中那样的计时循环来使屏幕在一段时间内是可见的。也可使用cin.clear()来重置输入流,这将在第6章和第17章介绍。
程序清单5.18 textin3.cpp
// textin3.cpp -- reading chars to end of file
#include <iostream>
int main()
{
using namespace std;
char ch;
int count = 0;
cin.get(ch); // attempt to read a char
while (cin.fail() == false) // test for EOF
{
cout << ch; // echo character
++count;
cin.get(ch); // attempt to read another char
}
cout << endl << count << " characters read\n";
return 0;
}
下面是该程序的运行情况:
The green bird sings in the winter.<ENTER>
The green bird sings in the winter.
Yes, but the crow flies in the dawn.<ENTER>
Yes, but the crow flies in the dawn.
<CTRL>+<Z><ENTER>
73 characters read
这里在Windows 7系统上运行该程序,因此可以按下Ctrl+Z和回车键来模拟EOF条件。请注意,在Unix和类Unix(包括Linux和Cygwin)系统中,用户应按Ctrl+Z组合键将程序挂起,而命令fg恢复执行程序。
通过使用重定向,可以用该程序来显示文本文件,并报告它包含的字符数。下面,我们在Unix系统运行该程序,并对一个两行的文件进行读取、回显和计算字数($是Unix提示符):
$ textin3 < stuff
I am a Unix file. I am proud
to be a Unix file.
48 characters read
$
1.EOF结束输入
前面指出过,cin方法检测到EOF时,将设置cin对象中一个指示EOF条件的标记。设置这个标记后,cin将不读取输入,再次调用cin也不管用。对于文件输入,这是有道理的,因为程序不应读取超出文件尾的内容。然而,对于键盘输入,有可能使用模拟EOF来结束循环,但稍后要读取其他输入。cin.clear()方法可以清除EOF标记,使输入继续进行。这将在第17章详细介绍。不过要记住的是,在有些系统中,按Ctrl+Z实际上将结束输入和输出,而cin.clear()将无法恢复输入和输出。
2.常见的字符输入做法
每次读取一个字符,直到遇到EOF的输入循环的基本设计如下:
cin.get(ch); // attempt to read a char
while (cin.fail() == false) // test for EOF
{
... // do stuff
cin.get(ch); // attempt to read another char
}
可以在上述代码中使用一些简捷方式。第6章将介绍的!运算符可以将true切换为false或将false切换为true。可以使用此运算符将上述while测试改写成这样:
while (!cin.fail()) // while input has not failed
方法cin.get(char)的返回值是一个cin对象。然而,istream类提供了一个可以将istream对象(如cin)转换为bool值的函数;当cin出现在需要bool值的地方(如在while循环的测试条件中)时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的bool值为true;否则为false。这意味着可以将上述while测试改写为这样:
while (cin) // while input is successful
这比! cin.fail()或!cin.eof()更通用,因为它可以检测到其他失败原因,如磁盘故障。
最后,由于cin.get(char)的返回值为cin,因此可以将循环精简成这种格式:
while (cin.get(ch)) // while input is successful
{
... // do stuff
}
这样,cin.get(char)只被调用一次,而不是两次:循环前一次、循环结束后一次。为判断循环测试条件,程序必须首先调用cin.get(ch)。如果成功,则将值放入ch中。然后,程序获得函数调用的返回值,即cin。接下来,程序对cin进行bool转换,如果输入成功,则结果为true,否则为false。三条指导原则(确定结束条件、对条件进行初始化以及更新条件)全部被放在循环测试条件中。