专栏首页秘籍酷原创 | 函数 scanf 前世今生

原创 | 函数 scanf 前世今生

C语言初学者,最常用的函数当属 printf() 和 scanf() ,前者无用多言,毕竟鼎鼎大名的 HelloWorld 也要仰仗它出手,printf()函数只管将数据输出至屏幕,基本没有什么出错的机会,而后者 scanf() 则隐晦许多,甚至有些自称编程老鸟也未必深谙其内涵,这篇小文,作为初级出门装,建议初学者们第一时间买上。

先来一段白痴式代码(idiot.c),由易入难,以体现我一贯的思维严谨性:

int age; scanf("%d", &age); printf("哇!您 %d 岁了!\n", age);

上述代码作何解读? 简单,就是让你从标准输入设备(也就是键盘),敲入一个十进制整数,然后放进变量 age 之中。然后做一惊一乍状爆出你的年龄。

既然本文面向C语言初学者,我也不怕做个长舌妇,把话说得更加完(luo)满(suo)一点,来提几个找抽的问题:

① 为什么是从键盘输入?

② 为什么是十进制整数?

③ 如果我就是要胡乱输入,你奈我何?(划重点)

不急,来一拳拳抡死这个智障

① 为什么是从键盘输入? 因为 scanf() 函数默认就是从键盘读取数据呀!好吧,这个回答可能会觉得索然无味,但如果我们认识 scanf() 的其他几个同门亲兄弟的话,可能感觉会有点不一样,他们是:

sscanf(); // 专门从某块内存读取数据 fscanf(); // 专门从某个文件读取数据

因此你现在知道,scanf() 只是家族众多兄弟中的一员,大家各有所好而已,没错,scanf() 就是专门从键盘读取数据的那个家伙。

② 为什么是十进制整数?因为代码中的 %d 就是 decimal 的首字母,这表明此时 scanf() 就是希望你输入一个十进制整数,这个 %d 就是所谓的格式控制符。那你会问了,如果希望输入别的什么进制的整数呢?或者浮点数、字符串呢?你猜到了,那将会有不同的控制符来表示,比如:

有了上表,可见我没骗你,%d 真是输入十进制整数的意思!

③ 如果我就是要胡乱输入,你奈我何?这个问题是本文要讨论的重点,先来看看一个很皮的家伙,是怎么戏弄上面这段程序的:

当某人输入二百五的时候,这段程序很老实地说他已经250岁了,虽然看起来无可指责,毕竟年龄是他自己输入进去的,但我们总会觉得这个程序缺少一点脑筋,正常来讲它应该要把人的年龄限制在一个合理的范围,比如:1 - 100岁之间。当人类输入一个不合理的年龄的时候,程序应该要能指出人类的愚蠢,很可惜我们的白痴程序没能做到这一点。

解决这个BUG比较简单,只要做个数值判断就可以了:

int age; scanf("%d", &age); if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);

再来看更离谱的错误:

当某个人类输入一个完全不是年龄的东西的时候,程序彻底傻X了,输出了一个完全不合理的非法年龄,你可以理解为:程序陷入了迷乱

接下来,我们要改造一下程序,使之具备一定的智能。但在此之前,需要对 scanf() 的来龙去脉理清头绪。

首先,当我们说函数 scanf() 是从键盘获取数据的时候,我们要承认这个说法是不严谨的,严格讲,scanf() 只是从键盘对应的文件的缓冲区中读取数据,而无法直接读取键盘敲入的数据,可以想象,键盘到 scanf() 中间有一段路程要求,要完美讲清楚这个过程显然要画出图来,以示诚意,是时候展现我的绘画才艺了,请欣赏:

对上图做点解释:

① 手指敲击键盘时,数据由键盘的驱动程序读取,并被保存在驱动程序中,此时跟scanf()没有半毛钱关系。

② 输完了并敲击回车键后,驱动程序将数据送往缓冲区,并通知 scanf() 来搬运数据。

③ scanf() 带着参数 %d 来到缓冲区,跟缓冲区中的数据格式对了对眼神,如果发现格式没错,那就搬走,放到你指定的 age 里面,如果格式不对,那 scanf() 将一走了之,不干任何事情。

