现代C++:Streams-cin >>的噩梦

378 阅读3分钟

cin

cin与input string stream的不同在于,当你读取完istringstream buffer内的数据时,istringstream流的EOF位开启,继续执行当前程序,而cin buffer内的数据读取完时,EOF位开启,会停止执行后续代码,等待用户的输入。

image.png 当你执行cin >> name;语句时,cin的buffer中是没有数据的,这时程序会等待用户输入。
和stringstream一样,读取cin流中的buffer时会以空白符为分隔符,读取到下一个字符为空白符时停止,并且下次再读取buffer时会跳过任意个前导空白符再读取下一个字符。

关键要点(Key Takeaways)

  • 程序什么时候提醒用户输入?当位置指针到达EOF,读取过buffer中最后一个token时。

  • 为什么cout操作没有立即将字符输出到控制台?什么时候才输出?见上一篇文章,缓冲区满/强制刷新缓冲区的时候才会输出。

  • 位置指针什么时候会跳过空白符?是在每次>>操作读取token之前还是读取token之后?读取token之前会跳过token前面的全部前导空白符。也就是说:当第一次读取完token后,位置指针的位置是:

    image.png
    或者换一种思路也能看出问题的答案:
    考虑下面的代码:

    string name;
    cin >> name;
    cout << name << endl;

如果我在控制台输入 67,输出是" 67"还是"67"

image.png

位置指针执行以下的操作:

  1. 消费所有空白符(空格、换行等)
  2. 尽可能的读字符,直到下一个字符为:
    1. 空白符
    2. 下一个字符不符合要读取的类型,如"86.2",我们想提取整数,那么就会在下一个字符为小数点时停下。

cin >> sth 是噩梦的3个理由

考虑下面的代码,如果我输入了"Avery Wang"之后按回车,会发生什么?

    cout << "input: ";
    string name,response;
    int age;
    cin >> name;
    cin >> age;
    cout << "Hello " << name << "(" << age << ")";
    cout << "do you want to repeat?";
    cin >> response;
    cout << response << endl;

image.png

导致输出为图中情况的原因是: 当cin >> age时,buffer内仍有token:Wang,而这个字符串无法转换成int类型,导致cin流进入Fail 状态,中止后续所有的cin >> 操作。
这就是为什么cin >>是nightmare的原因:

  1. cin一次会读取整行的字符+换行符进buffer,但他却是用空白符来分割token。
  2. 缓冲区中存在的垃圾会使cin无法在正确的时机提示用户输入。
  3. 当cin流进入Fail状态,后续的所有cin操作都会中止(也就是失败)

第一个问题可以用getline(cin,sth);来让进buffer的字符与分割token的方式都变成换行符分割。
getline(cin,sth)会读取一整行的字符串,且会消费分隔符(通常是换行符)(但不会保存进sth中),他的返回值与extract operator(>>)的返回值相同,都是返回流。

Always use getline with cin instead of >> 切记使用cin流时用getline而不是>>
Always check the return value of getline 切记检查getline的返回值

getline与>>混用的常见bug

考虑下面的例子,line的值是什么?

    istringstream iss("16.9\n 24");
    double val;
    string line;
    iss >> val;     // val = 16.9
    getline(iss,line);

由于>>提取运算符不会消费空白符,会在空白符前停止,因此getline读取流时下一个字符就是\nline的值是空字符串
解决办法是在getline之前再调用一次getline,或者iss.ignore()让位置指针走过换行符。

总结

  1. >>操作符消费空白符的时机,位置指针停止的时机
  2. cin >>的3个问题
  3. getline消费换行符的时机,位置指针停止的时机
  4. getline>>混用的问题