scanf()函数释疑(上)

一、序言

scanf()函数的控制串的使用

例1.

#include "stdio.h" 
int main(void) 

int a,b,c; 

scanf("%d%d%d",&a,&b,&c); 
printf("%d,%d,%d\n",a,b,c); 
return 0;
}

运行时按如下方式输入三个值:

3□4□5 (输入a,b,c的值)

3,4,5 (printf输出的a,b,c的值)

(1) &a、&b、&c中的&是地址运算符,分别获得这三个变量的内存地址。
(2) "%d%d%d"是按十进值格式输入三个数值。输入时,在两个数据之间可以用一个或多个空格、tab键、回车键分隔。
以下是合法输入方式: 
① 3□□4□□□□5
② 3
4□5
③ 3(tab键)4
5

例2.

#include "stdio.h" 
int main(void) 

int a,b,c;

scanf("%d,%d,%d",&a,&b,&c);
printf("%d,%d,%d\n",a,b,c);

return 0;
}

运行时按如下方式输入三个值:

3,4,5 (输入a,b,c的值)

或者

3,□4,□5 (输入a,b,c的值)

3,□□□4,□5 (输入a,b,c的值)
......
都是合法的,但是","一定要跟在数字后面,如:
3□,4,□5 就非法了,程序出错。(解决方法与原因后面讲)

再如:

1、sacnf()中的变量必须使用地址。

   int a, b;
scanf("%d%d",a,b); //错误
scanf("%d%d",&a,&b);

2、scanf()的格式控制串可以使用其它非空白字符,但在输入时必须输入这些字符。

例:
scanf("%d,%d",&a,&b); 
输入: 3,4 (逗号与"%d,%d"中的逗号对应) 
scanf("a=%d,b=%d",&a,&b); 
输入: a=3,b=4 ("a=","b=",逗号与"%d,%d"中的"a=","b="及逗号对应)

3、在用"%c"输入时,空格和“转义字符”均作为有效字符。

例:
scanf("%c%c%c",&c1,&c2,&c3); 
输入:a□b□c 
结果:a→c1,□→c2,b→c3 (其余被丢弃)

scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。
①遇空格、“回车”、“跳格”键。
②遇宽度结束。
③遇非法输入。

上篇就写到这里吧,第三小节的例程"抄"自网上的一个教程(原因有二:一,可以少打不少字。二,□我不知道怎么打。^_^),并删去其中的错误之处.这里也顺便提醒本文读者一句:凡事要亲力而为,即使是经典的书籍也不免有疏漏之处,所以,用编译器说话是最可靠的,是对是错请编译器告诉你。

scanf()函数释疑(下)

在上篇我已经表达了两个观点,这里再重申一次:1。本文仅对scanf()函数控制串运用进行探讨,本文所有例程并不构成编程建议。2。凡事要亲力而为,不同平台不同编译器,可能会有不同结果。本文所有例程均在WIN-TC+windows Me下调试。

四、 scanf()函数控制串运用出现的常见错误及对策技巧

问题一:程序编译通过,但运行错误提示如下:
scanf : floating point formats not linked
Abnormal program termination

出错示例程序:

#include <stdio.h>
int main(void)
{
int i,j ;
float s[3][3];

/*这里*/

for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf("%f",&s[i][j]);

for(i=0;i<3;i++)
for(j=0;j<3;j++)
printf("%f",s[i][j]);
}

这实际上是个与本文主题无关的问题,也是与scanf()函数无关,是编译器的问题。

原因很明确:没有链接浮点库。早期系统内存资源紧张,多维浮点数组占用内存量大(一维浮点数组就没有此问题),因此TC在编译时尽量不加入无关的部分,在没发现需要浮点转换程序时,就不在可执行程序中安装这个部分。而有时TC又不能正确识别实际上确实需要做浮点转换,因此就会出现上面错误。

解决的方法:告诉TC需要做浮点数的输入转换。将以下语句加入上面程序中标有/*这里*/处。

方法一: float c;
scanf("%f",&c);

方法二: float c,*t;//此处手误,现已更正&t===》*t;

t=&c;
.....

也就是说,编译器只要有浮点转换的线索,TC就会把浮点转换连上,所以一般大一点的程序里的就会有浮点变量反而没有此问题。

