《C++ Primer Plus》第17章:输入、输出和文件(3)

本文详细介绍了C++中cin对象如何进行输入处理,包括格式化输入函数如何转换不同类型的数据,以及如何检查和管理流状态。当输入不匹配或到达文件尾时,cin会设置相应的状态位,如failbit或eofbit。程序可以通过检查这些状态位来决定如何处理不适当的输入,如使用clear()方法重置流状态,或者设置异常处理机制。同时,文章提到了get()和getline()等非格式化输入函数,以及如何利用peek()、gcount()和putback()等方法辅助处理输入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用cin进行输入

现在来介绍输入,即如何给程序提供数据。cin对象将标准输入表示为字节流。通常情况下,通过键盘来生成这种字符流。如果键入字符序列 2011,cin 对象将从输入流中抽取这几个字符。输入可以是字符串的一部分、int值、float值,也可以是其他类型。因此,抽取还涉及了类型转换。cin 对象根据接收值的变量的类型,使用其方法将字符序列转换为所需的类型。

通常,可以这样使用 cin:

cin >> value_holder;

其中,value_holder 为存储输入的内存单元,它可以是变量、引用、被解除引用的指针,也可以是类或结构的成员。cin 解释输入的方式取决于 value_holder 的数据类型。istream 类(在iostream头文件中定义)重载了抽取运算符 >>,使之能够识别下面这些基本类型:

  • signed char &;
  • unsigned char &;
  • char &;
  • short &;
  • unsigned short &;
  • int &;
  • unsigned int &;
  • long &;
  • unsigned long &;
  • long long & (C++11);
  • unsigned long long & (C++11);
  • float &;
  • double &;
  • long double &

这些运算符函数被称为格式化输入函数(formatted input functions),因为它们可以将输入数据转换为目标指定的格式。

典型的运算符函数的原型如下:

istream & operator>>(int &);

参数和返回值都是引用。引用参数(参见第8章)意味着下面这样的语句将导致 operator>>() 函数处理变量 staff_size 本身,而不是像常规参数那样处理它的副本:

cin >> staff_size;

由于参数类型为引用,因此 cin 能够直接修改用作参数的变量的值。例如,上述语句将直接修改变量 staff_size 的值。稍后将介绍引用返回值的重要意义。首先来看抽取运算符的类型转换方面。对于上述列出的各种类型的参数,抽取运算符将字符输入转换为指定类型的值。例如,假设 staff_size 的类型为 int,则编译器将:

cin >> staff_size;

与下面的原型匹配:

istream & operator>>(int &);

对应于上述原型的函数将读取发送给程序的字符流(假设为字符2、3、1、8 和 4 )。对于使用 2 字节 int 的系统来说,函数将把这些字符转换为整数 23184 的 2 字节二进制表示。如果 staff_size 的类型为 double,则 cin 将使用 operator>>(double &) 将上述输入转换为值 23184.0 的 8 字节浮点表示。

顺便说一句,可以将 hex、oct 和 dec 控制符与 cin 一起使用,来指定将整数输入解释为十六进制、八进制还是十进制格式。例如,下面的语句将输入12或0x12解释为十进制的12,而将 ff 或 FF 解释为十进制的255:

cin >> hex;

istream 类还为下列字符指针类型重载了 >> 抽取运算符:

  • signed char *;
  • char *;
  • unsigned char *。

对于这种类型的参数,抽取运算符将读取输入中的下一个单词,将它放置到指定的地址,并加上一个空值字符,使之成为一个字符串。例如,假设有这样一段代码:

cout << "Enter your first name:\n";
char name[20];
cin >> name;

如果通过键入 Liz 来进行响应,则抽取运算符将把字符 Liz\0 放到 name 数组中( \0 表示末尾的空值字符)。name 标识符是一个 char 数组名,可作为数组第一个元素的地址,这使 name 的类型为 char*(指向 char 的指针)。

每个抽取运算符都返回调用对象的引用,这使得能够将输入拼接起来,就像拼接输出那样:

