c++输入输出

IO类

介绍

c++中主要的IO类有三个:iostream,fstream,sstream.iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型.详细的继承对应关系如下图:
img

条件状态(condition state)

strm::iostate strm指某一种IO类型,iostate是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit 流已崩溃
strm::failbit 指出一个IO操作失败了
strm::eofbit 指出流到达了文件结束
strm::goodbit 用来指出流未处于错误状态.此值保证为零
s.eof() 若流s的eofbit置位,则返回true
s.fail() 若流s的failbit或badbit置位,则返回true
s.bad() 若流s的badbit置位,则返回true
s.good() 若流s处于有效状态,则返回true
s.clear() 将流s中所有条件状态位复位,将流的状态设置为有效.返回void
s.clear(flags) 根据给定的flags标志位,将流s中对应条件状态位复位.flags的类型为strm::iostate.返回void
s.setstate(flags) 根据给定的flags标志位,将流s中对应条件状态位置位.flags的类型为strm::iostate.返回void
s.rdstate() 返回流s的当前条件状态,返回值类型为strm::iostate

例:

1
2
3
4
auto old_state = cin.rdstate();  // 记住cin的当前状态
cin.clear(); // 使cin有效
process_input(cin); // 使用cin
cin.setstate(old_state); // 将cin置为原有状态
1
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);    // 将failbit和badbit复位,且保持eofbit不变

缓冲

导致缓冲刷新的原因可能有以下几种:

  • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行.
  • 缓冲区满时.
  • 使用操纵符如endl(不附加任何额外字符的flush,附加一个空字符的ends)来显式刷新缓冲区

    使用fflush(cout)也可刷新缓冲区

  • 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区.默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的.例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /*
    std::ios_base& unitbuf( std::ios_base& str );
    std::ios_base& nounitbuf( std::ios_base& str );
    std::cout << std::unitbuf;
    */
    // modify unifbuf flag
    #include <ios> // std::unitbuf
    #include <fstream> // std::ofstream

    int main () {
    std::ofstream outfile ("test.txt");
    outfile << std::unitbuf << "Test " << "file" << '\n'; // flushed three times
    outfile.close();
    return 0;
    }
  • 一个输出流可能被关联到另一个流.在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新.例如,默认清空下,cin和cerr都关联到cout.因此,读cin或写cerr都会导致cout的缓冲区被刷新.例x.tie(&o)将流x关联到输出流o.

注:程序崩溃时,输出缓冲区不会被刷新

同步关系

c++中cin、cout,cerr和c的stdin、stdout、stderr都是同步的,即iostream 对象和cstdio流是同步的,同步关系如下
|C stream |iostream|
|————-|————|
|stdin | cin,wcin |
|stdout |cout,wcout|
|stderr |cerr,wcerr,clog,wclog|
可以使用

1
bool std::ios_base::sync_with_stdio (bool sync = true);

参数为false来取消同步来加快输出速度.(参考:探寻C++最快的读取文件的方案)

设置缓冲区大小

我们可以通过函数setbuf 和 setvbuf 自己设置输入输出流的缓冲区,需要注意的是不管程序中申请的的缓冲区实际大小为多少,setbuf都将缓冲区设置的大小为BUFSIZ(这个宏在windows下是512,ubuntu下是1024), setvbuf则可以设置缓冲区大小以及缓冲区的模式(行缓冲、全缓冲、无缓冲),需要注意的是这两个函数设置的是c的输入输出缓冲区,因为c++和c的缓冲区是同步的,所有该函数会对c++有影响

1
2
3
4
void setbuf ( FILE * stream, char * buffer );
// mode:_IOFBF(Full buffering),_IOLBF(Line buffering),_IONBF(No buffering)
int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );
//例: setvbuf ( pFile , NULL , _IOFBF , 1024 );

提升IO速度

可参考mixing cout and printf for faster output [翻译:混合cout和printf以更快的输出]和探寻C++最快的读取文件的方案
总之:

  • 取消同步可以使c++中cin或cout快很多
  • 尽量在io上少些格式化输出,如果必要可以先格式化输出到自己的缓冲区,然后再写入流中.如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /*puts("Hello World\n") is much faster than printf("%s", "Hellow World\n"). (Primarily due to the formatting overhead). Once you have isolated the formatted from plain text, you can do tricks like:
    */
    const char hello[] = "Hello World\n";
    cout.write(hello, sizeof(hello) - sizeof('\0'));

    const unsigned int MAX_BUFFER_SIZE = 256;
    char buffer[MAX_BUFFER_SIZE];
    sprintf(buffer, "%d times is a charm.\n", 5);
    unsigned int text_length = strlen(buffer) - sizeof('\0');
    fwrite(buffer, 1, text_length, stdout);

参考

C++ Primer 5th
c/c++ 输入输出缓冲区
探寻C++最快的读取文件的方案
C++ 输出缓冲区的管理
C++ 输入输出流
维基百科-iostream