但问题到此并没结束,我在上面有句“一维浮点数组就没有此问题”,那么我们来看看这样行不行:

#include <stdio.h>
int main(void)
{
int i,j ;
float s[3][3],*ptr;

ptr=&s[0][0];

for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf("%f",ptr+i*3+j);

for(i=0;i<3;i++)
for(j=0;j<3;j++)
printf("%7.2f\n",s[i][j]);
}

这样我们就把多维浮点数组降为一维浮点数组来处理,调试一下,程序运行正常。这说明TC编译器仅在处理多维浮点数组(结构体)有此“未链接浮点库”的问题。
类似问题:
#include "stdio.h"
#include "math.h"
main()
{ float a[3][3],sum=0;int i,j;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf("%f",&a[i][j]);
for(i=0;i<3;i++)
sum=sum+a[i][i];
printf("%f",sum);
getch();} 为什么结果一闪而过?该程序求矩阵对角元素之和

对于这个程序,由于出现了上面的问题,运行时系统提示:
      scanf : floating point formats not linked
Abnormal program termination 在此之后系统就直接结束运行,所以屏幕一闪而过。
验证:
#include "stdio.h"
#include "math.h"
main()
{ float a[3][3],sum=0;int i,j;
sleep(7); /* 程序在此停留7秒钟,以判断运行位置*/
for(i=0;i<3;i++)
for(j=0;j<3;j++)
scanf("%f",&a[i][j]);
for(i=0;i<3;i++)
sum=sum+a[i][i];
printf("%f",sum);
getch();}
然后再运行一次就可以看到上次运行时系统提示的 scanf : floating point formats not linked
Abnormal program termination 
解决:在sleep( )语句之前增加如下内容:
float b;

printf("Input a float number :\n");
scanf("%f",&b);
用来告诉TC需要做浮点数的输入转换,问题就可以解决了

问题二:scanf()函数不能正确接受有空格的字符串?如: I love you!

#include <stdio.h>
int main()
{
char str[80];

scanf("%s",str);     没有寻址符&
printf("%s",str);

return 0;
}

输入:I live you!
输出:I

scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。
①遇空格、“回车”、“跳格”键。
②遇宽度结束。
③遇非法输入。

所以,上述程序并不能达到预期目的,scanf()扫描到"I"后面的空格就认为对str的赋值结束,并忽略后面的"love you!".这里要注意是"love you!"还在键盘缓冲区(关于这个问题,网上我所见的说法都是如此,但是,我经过调试发现,其实这时缓冲区字符串首尾指针已经相等了,也就是说缓冲区清空了,scanf()函数应该只是扫描stdin流,这个残存信息是在stdin中)。我们改动一下上面的程序来验证一下:

#include <stdio.h>
int main()
{
char str[80];
char str1[80];
char str2[80];

scanf("%s",str);/*此处输入:I love you! */
printf("%s",str);
sleep(5);/*这里等待5秒,告诉你程序运行到什么地方*/
scanf("%s",str1);/*这两句无需你再输入,是对键盘盘缓冲区再扫描 */
scanf("%s",str2);/*这两句无需你再输入,是对键盘盘缓冲区再扫描 */
printf("\n%s",str1);
printf("\n%s",str2);
return 0;
}

输入:I love you!
输出:I
love
you!

好了,原因知道了,那么scanf()函数能不能完成这个任务?回答是:能!别忘了scanf()函数还有一个 %[] 格式控制符(如果对%[]不了解的请查看本文的上篇),请看下面的程序:

#include "stdio.h"
int main()
{
char string[50];

/*scanf("%s",string);不能接收空格符*/
scanf("%[^\n]",string);
printf("%s\n",string);
return 0;
}

问题三:键盘缓冲区残余信息问题

#include <stdio.h>
int main()
{
int a;
char c;

do
{
scanf("%d",&a);
scanf("%c",&c);
printf("a=%d c=%c\n",a,c);
/*printf("c=%d\n",c);*/
}while(c!='N');
}