④ 如果scanf() 成功搬运了一个数据,那就返回1,如果成功搬运了两个数据,那就返回2,如果没跟任何数据对上眼神,就返回0。

有了以上的工作流程,我们就可以改进上面的 idiot.c ,改成 regular.c。我们可以通过判断 scanf() 的返回值,来知道它究竟搬运了数据没有:

int age; if ( scanf("%d", &age) != 1) printf("叫你输入整数,别整些没用的!\n"); else if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);

来试试效果:

现在程序有点像正常人了,但这还不够,还有个不大不小的BUG,请看:

输入 23abc 本来应该是一个不正经的年龄,但是程序没报错,而是直接将前面的23读取并汇报了年龄。换句话讲,当输入 23abc 的时候,scanf() 是正常工作的,它返回了 1,正常拿到了整数数据并搬到了 age 里,只不过留下了未能匹配格式的 abc 在缓冲区中没有收拾,造成以上BUG。

这个问题的解决,就不能简单地判断 scanf() 的返回值,而是在他返回正常的数据个数之后,还要判断缓冲区中是否还残留有非法格式的数据,这个怎么判断呢?这就要用另一个函数了,就是它:

getchar();

这个函数的作用,就是读取缓冲区中的一个字符。你想,如果我们输入的是正常的年龄,比如 23 ,那么当 scanf() 把 23 搬走之后,缓冲区中必然留下的是一个回车键,即 "\n",否则,缓冲区中必然会留下其他的字符,根据这个思路立刻修改程序,变身为 smart.c :

int age; if ( scanf("%d", &age) != 1 ) printf("叫你输入整数,别整些没用的!\n"); else if ( getchar() != '\n' ) printf("数字后面别拖个尾巴,OK?\n"); else if( age < 1 || age > 100) printf("您确定您不是妖怪?\n"); else printf("哇!您 %d 岁了!\n", age);

最后测试一下程序:

本文分享自微信公众号 - 秘籍酷(mijiku040),作者:林世霖

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux基础(vi,我的最爱)

    很多时候我们需要在多个源程序之间实现函数、宏定义、外部变量等的跳转查询,甚至有时候需要到内核或库源代码里窥视他们真面目,这对于windows的各种IDE而言都是...

    用户2617681
  • Linux并发(POSIX信号量)

    System-V的信号量是老古董,除非万不得已,否则我们一般用POSIX信号量,好用、简单、靠谱。

    用户2617681
  • Linux基础(文件类型)

    Linux下一切(除网卡)皆文件的概念深入人心,那么世界万物在Linux系统中被分成多少种文件呢?他们有什么特点呢?

    用户2617681
  • SaltStack 远程命令执行漏洞(CVE-2020-11651、CVE-2020)

    所以我们部署系统默认生成的防火墙,总控机器[4505,4506]端口只允许来自各个 Master 机器源 IP,各个 Master 机器[4505,4506]端...

    糖果
  • C# 遍历枚举

    但是这个方法的性能比较差,可以使用一个库。首先打开 Nuget 安装 Enums.NET

    林德熙
  • python实现停车管理系统

    Python停车管理系统可实现车辆入库,按车牌号或者车型查询车辆,修改车辆信息,车辆出库时实现计费,按车型统计车辆数和显示全部车辆信息的功能

    砸漏
  • rgb cmyk lab的区别

    用户7657330
  • 【DB笔试面试583】在Oracle中,什么是绑定变量分级?

    绑定变量分级(Bind Graduation)是指Oracle在PL/SQL代码中会根据文本型绑定变量的定义长度而将这些文本型绑定变量分为四个等级,不同等级分配...

    小麦苗DBA宝典
  • Asp.Net Forms认证在移动平台中遇到的一个问题以及调查过程

    我们项目的网站的移动版是基于Asp.Net平台开发的,用户登录也是基于Asp.Net的Forms认证,在整个开发和测试过程中没有发现任何客户登录异常,但是发布后...

    葡萄城控件
  • CTF| 攻击取证之内存分析

    在CTF中,内存取证一般指对计算机及相关智能设备运行时的物理内存中存储的临时数据进行获取与分析,提取flag或者与flag相关重要信息。

    漏斗社区

扫码关注云+社区

领取腾讯云代金券