当前位置:   article > 正文

C语言安全输入:scanf函数_scanf("2d",n)

scanf("2d",n)

输入函数scanf()

函数名: 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在上面的程序中 我们通过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);
  • 1
  • 2
  • 3
  • 4

在现在的这个例子中,我们只需要输入"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);
  • 1
  • 2
  • 3
  • 4
  • 5

我们期待的结果是
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]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述
一共进行了两次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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行效果如下
被吃掉的\n
我只输入了一次 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

缓冲区导致的不安全的scanf

那么我们怎么解决这个问题呢

这个问题有如下几种解决办法

解决方案1

1.getchar()

int getchar(void)
  • 1

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用getchar吃掉了’\n’,程序可以正常等待输入了
正常输入

解决方案2

在第二次输入之前,再使用一个

 scanf("\n");
 scanf("%c", &c);
  • 1
  • 2

或是

 scanf("%c") 
 scanf("%c", &c);
  • 1
  • 2

或是

fgetc(stdin);
scanf(“%c”, &c);

获取’\n’,使缓冲区清空,等待输入;

解决方案3

第一次输入时使用

int a;
scanf("%d ", &a); //这里有个括号可以使用他吃掉回车字符
  • 1
  • 2

解决方案4

scanf("%*[\n]");
  • 1

这个字符串时scanf自带的正则表达式,它很有意思,让我们一一拆解一下

首先	"%[\n]" 表示  匹配 [ ] 中的字符 
		"%*[\n]"表示  匹配 [ ] 中的字符 并丢弃
  • 1
  • 2

扩展

在前面我们使用了如下表达式

scanf("%*[\n]");
  • 1

我们来讨论一下他的用法

首先	"%[ABC]" 表示  匹配 [ ] 中的字符  即ABC
		"%[A-Z]" 表示  匹配 [ ] 中的字符  即所有大写字母
		"%[^A-Z]"表示  匹配不是[]中的字符 即除开大写字符的所有字符
		"%*[ABC]"表示  匹配 [ ] 中的字符  并丢弃
  • 1
  • 2
  • 3
  • 4

下面我们来应用这些知识

练习题: 在一串字符串(可能包含任意字符)中有两个整形数字(不确定位置),如何取出他们并赋值给a,b。
在题目中我们需要丢弃所有所有非数字字符 表达式为

	"%*[^0-9]" //读取所有 非(^) 数字字符(0-9) 并丢弃(*)
  • 1

同时我们需要处理结尾出现的换行符 清除缓冲区,可以使用上文出现的方法
实现代码如下

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

恭喜你!! 通过在scanf函数上面添加补丁,你修复了自己程序的输入bug

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码吟游诗人2/article/detail/62651
推荐阅读
相关标签
  

闽ICP备14008679号