char name[20];
float fee;
int group;
cin >> name >> fee >> group;

其中,cin>>name 返回的 cin 对象成了处理 fee 的对象。

cin>>如何检查输入

不同版本的抽取运算符查看输入流的方法是相同的。它们跳过空白(空格、换行符和制表符),直到遇到非空白字符。即使对于单字符模式(参数类型为 char、unsigned char 或 signed char),情况也是如此,但对于 C 语言的字符输入函数,情况并非如此。在单字符模式下,>> 运算符将读取该字符,将它放置到指定的位置。在其他模式下,>> 运算符将读取一个指定类型的数据。也就是说,它读取从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容。

例如,对于下面的代码:

int elevation;
cin >> elevation;

假设键入下面的字符:

-123Z

运算符将读取字符-、1、2 和 3,因为它们都是整数的有效部分。但 Z 字符不是有效字符,因此输入中最后一个可接受的字符是3。Z将留在输入流中,下一个 cin 语句将从这里开始读取。与此同时,运算符将字符序列 -123 转换为一个整数值,并将它赋给 elevation。

输入有时可能没有满足程序的期望。例如,假设输入的是 Zcar,而不是 -123Z。在这种情况下,抽取运算符将不会修改 elevation 的值,并返回 0(如果 istream 对象的错误状态被设置,if 或 while 语句将判定该对象为 false,这将在本章后面做更详细的介绍)。返回值 false 让程序能够检车输入是否满足要求,如下程序所示:

// check_it.cpp -- checking for valid input
#include<iostream>

int main() {
    using namespace std;
    cout << "Enter numbers: ";

    int sum = 0;
    int input;

    while(cin >> input) {
        sum += input;
    }

    cout << "Last value entered = " << input << endl;
    cout << "Sum = " << sum << endl;

    return 0;
}

下面是输入流中包含不适当输入(-123Z)时程序的输出:

Enter numbers: 200
10 -50 -123Z 60
Last value entered = 0
Sum = 37

由于输入被缓冲。因此通过键盘输入的第二行在用户按下回车键之前,不会被发送给程序。然而,循环在字符Z处停止了对输入的处理,因此它不与任何一种浮点格式匹配。输入与预期格式不匹配反过来将导致表达式 cin>>input 的结果为 false,因此 while 循环被终止。

流状态

我们来进一步看看不适当的输入会造成什么后果。cin 或 cout 对象包含一个描述流状态(stream state)的数据成员(从 ios_base 那里继承的)。流状态(被定义为 iostate 类型,而 iostate 是一种 bitmask 类型)由 3 个 ios_base 元素组成:eofbit、badbit 或 failbit,其中每个元素都是一位,可以是 1(设置)或 0(清除)。当 cin 操作到达文件末尾时,它将设置 eofbit;当 cin 操作未能读取到预期的字符时(像前一个例子那样),它将设置 failbit。I/O 失败(如试图读取不可访问的文件或试图写入写保护的磁盘),也可能将 failbit 设置为1。在一些无法诊断的失败破坏流时,badbit 元素将被设置(实现没有必要就哪些情况下设置 failbit,哪些情况下设置 badbit 达成一致)。当全部 3 个状态位都设置为 0 时,说明一切顺利。程序可以检查流状态,并使用这种信息来决定下一步做什么。下表列出了这些位和一些报告或改变流状态的 ios_base 方法。

流状态

