Bitter of scanf

2024-06-06 10:18
文章标签 scanf bitter

本文主要是介绍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。


我的解决方案(个人感觉有点耍流氓式的不停调用getchar,哈哈):如下
#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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1035825

相关文章

HDU2523(论scanf的重要性)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2523 解题思路: 先把a数组排个序,然后把| xi - xj |的所有组合值求出来,把b数组在排个序。这时候要考虑出现1、1、1、2、2、3这种相邻两个一样的情况,开一个vis标记数组把相邻的数进行合并,这样就可以顺利取到第k大的值。 特别说明,论scanf和printf的重要性,用cin

ACM比赛中如何加速c++的输入输出?如何使cin速度与scanf速度相当?什么是最快的输入输出方法?

在竞赛中,遇到大数据时,往往读文件成了程序运行速度的瓶颈,需要更快的读取方式。相信几乎所有的C++学习者都在cin机器缓慢的速度上栽过跟头,于是从此以后发誓不用cin读数据。还有人说Pascal的read语句的速度是C/C++中scanf比不上的,C++选手只能干着急。难道C++真的低Pascal一等吗?答案是不言而喻的。一个进阶的方法是把数据一下子读进来,然后再转化字符串,这种方法传说中

c语言(scanf函数)

scanf函数注意事项: 1.输入字符 #include <stdio.h>int main(){char c;printf("请输入一个字符:\n");scanf("%c",&c);printf("输入的是%c\n",c);return 0;} 2.一次性输入多个数值,并且以某些符号隔开,应加以提示。( 注意:数值之间的分隔符是任意的,不一定要用中划线-,可以是逗号、空格、

C语言gets()与scanf()

先列举几种情况,以下的代码只有输入方式有变化: 1. #include<string>#include<math.h>using namespace std;int main(){char m[33];char n[33];gets(n);gets(m);printf("%s",m);//正常输出return 0;} 2. #include<string>#include<math.

iOS笔记:第二篇 C语言scanf函数、变量和运算符

第一节  scanf函数 一、scanf函数的作用和语法 1>作用:可以在程序运行的时候让用户输入数据将用户输入的数据存储在变量起来。 2>语法:scanf("格式控制字符串",变量的地址列表);        二、scanf函数的用法 1、scanf函数是1个阻塞式的函数.当执行到这句代码的时候 CPU的执行会暂停,等着用户输入数据。数据输入完毕之后,才会继续往下执行。 2

hdu1014简单公式题(题目已经给出)0~MOD-1以及scanf的考察

/*题目意思:seed(x+1) = [seed(x) + STEP] % MOD 输入step和mod,按照公式,seed(x)从0开始,进行运算循环。当再次遇到0时,完成一次循环。 做题思路:累计一次循环中出现的个数,判断是否等于mod*/ #include<iostream>#include<cstdio>using namespace std;int main(){int

【C++】scanf()和scanf_s()函数

scanf()函数是标准C中提供的标准输入函数,用以用户输入数据 scanf_s()函数是Microsoft公司VS开发工具提供的一个功能相同的安全标准输入函数,从vc++2005开始,VS系统提供了scanf_s()。在调用该函数时,必须提供一个数字以表明最多读取多少位字符。 原因和区别: scanf()在读取数据时不检查边界,所以可能会造成内存访问越界: //例如:分配了5字节的空间但

计算机二级题--函数(scanf)章节

1.scanf函数 1.  2. A:默认使用scanf函数的时候,如果空格,回车或跳格就结束了,不能再继续输入了 (同上知识点)

解决c语言中调用scanf()或者strcpy()函数报错问题

引言    最近在使用VS实现C语言程序的时候经常会遇到一些错误,但是这些函数的确是可以正确使用的,可能在 vc++6.0上就可以正常运行,很多参考书的代码都是以vc++6.0为标准的代码,所以这样我们在实现demo的时候会花费 一下时间在没有必要的错误上面,下面就给大家介绍一些解决方案。   问题重现   error C4996: 'scanf

scanf()、getchar()、gets()、cin之间的区别

问题描述一:(分析scanf()和getchar()读取字符)           scanf(), getchar()等都是标准输入函数,一般人都会觉得这几个函数非常简单,没什么特殊的。但是有时候却就是因为使用这些函数除了问题,却找不出其中的原因。下面先看一个很简单的程序: 程序1:      #include <stdio.h>int main