scanf()函数的陷阱 getchar()函数的陷阱

scanf()函数

1. 缓冲区的概念
缓冲区就是键盘按下回车键后 所输入内容的去处,也是sancf(),getchar()等函数读取数据的来源,这里有两个需要明白:
a. 函数从缓冲区读走字符 和 键盘把键值存到缓冲区 这是两个互相独立的过程,即 你存你的,我取我的
b. 当缓冲区为空的时候,调用sancf(),getchar()等函数会使程序处于等待输入的状态,而如果缓冲区有内容,则直接读取缓冲区,不用等待。

2. scanf()的工作流程
a. 从左到右处理格式串字符
b. 遇到转换说明符(如 %d),scanf( )尝试从缓冲区读取数据(缓冲区为空时,则等待键盘输入)中定位相应类型的项,并跳过必要的空格
c. 读取数据项,并在遇到不可能属于此数据类型的字符时停止
d. 若数据读取成功,则写入对应的变量(通过指针访问变量空间)
e. 继续处理格式字符串的剩余部分
f. 格式字符串处理完毕,结束调用
g. scanf()函数是有返回值的,表示成功读入数据项个数,比如成功读取2次,那scanf()的返回值就是2
注意:scanf()读取数据时会跳过必要的空格,准确的说是在读取非字符类型的数据才跳过,也就是在多个数据之间本来就可以随便加空白字符,对于下面最常见的代码:

#include <stdio.h>

int main()
{
    int i,j;
    scanf("%d%d",i,j);
    printf("i = %d, j= %d\n",i j);
    return 0;
}

在输入第一个数之后,可以随便敲任意的空格,回车,换行后再输入第二个数,程序照常运行,即使格式字符说明 %d%d 是紧挨着的

3. scanf()处理空白格式字符串

有时候会遇到 scanf("%d\n%d",i,j) scanf("%d %d",i,j) 的情况这里格式串字符带空白符的写法,是用特殊的用法,并没有那么简单

可能令人吃惊,\n在scanf格式串中不表示等待换行符,而是读取并放弃连续的空白字符。(事实上,scanf格式串中的任何空白字符都表示读取并放弃空白字符。而且,诸如%d这样的格式也会扔掉前边的空白,因此你通常根本不需要在scanf格式串中加入显式的空白。)
—— 《你必须知道的495个C语言问题》- 第12章标准输入输出库

因此 scanf("%d\n%d",i,j) scanf("%d %d",i,j) 中的空白符,并不表示匹配字符,而是重新读取一个非空白字符, scanf("%d\n%d",i,j) scanf("%d %d",i,j) 空白符介于两个数据转换符中间,体现不出来这一奇葩特性(空白符后面本来就需要读取一个非空白字符 %d 数据),但如果空白符位于格式串的末尾,那就有问题了----即:在按照指定方式读取后需要多读取一个非空白字符,具体见如下代码:

#include <stdio.h>

int main()
{
    int i;
    scanf("%d\n", &i);
    printf("i = %d\n", i);

    return 0;
}

运行结果:

在输入第一个数 1 之后,格式串字符遇到了 \n 。此时scanf()仍然处于等待状态,需要继续读取下一个非空白字符才会结束;

4. scanf()输入不匹配导致无限循环

问题代码如下

#include <stdio.h>

int main()
{
    int i;
    scanf("%d", &i);
    while(i != 123)
    {
        printf("输入错误\n");
        scanf("%d", &i);
    }

    return 0;
}

输入是%d类型的数据,程序可以正常运行,但是当你输入字母时,会无限打印 输入错误,是因为 scnaf() 函数遇到不可能属于次数据项类型的字符时停止,由于我们输入的是字母,显然不能匹配 %d ,于是scanf()函数读入失败,什么都没干直接结束,变量 i 的值未变,更为致命的是 非法的字符一直停留在缓冲区中,现在缓冲区已经非空,并且也不匹配 %d ,下次scanf() 从缓冲区直接读取,并且仍然非法,这样就形成死循环