成员描述
eofbit如果到达文件尾,则设置为1
badbit如果流被破坏,则设置为1;例如,文件读取错误
failbit如果输入操作未能读取预期的字符或输出操作没有写入预期的字符,则设置为1
goodbit另一种表示 0 的方法
good()如果流可以使用(所有的位都被清除),则返回 true
eof()如果 eofbit 被设置,则返回 true
bad()如果 badbit 被设置,则返回 true
fail()如果 badbit 或 failbit 被设置,则返回 true
rdstate()返回流状态
exceptions()返回一个bitmask,指出哪些标记导致异常被引发
exceptions(isostate ex)设置哪些状态将导致 clear() 引发异常;例如,如果 ex 是 eofbit,则如果 eofbit 被设置,clear() 将引发异常
clear(iostate s)将流状态设置为 s;s 的默认值为 0(goodbit);如果(rdstate()& exceptions() )!= 0,则引发异常 basic_ios::failure
setstate(iostate s)调用 clear (rdstate()
  1. 设置状态
    上表中的两种方法——clear() 和 setstate() 很相似。它们都重置状态,但采取的方式不同。clear() 方法将状态设置为它的参数。因此,下面的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit 和 failbit):

    clear();
    

    同样,下面的调用将状态设置为 eofbit;也就是说,eofbit 将被设置,另外两个状态位被清除:

    clear(eofbit);
    

    而 setstate() 方法只影响其参数中已设置的位。因此,下面的调用将设置 eofbit,而不会影响其他位:

    setstate(eofbit);
    

    因此,如果 failbit 被设置,则仍将被设置。
    为什么需要重新设置流状态呢?对于程序员来说,最常见的理由是,在输入不匹配或到达文件尾时,需要使用不带参数的 clear() 重新打开输入。这样做是否有意义,取决于程序要执行的任务。稍后将介绍一些例子。setstate()的主要用途是位输入和输出函数提供一种修改状态的途径。例如,如果 num 是一个 int,则下面的调用将可能导致 operator>>(int &) 使用 setstate() 设置 failbit 或 eofbit:

    cin >> num;		// read an int
    
  2. I/O 和异常
    假设某个输入函数设置了 eofbit,这是否会导致异常被引发呢?在默认情况下,答案是否定的。但可以使用 exceptions() 方法来控制异常如何被处理。

    首先,介绍一些背景知识。exceptions() 方法返回一个位字段,它包含3位,分别对应于 eofbit、failbit 和 badbit。修改流状态涉及 clear() 或 setstate(),这都将使用 clear()。修改流状态后,clear() 方法将当前的流状态与 exceptions() 返回的值进行比较。如果在返回值中某一位被设置,而当前状态中的对应位也被设置,则 clear() 将引发 ios_base::failure 异常。如果两个值都设置了 badbit,将发生这种情况。如果 exceptions() 返回 goodbit,则不会引发任何异常。ios_base::failure 异常类是从 std::exception 类派生而来的,因此包含一个 what() 方法。

    exceptions() 的默认设置为 goodbit,也就是说,没有引发异常。但重载的 exceptions(iostate) 函数使得能够控制其行为:

    cin.exceptions(badbit);	// setting badbit causes exception to be thrown
    

    位运算符 OR(在附录 E 讨论)使得能够指定多位。例如,如果 badbit 或 eofbit 随后被设置,下面的语句将引发异常:

    cin.exceptions(badbit | eofbit);
    

    下面的程序对之前的程序进行了修改,以便程序能够在 failbit 被设置时引发并捕获异常。

    // cinexcp.cpp -- having cin throw an exception
    
    #include <ios>
    #include<iostream>
    #include<exception>
    
    int main() {
        using namespace std;
        // have failbit cause an exception to be thrown
        cin.exceptions(ios_base::failbit);
        cout << "Enter numbers: ";
        int sum = 0;
        int input;
        try {
            while(cin>>input){
                sum += input;
            }
        }catch(ios_base::failure & bf){
            cout << bf.what() << endl;
            cout << "O! the horror!\n";
        }
    
        cout << "Last value entered = " << input << endl;
        cout << "Sum = " << sum << endl;
    
        return 0;
    }
    

    下面的程序的运行情况如下,其中的 what() 消息取决于实现:

    Enter numbers: 20 30 40 pi 6
    basic_ios::clear: iostream error
    O! the horror!
    Last value entered = 0
    Sum = 90
    

    这就是如何在接受输入时使用异常。然而,应该使用它们吗?这取决于具体情况。就这个例子而言,答案是否定的。异常用于捕获不正常的意外情况,但这个例子将输入错误作为一种退出循环的方式。然而,让这个程序在 badbit 位被设置时引发异常可能是合理的,因为这种情况是意外的。如果程序被设计成从一个数据文件中读取数据,直到到达文件尾,则在 failbit 位被设置时引发异常也是合理的,因为这表明数文件出现了问题。

  3. 流状态的影响
    只有在流状态良好(所有的位都被清除)的情况下,下面的测试才返回 true:

    while(cin >> input)
    

    如果测试失败,可以使用上表中的成员函数来判断可能的原因。例如,可以将程序中的核心部分修改成这样:

    while (cin >> input) {
    	sum += input;
    }
    if(cin.eof()){
    	cout << "Loop terminated because EOF encountered\n";
    }
    

    设置流状态位有一个非常重要的后果:流将对后面的输入或输出关闭,直到位被清除。例如,下面的代码不可行:

    while(cin >> input) {
    	sum += input;
    }
    cout << "Last  value entered = " << input << endl;
    cout << "Sum = " << sum << endl;
    cout << "Now enter a new number: ";
    cin >> input;		// won't work
    

    如果希望程序在流状态位被设置后能够读取后面的输入,就必须将流状态重置为良好。这可以通过调用 clear() 方法来实现:

    while (cin >> input) {
    	sum += input;
    }
    cout << "Last value entered = " << input << endl;
    cout << "Sum = " << sum << endl;
    cout << "Now enter a new number: ";
    cin.clear();		// reset stream state
    while (!isspace(cin.get())) {
    	continue;		// get rid of bad input
    }
    cin >> input;		// will work now
    

    注意,这还不足以重新设置流状态。导致输入循环终止的不匹配输入仍留在输入队列中,程序必须跳过它。一种方法是一直读取字符,直到到达空白为止。isspace() 函数 是一个 cctype 函数,它在参数是空白字符时返回 true。另一种方法是,丢弃行中的剩余部分,而不仅仅是下一个单词:

    while(cin.get() != '\n'){
    	continue;		// get rid of rest of line
    }
    

    这个例子假设循环由于不恰当的输入而终止。现在,假设循环是由于到达文件尾或者由于硬件故障而终止的,则处理错误输入的新代码将毫无意义。可以使用 fail() 方法检测假设是否正确,来修复问题。由于历史原因,fail() 在 failbit 或 eofbit 被设置时返回 true,因此代码必须排除后一种情况。下面是一个排除这种情况的例子:

    while (cin >> input) {
    	sum += input;
    }
    cout << "Last value entered = " << input << endl;
    cout << "Sum = " << sum << endl;
    if (cin.fail() && !cin.eof() )  {	// failed because of mismatched input
    	cin.clear();		// reset stream state
    	while ( !isspace(cin.get())) continue;	// get rid of bad input
    }
    else {
    	cout << "I cannot go on!\n";
    	exit(1);
    }
    cout << "Now enter a new number: ";
    cin >> input;		// will work now
    