scanf("%c",&c);这句不能正常接收字符,什么原因呢?我们用printf("c=%d\n",c);将C用int表示出来,启用printf("c=%d\n",c);这一句,看看scanf()函数赋给C到底是什么,结果是 c=10 ,ASCII值为10是什么?换行即\n.对了,我们每击打一下"Enter"键,向键盘缓冲区发去一个“回车”(\r),一个“换行"(\n),在这里\r被scanf()函数处理掉了(姑且这么认为吧^_^),而\n被scanf()函数“错误”地赋给了c.

解决办法:可以在两个scanf()函数之后加个fflush(stdin);,还有加getch(); getchar();也可以,但是要视具体scanf()语句加那个,这里就不分析了,读者自己去摸索吧。但是加fflush(stdin);不管什么情况都可行。

函数名: fflush 
功能: 清除一个流 
用法: int fflush(FILE *stream);

#include <stdio.h>
int main()
{
int a;
char c;

do
{
scanf("%d",&a);
fflush(stdin);
scanf("%c",&c);
fflush(stdin);
printf("a=%d c=%c\n",a,c);

}while(c!='N');
}

这里再给一个用“空格符”来处理缓冲区残余信息的示例:

运行出错的程序:

#include <stdio.h>
int main()
{
int i;
char j;
for(i = 0;i < 10;i++)
{
scanf("%c",&j);/*这里%前没有空格*/每次输入一个字符,回车后,相当于多了一个字符,结果就是只能输                                                                           入5次!!!
}
}

使用了空格控制符后:

#include <stdio.h>
int main()
{
int i;
char j;
for(i = 0;i < 10;i++)
{
scanf(" %c",&j);//注意这里%前有个空格!!!!!这里仅是对于字符%c,如果是%d数字还是输入10次,///因为空格回车符,\n之类都是字符,所以用%c时出问题!

}
}

可以运行看看两个程序有什么不同。

问题四如何处理scanf()函数误输入造成程序死锁或出错?

#include <stdio.h>
int main()
{
int a,b,c; /*计算a+b*/

scanf("%d,%d",&a,&b);
c=a+b;
printf("%d+%d=%d",a,b,c);
}

如上程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入,一旦输入了错误的类型,你的程序不是死锁,就是得到一个错误的结果,呵呵,这可能所有人都遇到过的问题吧?

解决方法:scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。

正确的例程:

#include <stdio.h>
int main()
{
int a,b,c; /*计算a+b*/

while(scanf("%d,%d",&a,&b)!=2)fflush(stdin);
c=a+b;
printf("%d+%d=%d",a,b,c);
}

不得不说,fflush(stdin);是错的。fflush是用来清空标准输出的,fflush(stdin);在C语言中没有定义,是依赖编译器的,不可移植的。

an extract from the C standard will help explain:

int fflush(FILE *ostream);

ostream points to an output stream or an update stream in which the
most recent operation was not input, the fflush function causes any
unwritten data for that stream to be delivered to the host environment to
be written to the file; otherwise, the behavior is undefined.

On occasions you may need to clear unwanted data in an input stream, most commonly keyboard input. This may be after a call to a read function that failed to input all available data, or it may be to ensure that the user doesn't try the "type ahead" approach when using your application.

As far as standard C and C++ go, there is no guaranteed method to clear an input stream. You can write code that will do a reasonably good job, but it probably won't work in all instances your require it to. Why not? Because in standard C/C++ input streams are buffered. This means that when you hit a key on the keyboard, it isn't sent directly to your program. Instead it is buffered by the operating system until such time that it is given to your program. The most common event for triggering this input is the pressing of the [Enter] key.

If you are sure that unwanted data is in the input stream, you can use some of the following code snippets to remove them. However, if you call these when there is no data in the input stream, the program will wait until there is, which gives you undesirable results.

/*
* This C implementation will clear the input buffer.
* The chances are that the buffer will already be empty,
* so the program will wait until you press [Enter].
*/

#include <stdio.h>

int main(void)
{
int ch;
char buf[BUFSIZ];

puts("Flushing input");

while ((ch = getchar()) != '\n' && ch != EOF);

printf ("Enter some text: ");

if (fgets(buf, sizeof(buf), stdin))
{
printf ("You entered: %s", buf);
}

return 0;
}

/*
* Program output:
*
Flushing input
blah blah blah blah
Enter some text: hello there
You entered: hello there
*
*/

And a C++ version:

#include <iostream> 
#include <cstdio>

using std::cin;
using std::cout;
using std::endl;

int main(void)
{
int ch;
char buf[BUFSIZ];

cout <<"Flushing input" <<endl;

while ((ch = cin.get()) != '\n' && ch != EOF);

cout <<"Enter some text: ";
cout.flush();

if (cin.getline(buf, sizeof(buf)))
{
cout <<"You entered: " <<buf <<endl;
}

return 0;
}

/*
* Program output:
*
Flushing input
blah blah blah blah
Enter some text: hello there
You entered: hello there
*
*/

But what about fflush(stdin);
Some people suggest that you use fflush(stdin) to clear unwanted data from an input buffer. This is incorrect.

Why fflush(stdin) is wrong

On occasions you may need to clear unwanted data in an input stream, most commonly keyboard input. One frequently suggested way of doing this is by using fflush(stdin). This is incorrect, and should be avoided, here is why.

stdin is a standard FILE* variable that points to the input stream normally used for keyboard input. The fflush() function is deemed to flush buffers. Put the two together and you have a method for clearing the input stream easily, right? WRONG! This is a common misconception in C and C++ programming, an extract from the C standard will help explain:

int fflush(FILE *ostream);

ostream points to an output stream or an update stream in which the
most recent operation was not input, the fflush function causes any
unwritten data for that stream to be delivered to the host environment to
be written to the file; otherwise, the behavior is undefined.

So, if the file stream is for input use, as stdin is, the behaviour is undefined, therefore it is not acceptable to use fflush() for clearing keyboard input.
As usual, there are some exceptions, check your compiler's documentation to see if it has a (non-portable) method for flushing input.

fflush(stdin);到底是什么呢:
有些人认为fflush(stdin)是用来清除在输入缓冲中你不想要的数据。但这样的认为是错的!
那fflush(stdin)为什么错呢?
有时你免不了需要清除一下那些在输入流中你不想要的数据,那些数据大多数情况是由键盘输入的。通常很习惯性的方法就是用fflush(stdin)来进行清除。但这样是错误的,而且应该避免,下面就是理由。(外国人怎么写文章比中国人还会托……烦死了)
stdin是一个标准FILE*(文件型指针)指向通常是用键盘的输入的输入流。fflush()的功能一般是被视为清除缓冲区的。一起输入两个,然后你有办法来清除这些输入流,不是么?错!这是一个对C/C++编程很菜鸟式的概念性错误认识!下面引用自C标准来帮助解释:
int fflush(FILE *ostream);
ostream流指向一个输出的流或一个在不久以前曾经操作过更(第四音)新的流,fflush的功能会导致任何并没有被写入缓冲流的数据被分配给主机环境,然后这些数据被写入文件;换句话,这种方式是没被定义的!(我狂晕啊,老外写的句子这么麻烦)
So,如果文件流是给输入用的,就像stdin一样,那么这种方式是没被定义的,因此用fflush()来清除键盘输入的这种方法是不能接受的。
通常,应该还有其他办法,查看一下你的协议文件是否有一种(非移植性--作者好像是想说非移植性指针)方法来清除输入。

