一起talk C栗子吧(第三十八回:C语言实例--你了解scanf吗)



各位看官们,大家好,上一回中咱们说的是获取当前日期和时间的例子,这一回咱们说的例子是C语言中的库函数:scanf。闲话休提,言归正转。让我们一起talk C栗子吧!

看官们,说到C语言中的库函数:scanf,我想大家都认识它,而且知道它是用来从标准输入中(这里可以当作是终端)获取输入的内容,不过关于该函数的一些小细节,我估计大部分人都不是十分了解,接下来我就把这些小的细节说给大家听。


scanf函数有返回值

大家在平常使用scanf函数时,都是为了从终端中获取输入的值,比如:scanf(“%d”,&a)会从终端中获取一个int类型的值,并且把该值保存在变量a中。大家都忽略了它还有一个返回值,该返回值为int类型,它表示scanf从终端中读取值的数量,比如:scanf(“%d”,&a)的返回值为1.如果它返回了0,说明从终端读取数值失败。因此,我们在写程序的时候,最好判断一下scanf的返回值,通过返回值来确认是否从终端中成功地读取数值,这样可以提高程序的健壮性。例如:if(1!=scanf(“%d”,&a)) exit(0);

我们举一个实际的例子来说明:

//相信大家都能看明白,因此main等相关的内容就不写出来了
printf("Please input a number,ex: 3 \n");
if(1 != scanf("%d",&iVal)) // confirm the inputting option
{
    printf("Can‘t get the number \n");
    return 1;
}
else
    printf("get the number %d normally \n",iVal);

编译并且运行程序后可以得到以下结果:

Please input a number,ex: 3
2                 //手动输入数字2
get the number 2 normally 

大家从程序的运行结果中可以看到,程序运行到了else分支中,这说明scanf返回了数值1.并且把我们在终端中输入的数字2,存放到变量iVal中。


scanf函数获取输入内容的优点和缺点

scanf函数的第一个参数是用来控制格式的,它可以控制读取的数值类型,比如读取int类型的数值时需要使用%d.这个功能,我就不多说了,我相信大部分看官们都知道,甚至可能像本山大叔的广告语:地球人都知道。哈哈。

它的另外一个功能是控制输入数值的格式,比如scanf(“%d “,&a)注意:在d后面有一个空格符,该例子表示从终端中获取int类型的数值直到遇到非空白字符为止。这里的空白字符包括空格,TAB键,换行符。

我们举一个实际的例子来说明:

printf("Please input a number,ex: 3 \n");
if(1 != scanf("%d ",&iVal)) // there is a space at the end of %d
{
    printf("Can‘t get the number \n");
    return 1;
}
else
    printf("get the number %d normally \n",iVal);

编译并且运行程序后可以得到以下结果:

Please input a number,ex: 3
6   //手动输入数字6,这时程序没有停止,它还在等待获取非空白字符
7   //再次输入非空白字符,也就是数字7,程序才结束
get the number 6 normally 

大家,我在注释里已经写清楚了,相信大家都能看明白其中的原理。大家可能会问,刚才输入的哪个数字7到哪里呢?其实它还在输入缓冲区中,如果在刚才的程序下面写以下程序:

scanf("%d",&iVal)
printf("get the number %d normally \n",iVal);

它会得到以下结果:

get the number 7 normally  //从输入缓冲区中获取数字,并且存放在变量iVal中

有看官提问:如果把上面的例子中的空格,移动到%d前面,也就是scanf(” %d”,&iVal),那么表示什么意思呢?它和不加空格的意思相同。也就是说:scanf(” %d”,&iVal)和scanf(“%d”,&iVal)得到的效果是一样的,我们通过实际的例子来说明:

printf("Please input a number,ex: 3 \n");
if(1 != scanf(" %d",&iVal)) //  there is a space before %d
{
    printf("Can‘t get the number \n");
    return 1;
}
else
    printf("get the number %d normally \n",iVal);

编译并且运行程序后可以得到以下结果:

Please input a number,ex: 3
6   //手动输入数字6,这时程序停止,输出运行结果。它不会像上面的例子中等待获取非空白字符
get the number 6 normally 

其实,scanf函数本身就有过滤空白符的功能,它从终端中读取数字或者字符,直到遇到非空白的数字或者字符为止,因此我们没有必要在它的第一个参数中加入空格,除非有专门的需要。那种以为加入空格就能过滤空白符的做法是错误的。

我们举个例子说明:

printf("Please input 3 number,ex: 3 \n");
if(3 != scanf("%d%d%d",&iVal,&a,&b)) // confirm the inputting option
{
    printf("Can‘t get the number \n");
    return 1;
}
else
printf("get the number %d %d %d normally \n",iVal,a,b);

编译并且运行程序后可以得到以下结果:

Please input 3 number,ex: 3
 7 8   //手动输入数字7和8,在7前面和后面都有空格,scanf自动过滤它们,等待输入第三个数字