其他 istream 类方法

第3章~第5章讨论了 get() 和 getline() 方法。您可能还记得,它们提供下面的输入功能:

  • 方法 get(char&) 和 get(void) 提供不跳过空白的单字符输入功能;
  • 函数 get(char*, int, char) 和 getline(char*, int, char) 在默认情况下读取整行而不是一个单词。

它们被称为非格式化输入函数(unformatted input functions),因为它们只是读取字符输入,而不会跳过空白,也不进行数据转换。

来看一下 istream 类的这两组成员函数。

  1. 单字符输入
    在使用 char 参数或没有参数的情况下,get() 方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get(char & ch) 版本将输入字符赋给其参数,而 get(void) 版本将输入字符转换为整型(通常是int),并将其返回。
    (1)成员函数 get(char &)
    先来看 get(char &)。假设程序中包含如下循环:

    int ct = 0;
    char ch;
    cin.get(ch);
    while (ch != '\n') {
    	cout << ch;
    	ct++;
    	cin.get(ch);
    }
    cout << ct << endl;
    

    get(void) 成员函数的返回类型为 int(或某种更大的整型,这取决于字符集和区域)。这使得下面的代码是非法的:

    char c1, c2, c3;
    cin.get().get)() >> c3;	// not valid
    

    这里,cin.get() 将返回一个 int 值。由于返回值不是类对象,因此不能对它应用成员运算符。因此将出现语法错误。然而,可以在抽取序列的最后使用 get():

    char c1;
    cin.get(c1).get();		// valid
    

    get(void) 的返回类型为 int,这意味着它后面不能跟抽取运算符。然而,由于 cin.get(c1) 返回 cin,因此它可以放在 get() 的前面。上述代码将读取第一个输入字符,将其赋给c1,然后读取并丢弃第二个输入字符。

    到达文件尾后(不管是真正的文件尾还是模拟的文件尾),cin.get(void)都将返回值 EOF——头文件 iostream 提供的一个符号常量。这种设计特性使得可以这样来读取输入:

    int  ch;
    while((ch = cin.get() ) ! = EOF) {
    	// process input
    }
    

    这里应将 ch 的类型声明为 int,而不是 char,因为值 EOF 可能无法使用 char 类型来表示。
    第 5 章更详细地介绍了这些函数,下表对单字符输入函数的特性进行了总结。
    cin.get(ch) 与 cin.get()

    特征cin.get(ch)ch = cin.get()
    传输输入字符的方法赋给参数 ch将函数返回值赋给 ch
    字符输入时函数的返回值指向 istream 对象的引用字符编码(int值)
    达到文件尾时函数的返回值转换为 falseEOF
  2. 采用哪种单字符输入形式
    假设可以选择 >> 、get(char &) 或 get(void),应使用哪一个呢?首先,应确定是否希望跳过空白。如果跳过空白更方便,则使用抽取运算符>>。例如,提供菜单选项时,跳过空白更为方便:

    cout << "a. annoy client				b. bill client\n"
    	 << "c. clam client					d. deceive client\n"
    	 << "q.\n";
    cout << "Enter a, b, c, d, or q: ";
    char ch;
    cin >> ch;
    while (ch != 'q') {
    	switch(ch) {
    		...
    	}
    	cout << "Enter a, b, c, d, or q: ";
    	cin >> ch;
    }
    

    要输入 b 进行响应,可以键入 b 并按回车健,这将生成两个字符的响应——b\n。如果使用 get(),则必须添加在每次循环中处理\n 字符的代码,而抽取运算符可以跳过它(如果使用过 C 语言进行编程,则可能遇到过使用换行符进行响应为非法的情况。这是个很容易解决的问题,但比较讨厌)。

    如果希望程序检查每个字符,请使用 get() 方法,例如,计算字数的程序可以使用空格来判断单词何时结束。在 get() 方法中,get(char &) 的接口更佳。get(void) 的主要优点是,它与标准 C 语言中的 getchar() 函数极其类似,这意味着可以通过包含 iostream(而不是 stdio.h),并用 cin.get() 替换所有的 getchar(),用cout.put(ch) 替换所有的 putchar(ch),来将 C 程序转换为 C++ 程序。

  3. 字符串输入:getline()、get() 和 ignore()

    接下来复习一下第 4 章介绍的字符串输入成员函数。getline() 成员函数和 get() 的字符串读取版本都读取字符串,它们的函数特征标相同(这是从更为通用的模板声明简化而来的):

    istream & get(char *, int, char);
    istream & get(char *, int);
    istream & getline(char *, int, char);
    istream & getline(char *, int);
    

    第一个参数是用于放置输入字符串的内存单元的地址。第二个参数比要读取的最大字符数大1(额外的一个字符用于存储结尾的空字符,以便将输入存储为一个字符串)。第三个参数指定用作分界符的字符,只有两个参数的版本将换行符用作分界符。上述函数都在读取最大数目的字符或遇到换行符后为止。

    例如,下面的代码将字符输入读取到字符数组 line 中:

    char line[50];
    cin.get(line, 50);
    

    cin.get() 函数将在到达第49个字符或遇到换行符(默认情况)后停止将输入读取到数组中。get() 和 getline() 之间的主要区别在于,get() 将换行符留在输入流中,这样接下来的输入操作首先看到的将是换行符,而getline() 抽取并丢弃输入流中的换行符。

    第4章演示了如何使用这两个成员函数的默认格式。现在来看一下接受三个参数的版本,第三个参数用于指定分界符。遇到分界符后,输入将停止,即使还未读取最大数目的字符。因此,在默认情况下,如果在读取指定数目的字符之前到达行尾,这两种方法都将停止读取输入。和默认情况一样,get() 将分界符留在输入流中,而 getline() 不保留。

    下面的程序演示了 getline() 和 get() 是如何工作的,它还介绍了 ignore() 成员函数。该函数接受两个参数:一个是数字,指定要读取的最大字符数;另一个是字符,用作输入分界符。例如,下面的函数调用读取并丢弃接下来的 255 个字符或直到到达第一个换行符:

    cin.ignore(255, '\n');
    

    原型为两个参数提供的默认值为 1 和 EOF,该函数的返回类型为 istream &:

    istream & ignore(int = 1, int = EOF);
    

    默认参数值 EOF 导致 ignore() 读取指定数目的字符或读取到文件尾。

    该函数返回调用对象,这使得能够拼接函数调用,如下所示:

    cin.ignore(255,'\n').ignore(255, '\n');
    

    其中,第一个 ignore() 方法读取并丢弃一行,第二个调用读取并丢弃另一行,因此一共读取了两行。现在来看一看下面的程序:

    // get_fun.cpp - using get() and getline()
    
    #include<iostream>
    const int Limit = 255;
    
    int main() {
        using std::cout;
        using std::cin;
        using std::endl;
    
        char input[Limit];
    
        cout << "Enter a string for getline() processing:\n";
        cin.getline(input, Limit, '#');
        cout << "Here is your input:\n";
        cout << input << "\nDone with phase 1\n";
    
        char ch;
        cin.get(ch);
        cout << "The next input character is " << ch << endl;
    
        if (ch!='\n'){
            cin.ignore(Limit,'\n'); // discard rest of line
        }
    
    
        cout << "Enter a string for get() processing:\n";
        cin.get(input, Limit, '#');
        cout << "Here is your input:\n";
        cout << input << "\nDone with phase 2\n";
    
        cin.get(ch);
        cout << "The next input character is " << ch << endl;
    }
    

    下面是程序的运行情况:

    Enter a string for getline() processing:
    Please pass
    me a #3 melon! 
    Here is your input:
    Please pass
    me a 
    Done with phase 1
    The next input character is 3
    Enter a string for get() processing:
    I still want my
     #3 melon! 
    Here is your input:
    I still want my
     
    Done with phase 2
    The next input character is #
    

    注意,getline() 函数将丢弃输入中的分界字符#,而 get() 函数不会。

  4. 意外字符串输入
    get(char *, int) 和 getline() 的某些输入形式将影响流状态。与其他输入函数一样,这两个函数在遇到文件尾时将设置 eofbit,遇到流被破坏(如设备故障)时将设置 badbit。另外两种特殊情况是无输入以及输入到达或超过函数调用指定的最大字符数。下面来看这些情况。

    对于上述两个方法,如果不能抽取字符,它们将把空值字符放置到输入字符串中,并使用 setstate() 设置 failbit。方法在什么时候无法抽取字符呢?一种可能的情况是输入方法立刻到达了文件尾。对于 get(char *, int) 来说,另一种可能是输入了一个空行:

    char temp[80];
    while( cin.get(temp,80) ) // terminates on empty line
    	...
    

    有意思的是,空行并不会导致 getline() 设置 failbit。这是因为 getline() 仍将抽取换行符,虽然不会存储它。如果希望 getline() 在遇到空行时终止循环,则可以这样编写:

    char temp[80];
    while (cin.getline(temp, 80) && temp[0] != '\0' ) // terminate on empty line
    

    现在假设输入队列中的字符数等于或超过了输入方法指定的最大字符数。首先,来看 getline() 和下面的代码:

    char temp[30];
    while (cin.getline(temp, 30))
    

    getline() 方法将从输入队列中读取字符,将它们放到 temp 数组的元素中,直到(按测试顺序)到达文件尾、将要读取的字符是换行符或存储了29个字符为止。如果遇到文件尾,则设置failbit;如果将要读取的字符是换行符,则该字符将被读取并丢弃;如果读取了 29 个字符,并且下一个字符不是换行符,则设置failbit。因此,包含 30 个或更多字符的输入行将终止输入。

    现在来看 get(char *, int) 方法。它首先测试字符数,然后测试是否为文件尾以及下一个字符是否是换行符。如果它读取了最大数目的字符,则不设置 failbit 标记。然而,由此可以直到终止读取是否是由于输入字符过多引起的。可以用 peek() 来查看下一个输入字符。如果它是换行符,则说明 get() 已读取了整行;如果不是换行符,则说明get() 实在到达行尾前停止的。这种技术对 getline() 不适用,因为 getline() 读取并丢弃换行符,因此查看下一个字符无法知道任何情况。然而,如果使用的是 get(),则可以知道是否读取了整个一行。下一节将介绍这种方法的一个例子。
    输入行为

    方法行为
    getline(char*, int)如果没有读取任何字符,比如输入立刻到达了文件尾(但换行符被视为读取了一个字符),则设置 failbit) ;如果读取了最大数目的字符,且行中还有其他字符,则设置 failbit。
    get(char*, int)如果没有读取任何字符,则设置 failbit

