15.1.4 流式输入(2)
2. 输入方法
与输出流一样,输入流也提供了一些方法,通过这些方法可以获得比普通>>运算符更底层的访问。
get()
get()方法允许从流中读入原始输入数据。get()最简单的版本返回流中的下一个字符,还有其他版本一次读入多个字符。get()常用于避免>>运算符的自动标志化。例如,下面这个函数从输入流中读入一个由多个词构成的名字,直到读到流尾。
- string readName(istream& inStream)
- {
- string name;
- while (inStream.good()) {
- int next = inStream.get();
- if (next == EOF)
- break;
- name += next;// Implicitly convert to a char and append.
- }
- return name;
- }
-
- 代码取自Get\Get.cpp
在这个readName()函数中,有一些有趣的发现:
这个函数的参数是一个对istream的非const引用,而不是一个const引用。从一个流读入数据的方法会改变实际的流(主要改变当前位置),因为这些方法都不是const方法。因此,不能对const引用调用这些方法。
get()的返回值保存在一个int中,而不是一个char中。因为get()会返回一些特殊的非字符的值,例如EOF(文件尾),因此使用int。当next被追加到一个string的时候,被隐式地转换为一个char;如果被追加到一个wstring,则会被转换为一个wchar_t。
readName()有一点奇怪,因为可以采用两种方式跳出循环。一种方式是流进入"不好的"状态,另一种方式是达到流尾。另一种从流中读入数据的更常用的方法是使用另一个版本的get(),这个版本接受一个字符的引用,并且返回一个流的引用。这种模式利用了一个事实:在条件环境中对一个输入流求值的时候,只有当输入流可以用于下一次读取的时候才会返回true。如果遇到错误或者到达文件尾都会使得流求值为false。第18章讲解了实现这个特性所需要的转换操作的底层细节。同一个函数的下面这个版本稍微简洁一些:
- string readName(istream& inStream)
- {
- string name;
- char next;
- while (inStream.get(next)) {
- name += next;
- }
- return name;
- }
-
- 代码取自Get\Get.cpp
unget()
对于大多数场合来说,理解输入流的正确方式是将输入流理解为一个单方向的滑槽。数据丢入滑槽,然后进入变量。unget()方法打破了这个模型,允许将数据塞回滑槽。
调用unget()导致流回退一个位置,将前一个读入的字符放回到流中。通过调用fail()方法可以查看unget()是否成功。例如,如果当前位置就是流的起始位置,那么unget()会失败。
本章前面出现的getReservationData()函数不允许输入一个带有空白字符的名字。下面的代码使用了unget(),允许名字中出现空白字符。这段代码逐字符读入,并检查字符是否为数字。如果字符不是数字,则将字符添加到guestName。如果字符是数字,则通过unget()将这个字符放回到流中,循环停止,然后通过>>运算符输入一个整数partySize。后面的"输入操作算子"小节将讨论noskipws的意义。
- void getReservationData()
- {
- string guestName;
- int partySize = 0;
- // Read letters until we find a non-letter
- char ch;
- cin >> noskipws;
- while (cin >> ch) {
- if (isdigit(ch)) {
- cin.unget();
- if (cin.fail())
- cout << "unget() failed" << endl;
- break;
- }
- guestName += ch;
- }
- // Read partysize
- cin >> partySize;
- cout << "Thank you '" << guestName
- << "', party of " << partySize << endl;
- if (partySize > 10) {
- cout << "An extra gratuity will apply." << endl;
- }
- }
-
- 代码取自Unget\Unget.cpp