9      //输入8后按下回车,然后输入第三个数字9。回车也是一种空白符,所以被scanf过滤掉了, 直到输入9它才结束
get the number 7 8 9 normally 

大家,我在注释里已经写清楚了,相信大家都能看明白其中的原理。

scanf函数自动过滤空白符的功能可以看作是它的一个优点,不过该功能也有一些副作用,就当作是它的缺点吧。比如在获取完int或者float等非字符类型的数值后,它会把这些数值后面的回车符放到输入缓冲区中,这时再使用scanf或者其它的输入函数(比如getchar)获取字符类型的数值时就会发现获取到是的回车符。我们举个例子来说明:

// get int value firstly
printf("Please input a number,ex: 3 \n");
if(1 != scanf("%d",&iVal)) // confirm the inputting option
{
    printf("Can‘t get the number \n");
    return 1;
}
else
    printf("get the number %d normally \n",iVal);

// get char value secondly
printf("Please input a char ex: a \n");
if(1 != scanf("%c",&ch)) // confirm the inputting option
{
    printf("Can‘t get the number \n");
    return 1;
}
else
    printf("get the number %c<==>%d normally \n",ch,ch);  

编译并且运行程序后可以得到以下结果:

Please input a number,ex: 3
4     //输入数字4,然后回车
get the number 4 normally
Please input a char ex: a
get the number     //我都没有输入字符,它就自动读取了4后面的回车符,并且显示回车
<==>10 normally    //回车符不太直观,所以我显示了它的的ASII码

大家,我在注释里已经写清楚了,相信大家都能看明白其中的原理。另外,大家可以查一下ASII码表,通过查表就能发现回车符的ASII码就是10。


scanf函数会造成缓冲区溢出

使用scanf获取字符串时会有缓冲区溢出的风险。比如scanf(“%s”,array)表示从终端中获取一个字符串,并且把获取到的字符串存放在数组array中。看官们有没有想过,如果用户输入的字符串长度大于数组array的容量时会有什么结果。我可以告诉你,这时候发生了严重的问题:缓冲区溢出

有没有办法能够让用户输入的字符串长度小于数组array的容量呢?这个有点难度,因为用户想输入什么内容,我们没有办法知道,而且也不能阻止用户输入过长的字符串呀。怎么办?我们可以通过修改scanf的第一个参数来手动指定scanf读入串的长度,这样就可以确保scanf从终端中获取的字符串长度比数组的容量小,进而避免发生缓冲区溢出。

下面我们举一个实际的例子:

// be carefull for overfollow
printf("Please input a string ex: hello  \n");
if(scanf("%7s",array)!= 1) // confirm the inputting option, 1 means 1 string
{
    printf("there is a voerfollow \n");
    return 1;
}
else
    printf("get the string %s \n",array);

编译并且运行程序后可以得到以下结果:

Please input a string ex: hello
123456789  //在终端中输入长度为9的字符串
get the string 1234567  

看官们,我们修改了scanf的第一个参数:%s==>%7s这表示读取长度为7的字符串。从程序的运行结果看,scanf完全是按照我们的要求,只从终端中读取了字符串中的前7个字符。这样就可以避免发生缓冲区溢出,提高了程序的健壮性。



看官们,我们在正文中为了说明scanf函数的原理,代码写的很散,完整的代码放到了我的资源中,大家可以点击这里下载使用。

各位看官,关于“你了解scanf吗”的例子咱们就说到这里,希望在我们说完这个章回后,大家能够更加深刻地了解scanf函数。欲知后面还有什么例子,且听下回分解。


版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-03 21:58:24

一起talk C栗子吧(第三十八回:C语言实例--你了解scanf吗)的相关文章

一起talk C栗子吧(第十八回:C语言实例--输出十六进制)

各位看官们,大家好,从今天开始,我们讲大型章回体科技小说 :C栗子,也就是C语言实例.闲话休提, 言归正转.让我们一起talk C栗子吧! 看官们,上一回中咱们说的是栈的例子,这一回咱们说的例子是:输出十六进制. 看官们,我想熟悉C语言的朋友们,对于输出十六进制的内容,肯定都会使用printf函数进行格式化输出. 不过,有时候想输出十六进制时就会有点"不识庐山真面目,只缘身在此山中"的感觉.我在前面的例子中 有一个关于进制转换的例子.当时输出十六进制时使用分别判断10到15,然后依据判

一起talk C栗子吧(第十二回:C语言实例--单链表一)

各位看官们,大家好,从今天开始,我们讲大型章回体科技小说 :C栗子,也就是C语言实例.闲话休提, 言归正转.让我们一起talk C栗子吧! 看官们,上一回中咱们没有说具体的例子,而且是说了例子中的文件组织结构.这一回咱们继续说C例子, 说的例子是链表,更准确的说法叫作单链表.咱们不但要说C例子,而且会在例子中使用上一回中说过的 文件组织结构,就当作是举例说明文件组织结构的使用方法. 有点一石二鸟的感觉,哈哈. 链表定义 看官们,所谓的链表其实就是一组元素通过一定的方式链接在一起.比如我们坐的火车