其他 istream 方法

除前面讨论过的外,其他 istream 方法包括 read()、peek()、gcount() 和 putback()。read() 函数读取指定数目的字符,并将它们存储在指定的位置中。例如,下面的语句从标准输入中读取 144 个字符,并将它们存储在 gross 数组中:

char gross[144];
cin.read(gross, 144);

与 getline() 和 get() 不同的是,read() 不会在输入后加上空值字符,因此不能将输入转换为字符串。read() 方法不是专为键盘输入设计的,它最常与 ostream write() 函数结合使用,来完成文件输入和输出。该方法的返回类型为 istream &,因此可以像下面这样将它拼接起来:

char gross[144];
char score[20];
cin.read(gross, 144).read(score, 20);

peek() 函数返回输入中的下一个字符,但不抽取输入流中的字符。也就是说,它使得能够查看下一个字符。假设要读取输入,直到遇到换行符或句点,则可以用 peek() 查看输入流中的下一个字符,以此来判断是否继续读取:

char great_input[80];
char ch;
int i = 0;
while ((ch = cin.peek()) != '.' && ch != '\n') {
	cin.get(great_input[i++]);
}
great_input[i] = '\0';

cin.peek() 查看下一个输入字符,并将它赋给 ch。然后,while 循环的测试条件检查 ch 是否是句点或换行符。如果不是,循环将该字符读入到数组中,并更新数组索引。当循环终止时,句点和换行符将留在输入流中,并作为接下来的输入操作读取的第一个字符。然后,代码将一个空值字符放在数组的最后,使之称为一个字符串。

