scanf与缓存

来源:互联网 发布:淘宝助理有mac版 编辑:程序博客网 时间:2024/06/06 01:15

转载自http://blog.sina.com.cn/s/blog_7b62c61c0100vhlu.html


       作为c语言的标准输入函数,scanf在实际的编程中无疑是十分重要的,可是如果使用不当,它也会产生一些莫明其秒的错误的,而这些错误即使你使用断点调试也很难找到错误的根源所在

       就像我前不久一篇文章中说到的 “access volition” 错误,scanf的使用不当就是造成这种错误的原因之一,而且由此造成的错误,你是很难发现的
       下面我说一下另外一种大家在C语言编程中可能遇到的问题,在说之前先看以下的代码:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    long i = 0;
    while(scanf("%ld",&i)!=1)
    {
        printf("error,input again:");
    }
    printf("%ld\n",i);
    return (EXIT_SUCCESS);
}
      初一看,这段代码是没有任何问题的,就是从标准输入接收用户输入的一个整数,如果正确的接收,则跳出while循环,并打印出接收到的数字,如果接收失败,则打印一个错误的信息,并要求用户再次输入一个数字,直到接收到一个整数为止。
     可是如果用户在输入的时候,输入的不是一个整数,而是包含一些非数字的字符,那么问题就出现了,这时程序会不停的输出 “error,input again:”,用户不能再次输入一个整数,出现这个问题的原因就是,scanf如果没有接收到所需要的数据类型的数据时,并不会清空缓存,以上代码在运行后,如果输入一个字符串时,由于scanf需要一个整型数据,可是当前输入缓冲中的数据并不是一个整型的数据,那么scanf会直接退出,并返回0,表示读到0个数,同时不会改为输入缓冲,这样在打印出"error,input again:"后,会进入到下一轮的循环之中,这时由于输入缓冲中还有数据,scanf不会阻塞,而是直接返回,可是发现缓冲中的数据不是所要的数据,这样返回0.....使得循环一直运行下去,而不可能因为用户的再次输入退出循环。
     解决这个问题的最直接的办法就是再读过之后清空输入缓存,因为缓存中的数据并不是想要的数据类型,所以可以直接清空,然后等待用户下一次的输入,那么如何做才能够清空缓存呢。
      方法一、使用fflush函数
         使用这个该方法进行标准输入缓冲的清除方法,具有一定的局限性,因为标准的C定义中,是不支持这种方法进行标准输入的缓存的清空的,也就是说使用这种方法编写的程序没有跨平台运行的特性,这种方法只能运用在VC编程环境下,在linux等其它平台上是不能正常工作的。
    方法二、setbuf函数
        关于这个函数我用的时候很时纠结,以下是我在google上搜到的有关这个函数的部分注解:
Sets the buffer to be used for I/O operations with the specified stream, which becomes a fully buffered stream, or alternatively, if the argument for buffer is NULL, it disables buffering for the stream, which becomes an unbuffered stream.
This function should be called once the file associated with the stream has already been opened but before any input or output operation has taken place.
意思是说,这个函数是用来设置指定I/O的缓冲区的大小,如果其第二个参数设置为NULL,那么表示对这个流进行相应的I/O操作时,将不再有缓冲,这个函数应该在一个流被打开后,并且在进行任何输入输出之前被调用,而且应该只被调用一次。然后我在测试时,将以上代码修改如下
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    long i = 0;
    setbuf(stdin,NULL);
    while(scanf("%ld",&i)!=1)
    {
        printf("error,input again:");     
    }
    printf("%ld\n",i);
    return (EXIT_SUCCESS);
}
完全是按照文档的要求做的,可是并没有达到预期的效果,还是不停的输出"error,input again:",也就是说,这个函数根本就没有起作用,而将代码改为如下形式
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    long i = 0;
    while(scanf("%ld",&i)!=1)
    {
        printf("error,input again:");
        setbuf(stdin,NULL);     
    }
    printf("%ld\n",i);
    return (EXIT_SUCCESS);
}
却能起到一定的作用,如果你输入的字符串长度为5,那么会连续输出5条"error,input again:"信息,然后再次要求你输入一个整数,这让我很是莫明其妙,虽然效果不太好,可是这种修改之后,至少不会出现死循环了。
    方法三、将缓冲区中的数据正常的读出来,这样由于数据被正常读出,缓冲区自然也就会自动清空了,可对代码作以下的修改:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    long i = 0;
    int k = 0;
    char ch;
    while((k = scanf("%ld",&i))!=1)
    {
        printf("error,input again:");
        while((ch=getchar())!='\n');
       
    }
    printf("%ld\n",i);
    return (EXIT_SUCCESS);
}
这样当用户输入的数据类型非法时,内层的while循环就会不停的读输入缓冲,直到读完或收到一个回车换行为止,这样就达到清空输入缓冲的目的了,注意这种方法具有跨平台的特性。
原创粉丝点击