一起talk C栗子吧(第一百三十二回:C语言实例--从内存的角度看进程和线程)

各位看官们,大家好,上一回中咱们说的C程序内存布局的例子,这一回咱们说的例子是:从内存的角度看进程和线程.闲话休提,言归正转.让我们一起talk C栗子吧! 看官们,我们刚刚介绍完C程序的内存布局,我们趁热打铁,从内存的角度来分析一下进程和线程. 不管是进程还是线程,他们都会加载到内存中才能运行,因此他们在内存中的布局和其它C程序的内存布局完全相同.进程和线程的内存布局也分为代码区,数据区,堆区和栈区. 对进程来说,父子进程只共享代码区中的内容,父子进程拥有各自的数据区,堆区和栈区.而且它们只能

一起talk C栗子吧(第三十二回:C语言实例--再谈最大公约数)

各位看官们,大家好,我们在第九回中一起说过最大公约数的例子,这一回咱们继续说该例子.闲话休提, 言归正转.让我们一起talk C栗子吧! 关于最大公约数的内容,我们在第九回中提到过,如果大家忘记了的话,可以点击这里查看原文. 我们今天继续说最大公约数,说的内容可以看作是对第九回的补充.和第九回一样,我们还是通过辗转相 除法来求最大公约数.不过我们在第九回中主要通过循环的方式来实现辗转相除法,今天我们使用另外一 种方式来实现辗转相除法. 看官们,正文中就不写代码了,详细的代码放到了我的资源中,大家

一起talk C栗子吧(第二十二回:C语言实例--队列一)

各位看官们,大家好,上一回中咱们说的是表达式求值的例子,该例子使用了栈,这一回咱们说的是栈的 兄弟:队列.闲话休提,言归正转.让我们一起talk C栗子吧! 我们在这里说的队列是一种抽象的数据结构,大家不用想的太抽象了,哈哈,其实它和我们日常生活中所 见的队列一样.不管怎么样,我们还是举一个容易理解的例子:大家在假期出去旅游的时候,都有过排队 买门票的经历吧.游客们在售票点的窗口前排成了一长串队列,售票人员先把门票卖给排在队列前面的游 客,买到门票的游客拿着门票兴高采烈地离开了队列,刚来到售票点

一起talk C栗子吧(第二十八回:C语言实例--希尔排序)

各位看官们,大家好,上一回中咱们说的是插入排序的例子,这一回咱们说的例子是:希尔排序.闲话休 提,言归正转.让我们一起talk C栗子吧! 希尔排序是对插入排序的一种改进,希尔排序的原理:先将容器分成若干子容器,然后分别对子容器进行 插入排序,当子容器全部排序完毕后,对全部元素进行一次插入排序. 希尔排序的实现步骤: 1.选取一个增量,增量的大小可以自己定义,其大小在1到容器长度之间: 2.以容器头部到增量位置的元素为起点,从起点到容器尾部依次遍历容器: 3.在步骤2中的遍历过程中,选择一个当前

程序员的奋斗史(三十八)——大学断代史(二)——我与数据库的故事

文/.温国兵 惰性人皆有之,也算是人的一大天性.几日之前便构思好此文,怎奈每日杂事繁多,今日才提起笔,作下此文.本文谈谈我与数据库的故事. 说起和数据库结缘,还得从大一说起.大一刚开始接触C语言,每日就沉浸在无止境的代码中.在网上查资料的过程中,看到别人用C语言写了一个小型的图书操作程序,数据库采用的是SQL Server,运行出来的效果图很炫,惊叹原来还可以这样管理数据.熟知C语言的同学肯定对文件操作不陌生,当时我们写程序如果有静态数据,都是放在文件里的.直到后来做的C语言课程设计,我也是一大

NeHe OpenGL教程 第三十八课:资源文件

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十八课:资源文件 从资源文件中载入图像: 如何把图像数据保存到*.exe程序中,使用Windows的资源文件吧,它既简单又实用. 欢迎来到NeHe教程第38课.离上节课的写作已经有些时日了,加上写了一整天的code,也许笔头已经

QT开发(三十八)——Model/View框架编程

QT开发(三十八)--Model/View框架编程 一.自定义模型 1.自定义只读模型 QAbstractItemModel为自定义模型提供了一个足够灵活的接口,能够支持数据源的层次结构,能够对数据进行增删改操作,还能够支持拖放.QT提供了 QAbstarctListModel和QAbstractTableModel两个类来简化非层次数据模型的开发,适合于结合列表和表格使用. 自定义模型需要考虑模型管理的的数据结构适合的视图的显示方式.如果模型的数据仅仅用于列表或表格的显示,那么可以使用QAbs