解决办法
利用 scnaf()函数有返回值,来判断数据读取是否成功,再通过getchar()将缓冲区残留字符清空,就可以实现对非法字符的处理,见如下代码:

#include <stdio.h>

//判断输入是否正确
int get_int()
{
    int i;
    while(1 != scanf("%d", &i))
    {
        printf("input illegal\n");
        while('\n' != getchar());   //清空缓冲区
    }
    else
    return i;
}

int main()
{
    int i;
    while(i = get_int())        //只有匹配到数字,才可以进入密码比对
    {
        if(123 == i)
        {
            printf("good job\n");
            break;
        }
        else
        {
            printf("password error\n");
            printf("input again\n");
        }
    }

    return 0;
}

6. scnaf()处理字符输入
scanf("%c", ch) 和 getchar() 一样,是允许接收任何输入的,也就是会把 你输入的空白符回车 换行也存入缓冲区,这样就容易存在误操作,因为缓冲区中存在空格 回车 换行 都可以被scanf("%c", ch) 和 getchar() 读取,误代码如下:

#include <stdio.h>

int main()
{
    char buf[5];
    char ch;
    int i;
    for(i = 0; i < 5; i++)
        scanf("%c", &buf[i]);

    for(i = 0; i < 5; i++)
        printf("%c", buf[i]);

    printf("\n");

    return 0;
}

运行结果
在这里插入图片描述
scanf("%c", ch) 允许接受任何输入,出现这种情况的原因就是 scanf()会读取键入缓冲区中的空格 换行 回车,实际程序想存入的是 a b c d e,如何让scnaf()函数吃掉回车,有以下几种解决办法:
a. 用getchar() 清除
b. 用 “%1s"限制;
c. 在 %c前面加一个空白符(空格 换行符 tab)变成 " %c" 或者 “\n%c” 或者 " %c"来吃掉回车(一个空格变成” %c" 最为美观 );
d. 用fflush(stdin)清楚全部剩余内容;(不建议使用)
第1中方法,利用getchar()将缓冲区中的回车读掉
第2中方法,显示只取一个字符,其他的丢弃
第3中方法,在格式串中加上空白符可以吃掉的回车,因为scanf()格式串中的空白符表示 按照指定方式读取后需要多读入一个非空字符,代码如下

#include <stdio.h>

int main()
{
    char buf[5];
    char ch;
    int i;
    for(i = 0; i < 5; i++)
        scanf(" %c", &buf[i]);		//用空格来吃掉空白符

    for(i = 0; i < 5; i++)
        printf("%c", buf[i]);

    printf("\n");

    return 0;
}

第4中方法不建议使用

7.小陷阱
见下面问题代码

#include <stdio.h>

int main()
{
    int i, j;
    char ch;
    scanf("%d%d", &i, &j);
    printf("%d %d",i, j);

    return 0;
}

类似于scanf("%d%d", &i, &j),scanf在读取键盘输入的时候,会读取第二个 %d 后面的空格,并存入缓冲区,这样缓冲区就会有残留的换行,我们对此验证

#include <stdio.h>

int main()
{
    int i, j;
    char ch;
    scanf("%d%d", &i, &j);
    printf("%d %d",i, j);

    ch = getchar();
    putchar(ch);

    return 0;
}

打印结果:
在这里插入图片描述
第二个 %d 后面的换行存缓冲区,此时需要情况缓冲区,以防止对后面输入造成不需要的麻烦
优化代码如下

#include <stdio.h>

int main()
{
    int i, j;
    char ch;
    scanf("%d%d", &i, &j);
    printf("%d %d",i, j);

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

    return 0;
}

运行结果:
在这里插入图片描述
已经清空缓冲区后面中的空白字符

以上就是我对scanf的认识了~~~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值