gcount() 方法返回最后一个非格式化抽取方法读取的字符数。这意味着字符是由 get()、getline()、ignore() 或 read() 方法读取的,不是由抽取运算符(>>)读取的,抽取运算符对输入进行格式化,使之与特定的数据类型匹配。例如,假设使用 cin.get(myarray, 80) 将一行 myarray 数组中,并想知道读取了多少个字符,则可以使用 strlen() 函数来计算数组中的字符数,这种方法比使用 cin.gcount() 计算从输入流中读取了多少字符的速度要快。

putback() 函数将一个字符插入到输入字符串中,被插入的字符将是下一条输入语句读取的第一个字符。putback() 方法接受一个 char 参数——要插入的字符,其返回类型为 istream &,这使得可以将该函数调用与其他 istream 方法拼接起来。使用 peek() 的效果相当于先使用 get() 读取一个字符,然后使用 putback() 将该字符放回到输入流中。然而,putback() 允许将字符放到不是刚才读取的位置。

下面的程序采用两种方式来读取并显示输入中#字符(不包括)之前的内容。第一种方法读取#字符,然后使用 putback() 将它插回到输入中。第二种方法在读取之前使用 peek() 查看下一个字符。

// peeker.cpp -- some istream methods
#include <iostream>

int main() {

    using std::cout;
    using std::cin;
    using std::endl;

    // read and echo input up to a # character
    char ch;

    while(cin.get(ch)) {   //terminates on EOF
        if (ch != '#'){
            cout << ch;
        }
        else {
            cin.putback(ch);
            break;
        }
    }

    if (!cin.eof()){
        cin.get(ch);
        cout << endl << ch << " is next input character.\n";
    }
    else {
        cout << "End of file reached.\n";
        std::exit(0);
    }

    while(cin.peek() != '#'){   // look ahead
        cin.get(ch);
        cout << ch;
    }
    if (!cin.eof()){
        cin.get(ch);
        cout << endl << ch << " is next input character.\n";
    }
    else {
        cout << "End of file reached.\n";
    }

    return 0;

}