scanf()函数释疑(word找的,没源地址了)的更多相关文章

  1. c语言学习之基础知识点介绍(三):scanf函数

    本节继续介绍c语言的基础知识点. scanf函数:用来接收用户输入的数据. 语法:scanf("格式化控制符",地址列表); 取地址要用到取地址符:&(shift+7) 例 ...

  2. 关于scanf()函数的一点理解

    习惯了c++的cin.cout之后,也不怎么关注空格,反正cin.cout会自动处理.有一次实验,创建Huffman树,要求输入空格字符,当时就懵逼了.cin咋输入空格呢? 没办法,只能重新用scan ...

  3. STM32 printf()函数和scanf()函数重定向到串口

    STM32 printf()函数和scanf()函数重定向到串口 printf()函数和scanf()函数重定向 在学习STM32的时候,常常需要用串口来测试代码的正确与否,这时候就要要用到print ...

  4. php对象和数组的相互转换(还是可以去找没有没php的高阶课程看看看)(要不别人分析一下重点要点,要不自己来,不然 效果真的不好)

    php对象和数组的相互转换(还是可以去找没有没php的高阶课程看看看)(要不别人分析一下重点要点,要不自己来,不然 效果真的不好) 一.总结 都是自己实现的函数 算法: 1.先判断类型,gettype ...

  5. scanf()函数原理

    一.三点说明 1.用户输入的字符,会以ASCII码形式存储在键盘缓冲区:2.每调用一次scanf函数,就从键盘缓冲区读走一个字符,相当于清除缓冲区:3.若用户一次输入n个字符,则前n次调用scanf函 ...

  6. scanf()函数的原理

    最近使用scanf发现了自己对scanf函数还是不太了解,主要出现在无意中出现的一个错误: scanf正确的写法是,scanf中以什么格式输入变量,则变量的类型就应该是什么格式,如下面scanf输入到 ...

  7. 4-printf & scanf函数

    一.printf函数 这是(printf和scanf)在stdio.h中声明的一个函数,因此使用前必须加入#include <stdio.h> 1.用法 1> printf(字符串) ...

  8. C语言scanf函数详细解释

    原文链接 函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准 ...

  9. 黑马程序员-scanf函数

    变量的内存:字节和地址:1.变量的存储单位是字节,每个字节都有存储地址.2.不同的数据大小占用的内存带下不同拥有的字节数也是不同的.变量的存储:1.存储是按照,先存储的放在地址教高的位置,优先存储的地 ...

随机推荐

  1. [学习笔记]尝试go-micro开发微服务<第一波>

    平时项目都是基于c++,lua,node, 现在打算开始自学开发微服务;   也顺带磨砺下go和docker 前期准备 1. 有golang编程基础 本系列文章是基于有golang编程基础,有过实际开 ...

  2. #3使用html+css+js制作网页 番外篇 使用python flask 框架 (II)

    #3使用html+css+js制作网页 番外篇 使用python flask 框架 II第二部 0. 本系列教程 1. 登录功能准备 a.python中操控mysql b. 安装数据库 c.安装mys ...

  3. Java向指定Excel写入读取数据

    今天在开发中遇到用户列表导入导出的功能实现,这里了解到使用POI函数库可以完成此任务!特此记录一下 POI Apache POI是Apache软件基金会开放的源码函数库,POI提供API给Java程序 ...

  4. 【UML】基本介绍与类图(依赖、泛化、实现、关联、聚合、组合关系)

    文章目录 UML基本介绍 UML图 UML类图 类图-依赖关系(Dependence) 类图-泛化关系(generalization) 类图-实现关系(Implementation) 类图-关联关系( ...

  5. CTF实验吧-WEB题目解题笔记(1)简单的登陆题

    1.简单的登陆题 解题链接: http://ctf5.shiyanbar.com/web/jiandan/index.php  Burp抓包解密 乱码,更换思路.尝试id intruder 似乎也没什 ...

  6. 1.2V转3V芯片,电路图很少就三个元件

    1.2V的镍氢电池由于稳定高,应用产品也是很广,但是由于电压低,需要1.2V转3V芯片,来将1.2V的电压升压转3V,稳定输出供电. 一般性的1.2V转3V芯片,都是用PW5100比较多,固定输出电压 ...

  7. 知识图谱KnowledgeGraph核心技术培训班 2月03日— 2月06日

  8. Kubernetes之GlusterFS集群文件系统高可用安装,提供动态卷存储

    GlusterFS高可用安装 一. 准备工作 安装好的k8s集群,提供其中三个节点给GFS,这三个节点都至少有一个可用的裸块设备 在k8s所有节点安装所需要的组件 # ubuntu16.04 add- ...

  9. 【Android初级】使用Gallery实现照片拖动的特效(附源码)

    今天要分享一个非常简单的功能: 使用Android原生控件Gallery实现照片拖动的特效 实现思路如下: 在布局文件中定义一个Gallery控件 由于要显示多张图,为了方便,我直接引用了Androi ...

  10. kafka项目经验之如何进行Kafka压力测试、如何计算Kafka分区数、如何确定Kaftka集群机器数量

    @ 目录 Kafka压测 Kafka Producer(生产)压力测试 Kafka Consumer(消费)压力测试 计算Kafka分区数 Kafka机器数量计算 Kafka压测 用Kafka官方自带 ...