C++-回车符作为 scanf 的结束为什么会影响到下一次输入的获取
看下面这段代码:
#include <stdio.h>
void main()
{
int a;
char c = '';
printf("inputn");
scanf("%d",&a);
scanf("%c",&c);
printf("n");
printf("%d,%x",a,c);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
可用 while((ch = getchar()) != 'n' && ch != EOF) 这种通用的方法清空缓冲区里的回车符!
回车也是字符来的,所以用 %c scanf 的时候,一般都放在其他类型的前面
放在最后的话,回车会被认为输入 rn 或者 n 字符来处理,
显然 rn 或者 n 都是字符,被填充到 c 变量中
更新:
为了更好的验证些问题,我做了如下的操作:
编辑一个 todo.c 文件
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char ch;
scanf("%c", &ch);
printf("ch = %dn", ch);
return 0;
}
编译链接后生成 todo
然后再写一个 input.c 文件,内容为
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
printf("rn");
return 0;
}
编译链接后生成 input
在 shell 里面使用命令
./input | ./todo
结果为: ch = 13
当我修改 input.c 的 printf 里面的内容为 "n" 后,编译出来的 input 再次调用
结果为: ch = 10
然后再次修改 input.c 的 printf 为 "r",重新编译链接 input
结果为: ch = 13
结论:当 scanf 用 %c 结尾接收数据时,碰到 r 或者 n 都会作为结束
进来的是什么字符,就会把什么字符赋值给对应的变量,这一点与 qingyunww 的观点不同
更新
将 todo.c 的程序修改为:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char a, b, c;
scanf("%c%c%c", &a, &b, &c);
printf("char: %d %d %dn", a, b, c);
return 0;
}
然后调用 input 填充数据时分别测试:
rrr - 13 13 13
rnr - 13 10 13
nnn - 10 10 10
而修改为 rn 后测试结果比较诡异,每次的结果都不会一样:
以上结果来自:Arch 3.6.4 gcc 4.7.2
祝好,
斑驳敬上
在用"%c"输入时,空格和“转义字符”均作为有效字符。例:
scanf("%c%c%c",&c1,&c2,&c3);
输入:a□b□c↙
结果:a→c1,□→c2,b→c3 (其余被丢弃)
scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数
仅在每一个数据域均有数据,并按回车后结束)。
① 遇空格、“回车”、“跳格”键。
② 遇宽度结束。
③ 遇非法输入。
这里对lz的程序作了下修改:
#include <stdio.h>
void main()
{
int a;
char c = '';
char d = '';
printf("%d,%d n",'r','n');
printf("inputn");
scanf("%d",&a);
scanf("%c",&c);
scanf("%c",&d);
printf("%d,%d,%d",a,c,d);
printf("-------------------------n");
}
如果输入:12↙↙
结果为:12,10,10
如果输入:12□□↙
结果为:12,32,32
所以,程序中的scanf()在输入12后,再连续两次按下Enter键时,发现a(12)、c(10)、d(10),从这里可以看出Enter输入的rn被处理成n,并且被c、d接受了;同时,需要输入字符时,无论输入的是什么都会被接受。
跟踪了下VS中scanf的源码,找到了一些线索,但并不是如楼上两位所说的跟r有关系。
scanf底层调用流程:scanf->vscanf->inputfn。
附上inputfn函数关键代码:
comchr = *format | (_T('a') - _T('A'));
if (_T('n') != comchr)
if (_T('c') != comchr && LEFT_BRACKET != comchr)
ch = EAT_WHITE();
else
ch = INC();
...
...
case _T('d') :
while (!done_flag) {
if (_T('x') == comchr || _T('p') == comchr)
if (_ISXDIGIT(ch)) {
number = (number << 4);
ch = _hextodec(ch);
}
else
++done_flag;
else if (_ISDIGIT(ch))
if (_T('o') == comchr)
if (_T('8') > ch)
number = (number << 3);
else {
++done_flag;
}
else /* _T('d') == comchr */
number = MUL10(number);
else
++done_flag;
if (!done_flag) {
++started;
number += ch - _T('0');
if (widthset && !--width)
++done_flag;
else
ch = INC();
} else
UN_INC(ch);
comchr在解析scanf("%d")后,等于'd',INC的代码如下:
static _TINT __cdecl _inc(FILE* fileptr)
{
return (_gettc_nolock(fileptr));
}
#define _gettc_nolock _getc_nolock
#define _getc_nolock(_stream) _fgetc_nolock(_stream)
#define _fgetc_nolock(_stream) (--(_stream)->_cnt >= 0 ? 0xff & *(_stream)->_ptr++ : _filbuf(_stream))
可以看到这里INC最后面会根据(_stream)->_cnt的值决定是否调用_filbuf(_stream),_stream其实就是我们说的标准I/O输入,其结构体如下:
struct _iobuf {
char *_ptr; // 缓冲区第一个未读字节
int _cnt; // 缓冲区剩余未读字节数
char *_base; // 缓冲区基址指针
int _flag;
int _file;
int _charbuf;
int _bufsiz; // 若采用系统标准I/O,该值为4096
char *_tmpfname;
};
typedef struct _iobuf FILE;
函数_filbuf会最终调用windows的系统调用ReadFile用来读入用户输入,这里不再深入讨论。
假设我们输入如下所示:
32<Enter>
此时缓冲区存的内容为:
32n
跟踪INC()函数可以看到变量的变化情况:
第1次INC()后:
_cnt=2 ptr='2' ch='3'
第2次INC()后:
_cnt=1 ptr=0x0a ch='2'
第3次INC()后:
_cnt=0 ptr=0x0a ch=0x0a
注意第3次INC后,ch=0x0a,程序执行:
++done_flag;
接着继续执行:
UN_INC(ch);
再看看UN_INC(ch)最终会调用ungetc(_c,_stm),它会修改_stream的_cnt域,也就是说在执行了UN_INC(ch)后,
_cnt=1, ptr=0x0a。
之后,程序继续执行
scanf("%c",&c);
继续往下:
comchr = *format | (_T('a') - _T('A'));
if (_T('n') != comchr)
if (_T('c') != comchr && LEFT_BRACKET != comchr)
ch = EAT_WHITE();
else
ch = INC();
可以看到程序会继续执行ch = INC()语句,而这个时候,因为(_stream)->_cnt == 1,所以_fgetc_nolock会执行:
0xff & *(_stream)->_ptr++;
而不会调用我们期望的:
_filbuf(_stream);
此时,c就等于0x0a了。
综上所述,问题确实是出在一次
scanf("%d",&a);
最后会调用:UN_INC(ch);改变了stream._cnt的值,从而导致下一次
scanf("%c",&c);
并没有达到我们预期的效果。
至于为什么匹配一个int类型成功后会调用UN_INC(ch)往前移动缓冲区的指针,这个也是可以理解的。做过词法分析的同学都知道,在匹配一个关键字时,我们需要往后看一个字符,是继续往后匹配(非空格时)呢?还是截断(遇到空格)?都需要往后看看才能确定。
补充:
既然前面已经找到了问题所在,只需要在两个scanf之间调用一个fflush(stdin)刷新下输入缓冲区即可,如下代码:
scanf("%d",&a);
fflush(stdin);
scanf("%c",&c);