标准输入流中空白字符的怪异 —— 区分 scanf 与 cin

在论坛上看到有人提出一个如下的问题,在此总结一下。
原问题:
http://topic.csdn.net/u/20110414/22/90d0606c-9876-48e4-9b69-bd8bd8a41897.html

 

  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int ival1 = 123, ival2 = 1;  
  5.     char ch = 't';  
  6.     scanf("%d%d", &ival1, &ival2);  
  7.     scanf("%c", &ch);  
  8.     printf("%d/n%d/n%c/n", ival1, ival2, ch);  
  9.     return 0;  
  10. }  
  11. /* 
  12. 输入:2回车a回车 
  13. 结果是: 
  14. 2 
  15. 1 
  16. a 
  17. */  

 

 

LZ的疑问是 :但是第一个回车符还在缓冲区中啊! 为什么它没有被变量 ch 读走?
我将上述的问题再详细描述一下:即,输入完变量 ival1 之后,输入缓冲区中现在存留一个换行符 '/n',然后此时又从键盘上输入一个字符a,LZ的问题是,为什么变量 ch 没有将前面的换行符 '/n' 读走,而是跳过却将后面输入的字符a读走了。


问题解释 

在很多词法/语法分析器中,空白字符(whitespace)指的是:任意多个基本空白字符(包括空格、制表符/t、回车符/r、换行符/n等)的连续组合。注意,在C/C++编程语言中,C风格的字符串又叫null-terminated string,一般译为空字符结尾的字符串,这里空字符(注意,不叫空白字符)是指'/0',或者是字符的编码值为0的字符。C的标准库中有一个函数,isspace(char c)函数可以检测一个字符是否为空白符,需要包含cctype头文件。

看 scanf 的两种情况 :

Case 1: 输入完ival1之后,不管输入多少个 whitespace,都会继续等待输入ival2,原因是:此时的 whitespace 被视为输入的分隔符,即,会被忽略。

  1. int ival1, ival2;  
  2. scanf("%d", &ival1);  
  3. scanf("%d", &ival2);  


Case 2: 输入完ival之后,变量ch也会从stdin流中读取数据,输出显示 ch 的ASCII码值为10,即,为换行符 '/n'。而换行符属于 whitespace,本应该起到分隔符的作用,但是这里却当成有效字符读入到变量 ch 中。

  1. int ival;  
  2. char ch;  
  3. scanf("%d", &ival);  
  4. scanf("%c", &ch);  


再对比下 cin 的两种情况 :( 主要注意 Case 2 )

Case 1: 输入完ival1之后,不管输入多少个 whitespace,都会继续等待输入ival2,原因是:此时的 whitespace 被视为输入的分隔符,即,会被忽略。

  1. int ival1 = 0, ival2 = 0;  
  2. cin >> ival1;  
  3. cin >> ival2;  


Case 2: 输入完ival之后,不管输入多少个 whitespace,都会继续等待输入 ch ,这里空白符依然被忽略。注意,这里与 scanf 效果不同!

  1. int ival = 0;  
  2. char ch;  
  3. cin >> ival;  
  4. cin >> ch;  


结论 
(1) 当 scanf 中的输入格式说明符 (Format specifiers) 不是 "%c"的时候,那么空白字符 (whitespace) 将起到分隔符的作用。把分隔好的两个数据分别赋值到各自定义好的变量或数组中去,两个数据之间的 whitespace 被从缓冲区读出但是不起任何作用,当然最后一个 '/n' 会被留在缓冲区内,除非用 getchar(); 或 scanf("%c",&ch); 把它读出来。
(2) 当 scanf 中的输入格式说明符 (Format specifiers) 是 "%c"的时候,那么空白字符 (whitespace) 将会被正常读入,不再起到分隔符的作用。
(3) 使用 cin 的时候,空白字符都会当成分隔符而被忽略。

注意 :
(1) 当按键盘上的回车键将产生了2个字符:回车符('/r')和换行符('/n')。回车符'/r' (CR:carriage return:倒车) 使光标回到这行的首部,换行符('/n') (new line) 然后再换行。 
(2) 回车是一定要有的,不管是 getchar(); 还是 scanf 只要是通过缓冲区输入数据的函数都是等待回车符 '/r' 出现才进入缓冲区的。

Format specifiers:
 A sequence formed by an initial percentage sign (%) indicates a format specifier, which is used to specify the type and format of the data to be retrieved from stdin and stored in the locations pointed by the additional arguments. A format specifier follows this prototype:
%[*][width][modifiers]type

 


再看看LZ的问题 

