scanf和回车的关系:
先看一个我们经常使用的代码:
char c;
scanf("%c", &c);
scanf 的各种格式中,%d、%c、%s三种最常用,通常都是以回车作为一次输入的结束。由于对字符解析方式及字符特点不同(如数字中一般不会出现空格回车等特殊字符)不同,%d格式一般不会在连续输入时,遇到问题。而%s和%c却会出现各种各样的问题,比如回车问题,空格问题等。
我们已经习惯了上面的代码,再看下面存在连续输入时的情况:
int i;
char c;
scanf("%d", &i);
scanf("%c", &c);
如果我们为i赋值为3,并以回车的方式结束对i的赋值。再按常规思路为c赋值时,就会发现并不能为c赋值,并且c自动赋值为十进制的10,也就是回车键。why???
要回答这个问题,先看scanf函数的功能和工作原理:
scanf函数这个函数的作用是从标准输入设备获取输入值,并存储到参数列表中指针所指向的内存单元,如果读入成功,函数会返回读入成功的数据的个数;scanf函数的结束通常有3种,遇到空格、回车或者tab键为常用的结束方式;或者按照格式控制符的指定来控制结束,如%5d类的格式;遇到非法输入也会自动结束。
对常用的三种格式,结束符号分别如下:
%d格式输入,默认分隔符是所有的 white-spaces(空格、回车、制表);
%c格式输入,则按ASCII字符考虑,无分隔符。可能会受到之前输入的影响,必要时用fflush(stdin);清除缓冲区;
%s 是 字符串格式,默认分隔符是所有的 white-spaces,输入后自动加入结束符"\0"。
继续上面的由于连续输入带来的问题,往深了说,就涉及到缓冲区了。scanf函数是以删除的方式从缓冲区读取数据(缓冲区中存储来自标准输入的数据)。如果缓冲区是空的,就阻塞之,等待从键盘输入;并且scanf还能对数字输入忽略先导的空白符,如\n\t和空格等(注意,对字符输入并不忽略先导字符,这个也是很自然的道理,因为\n\t和空格在字符中都是合法的字符)。
scanf的缓冲机制和对字符的处理方式就造成了scanf对字符%c和字符串%s的读取时的各种意外。比如上面的例子,回头分析这段代码:
输入了i的值为3然后按回车,当前缓冲区中数据为”3\n”,由回车的作用scanf开始从缓冲区中读取一个%d控制的数据,也就是3,此时缓冲区中还剩”\n”;对下一个scanf函数的格式控制是%c,这个情况下并不忽略先导的空白字符\n,而是直接赋给字符c了。想要查看缓冲区的内容,stdin[s1] ,如果想要查看当前stdin中的内容,一般方法都比较忤逆,可以尝试使用文件操作freopen将stdin中的数据重定向到另外一个file*中。这个另辟一文。
如何解决这个缓冲区和字符解析的问题呢?既然缓冲区有我们不需要的东西,那就清除缓冲区。微软系统中是fflush(stdin)函数可以清除缓冲区,而有的编译系统并没有定义对stdin的fflush操作,就把stdin中的数据读出来:
可以使用fgets()函数,这个函数没有编译器的限制;
或者把缓冲区中多余的东西交给别的函数,如getchar(),具体代码为 while( (c = getchar() ) != ‘/n’ && c != EOF );
上面的方法基于原理,但有点麻烦,尤其是遇到字符%c和字符串%s对回车的处理时。C还提供了gets()函数解决了这个问题,gets()函数是不论中间有什么字符,一律读进来,直到遇到回车符;
C++中,还可以操作stdin的指针,stdin是一个File*类型的数据结构,使stdin->_IO_read_ptr = stdin->_IO_read_end;。但是C中不可以。
scanf和空格键
前面就有说,scanf函数,根据格式的不同,对空白字符的处理也不同。%d格式下,对空白字符不敏感,通常都是作为结束符的;对%c来说,对回车符比较敏感,空格的做为一个普通字符处理的;对%s来说,回车和空格都是当前函数的结束字符,由于缓冲区stdin机制,这里又要特别注意 空格和回车对%s的影响。
%s默认分隔符是所有的 white-spaces,输入后自动加入结束符"\0",使其成为一个字符串(之所以加上\0,是和字符数组char[]的结束符有关的,C中是没有string这个类型的,是使用char[]结构实现字符串)。值得注意的是,即使输入字符的长度足够,%s是宁愿舍弃输入字符,也要把/0加上去的,作为字符串的结束。并且,scanf会忽略缓冲区开头的空格,知道遇到一个非空格字符,才开始向内存中读取数据。
比如,我们想要输入”The C Programming Language.\n”,中间的空格怎么处理呢?
其中一个解决方法是使用gets函数,这个函数是以回车符作为输入结束的标志的;还有一种解决方式是:scanf("%[^\n]", c):
char c[15];
scanf("%[^\n]", c[s2] );
只是scanf("%[^\n]", c);和gets这两种处理方式都是不忽略所有的空格,包括缓冲区开头的空格;这一点和scanf("%s",c);的处理方式不同,%s的方式,忽略缓冲区开头的空格。
另:scanf的返回值
scanf()函数返回的值为:正确按指定格式输入变量的个数;也即能正确接收到值的变量个数。在类型匹配错误的时候,以非正常的方式退出。可以利用scanf函数的返回值判断输入是否正确,并进行流程控制:
int i = 0;char c1[15];while((scanf("%c", &c1[i])!=EOF) && i<14){i++;}
[s1]stdin是标准输入,一般针对键盘,是个FILE* 类型的数据。
[s2]这里注意使用c 还是 &c:
c是个数组,数组 即代表了数组的首地址,所以可以使用c;&c,表示数组c的地址,也就是指首地址,所以也是可以的。但通常c更符合逻辑一点。这在读取%s类型的数据时可以说明这一点:scanf(“%s”, c);