C/C++语言误区之:fflush(stdin)

2024-03-20 22:18
文章标签 语言 c++ stdin 误区 fflush

本文主要是介绍C/C++语言误区之:fflush(stdin),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.为什么fflush(stdin) 是错的
首先请看以下程序:
#include <stdio.h>
int main( void )
{
    int i=1;
    while(i)
    {
        printf("Please input an integer: ");
        scanf("%d", &i);
        printf("%d/n", i);
    }
    return 0;
}
这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,
程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。
但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数
是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为scanf("%d", &i);
只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。
因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,
这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。
也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,
把输入缓冲清空掉不就行了?”然而这是错的!C和C++的标准里从来没有定义过 fflush(stdin)。
也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,
某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持
这个功能(linux 下的 gcc 就不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档
里也清楚地写着fflush on input stream is an extension to the C standard(fflush 操作
输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么
大问题。以下是 C99 对 fflush 函数的定义:
int fflush(FILE *stream);
如果 stream 指向输出流或者更新流(update stream),并且这个更新流
最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至
宿主环境(host environment)写入文件。否则,它的行为是未定义的。
原文如下:
int fflush(FILE *stream);
If stream 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.
其中,宿主环境可以理解为操作系统或内核等。
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。
故而使用 fflush(stdin)  是不正确的,至少是移植性不好的。
2.清空输入缓冲区的方法
虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数
后面加上几句简单的代码就可以了。
/* C 版本 */
#include <stdio.h>
int main( void )
{
    int i, c;
    for ( ; ; )
    {
        printf("Please input an integer: ");
        scanf("%d", &i);
       
        if ( feof(stdin) || ferror(stdin) )
        {
            /* 如果用户输入文件结束标志(或文件已被读完), */
            /* 或者发生读写错误,则退出循环              */
            /* do something */
            break;
        }
        /* 没有发生错误,清空输入流。                */
        /* 通过 while 循环把输入流中的余留数据“吃”掉 */
        while ( (c = getchar()) != '/n' && c != EOF ) ;
        /* 使用 scanf("%*[^/n]"); 也可以清空输入流, */
        /* 不过会残留 /n 字符。                          */
        printf("%d/n", i);
    }
    return 0;
}
/* C++ 版本 */
#include <iostream>
#include <limits> // 为了使用numeric_limits
using std::cout;
using std::endl;
using std::cin;
using std::numeric_limits;
using std::streamsize;
int main()
{
    int value;
    for ( ; ; )
    {
        cout << "Enter an integer: ";
        cin >> value;
        if ( cin.eof() || cin.bad() )
        {
            // 如果用户输入文件结束标志(或文件已被读完),
            // 或者发生读写错误,则退出循环
            // do something
            break;
        }
        // 读到非法字符后,输入流将处于出错状态,
        // 为了继续获取输入,首先要调用 clear 函数
        // 来清除输入流的错误标记,然后才能调用
        // ignore 函数来清除输入流中的数据。
        cin.clear();
        // numeric_limits<streamsize>::max() 返回输入缓冲的大小。
        // ignore 函数在此将把输入流中的数据清空。
        // 这两个函数的具体用法请读者自行查询。
        cin.ignore( numeric_limits<streamsize>::max(), '/n' );
        cout << value << '/n';
    }
    return 0;
}

 

这篇关于C/C++语言误区之:fflush(stdin)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/830897

相关文章

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

深入理解C语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初