LZ的问题是,为什么变量 ch 没有将前面的换行符 '/n' 读走,而是跳过却将后面输入的字符a读走了。
原因就是:读取ival1成功之后,下一个要读取的数据格式是 "%d",所以前面存留的换行符 '/n' 被忽略了,因此读取的是下个输入的非空白字符 'a',即,变量 ch 的值为 'a'。
这里还要注意:变量 b 没有读到合法的数据,因此,变量 b 仍然是以前的初始值。通过 scanf 的返回值可以判断成功读取(从键盘输入)数据的个数。

scanf 版

  1. #include <cstdio>  
  2. #include <cstdlib>  
  3. #include <cctype>  
  4. int main()  
  5. {  
  6.     int ival1 = 0, ival2 = 0;  
  7.     char ch = 't';  
  8.     char ch_tmp;  
  9.     scanf("%d", &ival1);  
  10.     scanf("%c", &ch_tmp);  
  11.     if (isspace(ch_tmp))  
  12.     {  
  13.         printf("%d is a whitespace right ? %d is the newline character!/n", ch_tmp, ch_tmp);  
  14.     }  
  15.     if (scanf("%d", &ival2) != 1)  
  16.     {  
  17.         printf("Damn it! ival2 cannot read input!/n");  
  18.     }  
  19.     scanf("%c", &ch);  
  20.     printf("%d %d %c/n", ival1, ival2, ch);  
  21.     system("pause");  
  22.     return 0;  
  23. }  

 

cin 版

  1. #include <iostream>  
  2. #include <cctype>  
  3. using namespace std;  
  4. int main()  
  5. {  
  6.     int ival1 = 0, ival2 = 0;  
  7.     char ch = 't';  
  8.     char ch_tmp;  
  9.     cin >> ival1;// 读取并忽略有效字符之前所有的空白字符,然后读取字符直至再次遇到空白字符,读取终止,该空白字符仍留在输入流中  
  10.     cin >> ch_tmp;// 注意:这里与scanf的区别,此处将空白字符忽略,而不是作为有效字符  
  11.     if (isspace(ch_tmp))  
  12.     {  
  13.         cout << ch_tmp << " is a whitespace right ?" << ch_tmp << " is the newline character!" << endl;  
  14.     }  
  15.     cin >> ival2;  
  16.     if (! cin)  
  17.     {  
  18.         cout << "Damn it! ival2 cannot read input!" << endl;  
  19.     }  
  20.     cin >> ch;  
  21.     cout << ival1 << " " << ival2 << " " << ch;  
  22.     system("pause");  
  23.     return 0;  
  24. }  

 

再对 cin 版验证一下:

  1. #include <iostream>  
  2. #include <cctype>  
  3. using namespace std;  
  4. int main()  
  5. {  
  6.     int ival1 = 0, ival2 = 0;  
  7.     char ch = 't';  
  8.     char ch_tmp;  
  9.     streambuf* sb;  
  10.     sb = cin.rdbuf();  
  11.     cout << sb->in_avail() << endl;// will print 0  
  12.     cin >> ival1;// 读取并忽略有效字符之前所有的空白字符,然后读取字符直至再次遇到空白字符,读取终止,该空白字符仍留在输入流中  
  13.     cout << sb->in_avail() << endl;// will print 1 证明空白字符'/n' 确实留在输入缓冲区中  
  14.     cin >> ch_tmp;// 注意:这里与scanf的区别,此处将空白字符忽略,而不是作为有效字符  
  15.     if (isspace(ch_tmp))  
  16.     {  
  17.         cout << ch_tmp << " is a whitespace right ?" << ch_tmp << " is the newline character!" << endl;  
  18.     }  
  19.     cin >> ival2;  
  20.     if (! cin)  
  21.     {  
  22.         cout << "Damn it! ival2 cannot read input!" << endl;  
  23.     }  
  24.     cin >> ch;  
  25.     cout << ival1 << " " << ival2 << " " << ch;  
  26.     system("pause");  
  27.     return 0;  
  28. }  

参考 :
scanf 
http://www.cplusplus.com/reference/clibrary/cstdio/scanf/ 
getchar,scanf以及缓冲区的概念
http://blog.csdn.net/weinixugeyuan/archive/2009/03/12/3980498.aspx 
getch()、getche()和getchar()之间的区别 
http://www.cnitblog.com/mantou/archive/2010/01/13/1250.html

ios::rdbuf
http://www.cplusplus.com/reference/iostream/ios/rdbuf/ 
http://social.msdn.microsoft.com/forums/en-US/vclanguage/thread/57d90614-8555-488c-acc4-f53fb04baaa3 

 文章出自 http://blog.csdn.net/delphiwcdj/article/details/6325386

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值