本文主要是介绍Bitter of scanf,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
说明一下:
这篇blog其实是私人写给好朋友的。写的不好,大家就不要太在意
之初学习C,是在VC下面的(其实第一个hello world都是在turbo C下面敲出来的,蓝色屏幕,hello world,别提当时多激动了)。
学到后面,见识过各路高手批评我的代码风格很乱,后面才慢慢改,原谅我的无知,看的第一本教材是用的老谭的PDF版本的(高中毕业,在家没事干,窝在家里对着老掉牙的turbo C 敲代码,只是觉得很好玩,觉得这东西很神奇,我居然可以让电脑按照我的想法做事情,原来这就是编程。。。)大概过了一个月的样子才换成VC,后面又嫌弃VC换成了code::blocks
问题来鸟。。。慢慢对自己代码风格有要求的时候,在VC下学会了一个当时看似很酷的skill。
while(!scanf("%d",&varible))
{printf("sorry, your entered data is error!\please enter again!\n");
}
多漂亮的类型检查!
高中暑假的时候就只知道scanf,scanf。然后由scanf引起的各种bug,痛苦不堪啊。。。那种纠结是凌晨3点的痛。。。。。
当时只是知道函数返回值的概念,和现在的ZM差不多,但是很少利用这个返回值,我现在想尽力传递给ZM的思想就是领悟返回值的意义并学会利用返回值写出更好的代码,简直可以说逻辑漂亮的代码。可能我最近对你要求有点高吧。。。不过我希望ZM尽快的入门,少走我走过的弯路,当我告诉你一些细节的问题的时候,那些经验可能都是我熬了很久才学会的。短时间内,你可能会觉得难,这是正常的,但是你要知道,越是困难,经历过之后,你的收获也就越大。就好比打羽毛球一样,和高手打球进步速度是飞一般的。和水平相近或者更低的人,怎么都不能进步。我随不是高手,但是想把自己的经验告诉你,你也好少走点弯路,时间不多了,大二了。
废话少说,说scanf。
int scanf(const char *format, ...);
scanf的declaration 。你可能现在不知道指针和变长参数,没关系,不用管它,利用自己可以利用的信息,可以看懂返回值的类型是int。那么说,scanf是有返回值的。具体返回值的意义是什么呢?
要学着去man *** 。 你学C比我的起点高,教材也好,解决问题的渠道也比我当时多。当时我遇到问题了就一个人苦逼的郁闷。。。是到了大一开学军训的时候才把CPP看到中间的,想来当时很开心,不管走到哪儿都拿着手机看CPP,路上,开会,下课。有时间就看CPP。读一本好的书就像和大师交流一样。
你现在是在linux下学习C语言,可以说比很多人的起点都高。非常纯净的C环境,编译器是最标准的GCC。当然我知道,已经给你很大的挑战,所以没要你学terminal下用vim写C,而不是用code::blocks,不过你愿意的话,也可以学,也不是很难,其实我已经悄悄的教过你一点terminal的东西了。很多东西都是我花了很多时间才知道的,比方linux,我几乎用了一个暑假来入门,看了《鸟哥的linux私房菜》,我才知道怎么使用linux。而你是直接用的,我也反思,一下子都告诉你,接受不了也正常,所以当我问的急切的事情,别生气。。。我只是想你早点秒掉C。
好吧。。。不扯淡了
man scanf,你想要的他都会告诉你
RETURN VALUE
These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.
The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs.
EOF is also returned if a read error occurs, in which case the error indicator for the stream (see ferror(3)) is set, and errno is set indicate the error.
scanf的返回值是正确读入的数据变量的个数。如果没有正确读入一个数据,返回0,出错返回EOF。
Question is coming. BUG is coming.
#include "stdio.h"int main()
{int ret = 0;int var1 = 0;while(!(ret = scanf("%d",&var3))){printf("scanf error!\nplease enter again!\n");}printf("returned value : %d\n",ret);return 0;
}
简单到死的测试代码。GCC bug了!
我故意输入一个字符例如'a' 然而,程序死在whlie循环里面了。一直输入下面这个报错提示
scanf error!
please enter again!
scanf error!
please enter again!
scanf error!
please enter again!
scanf error!
please enter again!
scanf error!
我当时就呆了!自己写了这么就的类型检查,居然bug了。。。完全没有意识到啊,gdb调试,发现根本就没有重新scanf读入,每次到scanf直接跳过了。第一感觉以为是编译器优化掉了,之前keil有类似的经历,相当之坑爹。。。关闭优化,发现还是一样的bug。。。不淡定了,各种google 百度。得到结论,是编译器的问题。其实还有个问题,但是我这里没有VC了,所以不方便在VC上测试,就是这篇blog最初给出的scanf类型检查方法其实也有个问题。可能有点不好理解,不过我觉得有必要跟说了。因为你已经接触了scanf,scanf那点事还是早知道好。。。
首先貌似我要给你介绍缓冲区这个概念。。。
缓冲区叫buffer。我们的标准输入都是从键盘或者其他输入设备输入的,然后把输入信息储存在缓冲区。如果你不能很具体的想象缓冲区是什么。你就想成是个盒子。。。然后这个盒子里面会暂时的存放数据,注意是暂时的哦。不然也不会叫“缓冲”区。。。
我们把输入信息输入到缓冲区,只有刷新缓冲区的时候,标准输入储存在缓冲区的信息才会显示在屏幕上面。如果不知道刷新是什么。你就想,刷新就是那个盒子(缓冲区)里面的东西全部都倒出来给你看。
背景知识over
正题,scanf在读入的时候,scanf("%d",&number);这个时候你不仅仅输入了一个int 类型数据,你输入了数据,你发现没有,你还敲击了键盘上面的一个键!which one? Enter!回车键!发现没有,我们每次输入数据都要确认输入,而确认输入就是通过回车,对不?这个时候键盘就会输入一个额外的信息!回车符号'\n'
这个时候你的缓冲区就存在两个数据 number 和' \n'! 当下一次scanf("%d",&number)的时候,程序就不会等待你输入数据写到number的地址里面去了。他会直接读取缓冲区的第一个数据,因为之前的number已经被读取了,此时缓冲区就只剩下‘\n’于是,‘\n’被读入number的地址,scanf发生错误(应该读入整形数据),于是scanf返回 0!
while(!scanf("%d",&varible))
{printf("sorry, your entered data is error!\please enter again!\n");
}
!scanf("%d",&varible) 这个表达式的值就一直为真,于是dead loop。。。。
怎么搞定这个bug呢?
VC下面常用的一种方法是fflush(stdin),我以前就这么做的,强制刷新缓冲区,把缓冲区的那个‘\n’刷新去除,保证scanf读取的是数据,而不是换行符号。
熟话说,一个bug让你发现另外的bug。。。。我必须感谢你让我发现这个bug。不然我一直不知道,被“欺瞒“,以至于以后可能发生很严重的bug。
新的bug就是,GCC是不支持这个fflush()函数的!我不知道!
VC下面,
while(!scanf("%d",&varible))
{fflush(stdin);printf("sorry, your entered data is error!\please enter again!\n");
}
这样就可以了
GCC不行,那句fflush简直就是被无视了,我google发现GCC不支持fflush。
解决方案,搞定那个换行符,OK。怎么搞定?getchar(); 然后丢弃,不要赋值给任何变量。
while(!(ret = scanf("%d",&varible)))
{getchar();printf("sorry, your entered data is error!\please enter again!\n");
}
#include "stdio.h"int main()
{int ret = 0;int var1 = 0;while(!(ret = scanf("%d",&var1))){getchar();printf("scanf error!\nplease enter again!\n");}printf("returned value : %d\n",ret);return 0;
}
运行结果:
l@ubuntu:~$ ./b.out
a
scanf error!
please enter again!
f
scanf error!
please enter again!
w
scanf error!
please enter again!
1
returned value : 1
可以看出,我故意输入与格式说明符号不一致的字符a,然后scanf就读入错误,返回值是0,取反是1,进入循环,去除换行符,然后提示读入错误,再次scanf读入,再判断是否正确读入,这实际上就实现了scanf的数据读入的类型检查,不是吗?非得你正确输入一个数据不可!输入错了,就再输入
我那年暑假第一次看到这个技巧的时候简直觉得漂亮极了
补充一下:
viewer 看到很友好的给予了补充,如果输入abcdefg 那么缓冲区会保留 剩下的bcdefg和回车换行符,于是这时候getchar()不能一次性读取所有的缓冲区字符,于是还是会有bug。
#include "stdio.h"int main()
{int ret = 0;int var1 = 0;char temp = 0;while(!scanf("%d",&var1)){while('\n' != getchar()){}printf("scanf error!\nplease enter again!\n");}printf("returned value : %d\n",ret);return 0;
}
Now,we killed all the bug in this program.
好吧。。。更新说明一下,之前看过printf的实现,觉得想补充一下scanf的库函数实现,把scanf的所有细节都挖出来,但是花了很长时间去找scanf的implementation,看过之后又觉得没有必要,没有必要太过于纠结太底层的实现,暑假纠结printf的实现到凌晨四点,兴奋到不能睡觉,但是后来就觉得有些“好奇心”是好的,但是不能太过于把时间花在那上面,我们可以把更多的时间花在利用库函数的基础上,去写出更好的程序,而不是纯粹去满足自己的好奇心。
过段时间我把printf的实现share一下。挺有意思,不过探索的过程比较痛苦。当然我还是提倡看内核实现的。。。
这篇关于Bitter of scanf的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!