cin
cin与input string stream的不同在于,当你读取完istringstream buffer内的数据时,istringstream流的EOF位开启,继续执行当前程序,而cin buffer内的数据读取完时,EOF位开启,会停止执行后续代码,等待用户的输入。
当你执行
cin >> name;
语句时,cin的buffer中是没有数据的,这时程序会等待用户输入。
和stringstream一样,读取cin流中的buffer时会以空白符为分隔符,读取到下一个字符为空白符时停止,并且下次再读取buffer时会跳过任意个前导空白符再读取下一个字符。
关键要点(Key Takeaways)
-
程序什么时候提醒用户输入?当位置指针到达EOF,读取过buffer中最后一个token时。
-
为什么cout操作没有立即将字符输出到控制台?什么时候才输出?见上一篇文章,缓冲区满/强制刷新缓冲区的时候才会输出。
-
位置指针什么时候会跳过空白符?是在每次
>>
操作读取token之前还是读取token之后?读取token之前会跳过token前面的全部前导空白符。也就是说:当第一次读取完token后,位置指针的位置是:
或者换一种思路也能看出问题的答案:
考虑下面的代码:
string name;
cin >> name;
cout << name << endl;
如果我在控制台输入 67
,输出是" 67"
还是"67"
?
位置指针执行以下的操作:
- 消费所有空白符(空格、换行等)
- 尽可能的读字符,直到下一个字符为:
- 空白符
- 下一个字符不符合要读取的类型,如
"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;
导致输出为图中情况的原因是: 当cin >> age
时,buffer内仍有token:Wang
,而这个字符串无法转换成int
类型,导致cin流进入Fail 状态,中止后续所有的cin >>
操作。
这就是为什么cin >>
是nightmare的原因:
- cin一次会读取整行的字符+换行符进buffer,但他却是用空白符来分割token。
- 缓冲区中存在的垃圾会使cin无法在正确的时机提示用户输入。
- 当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读取流时下一个字符就是\n
,line
的值是空字符串。
解决办法是在getline
之前再调用一次getline
,或者iss.ignore()
让位置指针走过换行符。
总结
>>
操作符消费空白符的时机,位置指针停止的时机cin >>
的3个问题getline
消费换行符的时机,位置指针停止的时机getline
与>>
混用的问题