下面是该程序的运行情况:

ssssss#
ssssss
# is next input character.



ssssss#
ssssss
# is next input character.

达到文件尾时,表达式(cin.get(ch) )将返回 false,因此从键盘模拟文件尾将终止循环。如果#字符首先出现,则程序将该字符放回到输入流中,并使用 break 语句来终止循环。

第二种方法看上去更简单:

while(cin.peek() != '#' ) {
	cin.get(ch);
	cout << ch;
}

程序将查看下一个字符。如果它不是#,则读取并显式它,然后再查看下一个字符。这一过程将一直继续下去,直到出现分界字符。

现在来看一个例子,它使用 peek() 来确定是否读取了整行。如果一行中只有部分内容被加入到输入数组中,程序将删除余下的内容。

// truncate.cpp -- using get() to truncate input line, if necessray
#include<iostream>
const int SLEN = 10;

inline void eatline() { while (std::cin.get() != '\n') continue; }

int main() {
    using std::cin;
    using std::cout;
    using std::endl;

    char name[SLEN];
    char title[SLEN];
    cout << "Enter your name: ";
    cin.get(name, SLEN);
    if(cin.peek()!='\n'){
        cout << "Sorry, we only have enough room for " << name << endl;
    }
    eatline();

    cout << "Dear " << name << ", enter your title: \n";
    cin.get(title, SLEN);

    if (cin.peek() != '\n' ){
        cout << "We were forced to truncate your title.\n";
    }
    eatline();
    cout << " Name: " << name
         << "\nTitle: " << title << endl;
    
    return 0;
}

下面是程序的运行情况:

Enter your name: Ella Fishsniffer
Sorry, we only have enough room for Ella Fish
Dear Ella Fish, enter your title: 
Executive Adjunct
We were forced to truncate your title.
 Name: Ella Fish
Title: Executive

注意,下面的代码确定第一条输入语句是否读取了整行:

while(cin.get() != '\n' ) continue;

如果 get() 读取了整行,它将保留换行符,而上述代码将读取并丢弃换行符。如果 get() 只读取一部分,则上述代码将读取并丢弃该行中余下的内容。如果不删除余下的内容,则下一条语句将从第一个输入行中余下部分的开始位置读取。对于这个例子,这将导致程序把字符串 sniffer 读取到 title 数组中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值