赞
踩
函数名: scanf
功 能: 执行格式化输入
用 法: int scanf(char *format[,argument,…]);
format 是一个字符串,通过这个字符串限制了scanf的数据匹配格式,
argument 指的是后面的需要进行匹配赋值的参数,
… 指后面的可变参数
例如:
int a, b;
// 这里有两个变量 我们可以通过scanf对他进行赋值
scanf("%d%d", &a, &b); // 正确用法 如果要通过函数修改变量,应当将他的地址传给这个函数
// 否则只会修改他的值得拷贝
// scanf("%d%d", a, b); // 错误用法
printf("%d %d\n", a, b);
在上面的程序中 我们通过scanf对a,b两个变量赋值,
其中的format参数的值是 “%d%d” 其中使用到的%d是格式转换符
格式转换说明符 | 功能描述 |
---|---|
%d | 输入一个十进制整数 |
%f | 输入一个单精度实数 |
%lf | 输入一个双精度实数 |
%c | 输入一个字符 |
%s | 输入一个字符串 |
%o | 输入一个八进制整数 |
%x | 输入一个十六进制整数 |
%* | 表示本输入项只是读入,但不赋给相应变量 |
在上个例子中,我们使用格式转换说明符匹配了两个整形变量,但是值得注意的是,这个格式说明符· 中间没有分隔,程序是怎么知道要分开两个数的呢?
实际上,程序通常有三种条件进行下一个格式说明符的匹配
1、空白字符(空白字符是指空格键,tab键,回车键)
2、遇宽度结束
3、遇非法输入(结束scanf)
4、缓冲区被读取完毕(结束scanf)
在实际运行过程中,我输入的是"12 34"通过一个空格(空白字符)隔开两个值,接下来我们来看第二种情况
在格式转换符的帮助下我们已经可以输入一个小数了,但是现实条件下我们通常会限制数据的长度
此时可以用到%nd (n为一个十进制数)
int a, b;
// 这里有两个变量 我们可以通过scanf对他进行赋值
scanf("%2d%2d", &a, &b); // 通过限制获取两位
printf("%d %d\n", a, b);
在现在的这个例子中,我们只需要输入"1234",不用分隔符也能匹配上两个数。
我们来分析一下,程序先使用"%2d"匹配了前两个字符"12",然后将其赋值给变量a,然后继续使用下一个"%2d"匹配接下来的"34",赋值给变量b。
注意!!!! 在这个过程中程序在匹配了一个字符之后,转而进行变量的赋值,此时"%2d%2d"的匹配被打断了,等待赋值完成后,程序继续进行匹配,
这代表程序实际上是有一块缓冲的地方给字符串存储的。
实际上由于cpu处理速度远高于我们IO输入的速度,所以在计算机设计过程中,有专门的缓冲区来缓冲输入,输出同样也有。
缓冲区用来缓存“输入数据”的ASCII码,而scanf()每次从缓冲区中读取一个字符(ASCII码),在运行过程中,如果缓冲区为空,scanf会要求用户输入数据到缓冲区,然后scanf进行读取直到缓冲区为空。如果缓冲区为非空状态,则下次执行scanf()不会要求输入,如果键盘缓冲区为空,执行scanf()则会等待用户的输入。
下面我们来看scanf第三种结束条件
我们尝试给一个int和一个char赋值 我们输入"A A",使用一个空格(空白字符)隔开两个值
int a;
char c;
// 这里有两个变量 一个int 和一个char 我们可以通过scanf对他进行赋值
scanf("%d%c", &a, &c); // 通过限制获取两位
printf("%d %c\n", a, c);
我们期待的结果是
a的值为65 即’A’的ASCII码的值
c的值为’A’ 即’A’被成功赋值
但是实际运行结果是 "0 "(两个括号 一个是printf内的空格字符,一个是c的字符值)
这意味着在实际过程中 scanf在使用"%d"给’A’匹配时发生了错误,他并没有给a赋值,而且直接结束了匹配。
那么剩下还有一个的’A’在哪呢 我们来看看缓冲区还剩什么
int a;
char c;
// 这里有两个变量 一个int 和一个char 我们可以通过scanf对他进行赋值
scanf("%d%c", &a, &c);
char arr[10]; // 用数组获取缓冲区
scanf("%c%c", &arr[0], &arr[1]);
printf("%c %c\n", arr[0], arr[1]);
一共进行了两次scanf,第一次scanf由于匹配失败,在缓冲区留下了一个’A’
第二次scanf的执行并没有等待我输入,他直接从缓冲区读取到了上一个scanf留下的那个’A’
程序出现了开发者不希望出现得到情况,第二个scanf直接破坏了整个程序的运行逻辑
再比如一个很多人可能遇见的情况,没遇见过的同学可以拷贝去尝试运行一下
int a;
char c;
// 这里有两个变量 一个int 和一个char 我们可以通过scanf对他进行赋值
printf("input one integer:");
scanf("%d", &a);
printf("input one char:");
scanf("%c", &c);
printf("input end\n");
printf("%d %c\n", a, c);
运行效果如下
我只输入了一次 12 但是他第二次scanf同样没有要求我输入,我们没有输入字符,但是他第二次居然获取了缓冲区的数据。灵异事件出现了!
回想一下刚才的输入过程,我们输入了一次12 然后按了回车,实际上回车这个键输入了两个字符\r\n(回车换行),scanf使用%d匹配了12,但是换行符没有人匹配,被留在了缓冲区,第二次scanf看见缓冲区有数据,读取了一个\n
为了验证这个结论我们修改下代码
int a;
char c;
// 这里有两个变量 一个int 和一个char 我们可以通过scanf对他进行赋值
printf("input one integer:");
scanf("%d", &a);
printf("input one char:");
scanf("%c", &c);
printf("input end\n");
printf("%d %d\n", a, c);
return 0;
那么我们怎么解决这个问题呢
这个问题有如下几种解决办法
1.getchar()
int getchar(void)
getchar能从缓冲区获取一个字节并返回
来看使用getchar的优化方法
int a;
char c;
// 这里有两个变量 一个int 和一个char 我们可以通过scanf对他进行赋值
printf("input one integer:");
scanf("%d", &a);
printf("input one char:");
getchar();
scanf("%c", &c);
printf("input end\n");
printf("%d %d\n", a, c);
使用getchar吃掉了’\n’,程序可以正常等待输入了
在第二次输入之前,再使用一个
scanf("\n");
scanf("%c", &c);
或是
scanf("%c")
scanf("%c", &c);
或是
fgetc(stdin);
scanf(“%c”, &c);
获取’\n’,使缓冲区清空,等待输入;
第一次输入时使用
int a;
scanf("%d ", &a); //这里有个括号可以使用他吃掉回车字符
scanf("%*[\n]");
这个字符串时scanf自带的正则表达式,它很有意思,让我们一一拆解一下
首先 "%[\n]" 表示 匹配 [ ] 中的字符
"%*[\n]"表示 匹配 [ ] 中的字符 并丢弃
在前面我们使用了如下表达式
scanf("%*[\n]");
我们来讨论一下他的用法
首先 "%[ABC]" 表示 匹配 [ ] 中的字符 即ABC
"%[A-Z]" 表示 匹配 [ ] 中的字符 即所有大写字母
"%[^A-Z]"表示 匹配不是[]中的字符 即除开大写字符的所有字符
"%*[ABC]"表示 匹配 [ ] 中的字符 并丢弃
下面我们来应用这些知识
练习题: 在一串字符串(可能包含任意字符)中有两个整形数字(不确定位置),如何取出他们并赋值给a,b。
在题目中我们需要丢弃所有所有非数字字符 表达式为
"%*[^0-9]" //读取所有 非(^) 数字字符(0-9) 并丢弃(*)
同时我们需要处理结尾出现的换行符 清除缓冲区,可以使用上文出现的方法
实现代码如下
void safeScanf()
{
printf("safeScanf:\n");
int a = 0, b = 0;
scanf("%*[^0-9]"); // 读入所有非数字并丢弃
scanf("%d", &a);
scanf("%*[^0-9]"); // 读入所有非数字并丢弃
scanf("%d", &b);
scanf("%*[^\n]%*c"); // 他是下面两句的集合
// scanf("%*[^\n]"); // 读入所有非换行符并丢弃
// scanf("%*c"); // 读入换行符并丢弃
printf("a=%d b=%d\n", a, b);
}
恭喜你!! 通过在scanf函数上面添加补丁,你修复了自己程序的输入bug
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。