美文网首页
第8章:IO库

第8章:IO库

作者: MrDecoder | 来源:发表于2018-10-10 14:36 被阅读9次
    • #1.IO类
      • IO对象无拷贝或赋值
      • 条件状态
      • 管理输出缓冲
    • #2.文件输入输出
      • 使用文件流对象
      • 文件模式
    • #3.string流
      • 使用istringstream
      • 使用ostringstream

    C++语言不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。

    #1. IO类

    iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。

    1.1 IO对象无拷贝或赋值

    ofstream out1,out2;
    out1 = out2; //错误:不能对流对象赋值
    ofstream print(ofstream); //错误:不能初始化ofstream参数
    out2 = print(out2); //错误:不能拷贝流对象
    

    由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型。进行IO的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

    1.2 条件状态

    IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。表中列出了IO类定义了一些函数和标志,可以帮助我们访问和操作流的条件状态

    函数 状态
    strm::iostate strm是一种IO类型。iostate 是一种机器相关类型,提供了表达条件状态的完整功能
    strm::badbit strm::badbit 用来指出流已崩溃
    strm::failbit strm::failbit 用来指出一个IO操作失败了
    strm::eofbit strm::eofbit 用来指出流到达了文件结束
    strm::goodbit strm::goodbit 用来指出流未处于错误状态。此值保证为零。
    查询流的状态

    将流作为条件使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们需要知道流为什么失败。

    IO库提供了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。IO库定义了4个iostate类型的constexpr值表示特定的位模式。badbit表示系统级错误,如不可恢复的读写错误。在发生可恢复错误后,failbit被置位。如果到达文件结束位置,eofbit和failbit都会被置位。goodbit的值为0,表示流未发生错误。如果bidbit、failbit和eofbit任一个被置位,则检测流状态的条件会失败。

    管理条件状态

    流对象的rdstate成员返回一个iostate值,对应流的当前状态。setState操作将给定条件位置位,表示发生了对应错误。

    auto old_state = cin.rdstate(); //记住cin的当前状态
    cin.clear(); //使cin有效
    process_input(cin); //使用cin
    cin.setstate(old_state); //将cin置为原有状态
    

    1.3 管理输出缓冲

    每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如,如果执行下面的代码:

    os << "Please enter a value";
    

    文本可能立即打印出来,也可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。

    导致缓冲刷新的原因有很多:

    • 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
    • 缓冲区满时,需要刷新缓冲区,而后新的数据才能写入缓冲区。
    • 我们可以使用操纵符如endl来显示刷新缓冲区。
    • 在每个输出操作之后,我们可以使用操纵符unitbuf设置流的内部状态,来清空缓冲区。
    • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
    刷新输出缓冲区

    我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。

    cout << "hi!" << endl; //输出hi和一个换行,然后刷新缓冲区
    cout << "hi!" << flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
    cout << "hi!" << ends; //输出hi和一个空字符,然后刷新缓冲区
    
    unitbuf操作符

    如果想在每次操作后都刷新缓冲区,可以使用unitbuf操纵符。它告诉流接下来的每次写操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:

    cout << unitbuf; //所有输出操作后都会立即刷新缓冲区
    //任何输出都立即刷新,无缓冲
    cout << nounitbuf; //回到正常的缓冲方式
    

    ==如果程序崩溃,输出缓冲区不会被刷新。==

    关联输入和输出流

    当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此下面语句:

    cin >> ival;
    

    导致cout缓冲区被刷新。

    ==交互式系统应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。==


    #2 文件输入输出

    头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。

    除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员管理与流关联的文件。表中列出了这些操作,我们可以对fstream、ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。

    fstream特有的操作 含义
    fstream fstrm; 创建一个未绑定的文件流。fstream是头文件fstream中定义的一个类型。
    fstream fstrm(s); 创建一个fstream,并打开名为s的文件。s可以是string类型,或者是一个指向C风格字符串的指针。这些构造函数都是explict的。默认的文件模式依赖于fstream的类型
    fstream fstrm(s,mode); 与前一个构造函数类似,但按指定mode打开文件。
    fstrm.open(s) 打开名为s的文件,并将文件与fstrm绑定。返回void。
    fstrm.close() 关闭与fstrm绑定的文件。返回void。
    fstrm.is_open() 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭。

    2.1 使用文件流对象

    当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流类都定义了一个名为open的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。

    ifstream in(ifile); //构造一个ifstream并打开给定文件
    ofstream out; //输出文件流并未关联任何文件
    
    用fstream代替iostream&

    接受一个iostream类型引用参数的函数,可以用一个对应的fstream类型来调用。

    成员函数open和close

    如果我们定义了一个空文件流对象,可以随后调用open来将它与文件关联起来:

    ifstream in(ifile); //构筑一个ifstream并打开给定文件
    ofstream out; //输出文件流并未与任何文件关联
    out.open(ifile + ".copy"); //打开指定文件
    

    如果调用open失败,failbit会被置位。

    自动构造和析构
    for(auto p = argv + 1;p != argv + argc;++p) {
        ifstream input(*p); //创建输入流并打开文件
        if(input) { //如果文件打开成功,“处理”此文件
            process(input);
        }else {
            cerr << "couldn't open: " << string(*p); 
        }
    } //每个循环步input都会离开作用域,因此会被销毁。
    

    ==当一个fstream对象被销毁时,close会自动被调用。==

    2.2 文件模式

    每个流都有一个关联的文件模式,用来指出如何使用文件。表给出了文件模式和它们的定义:

    文件模式 含义
    in 以读方式打开
    out 以写方式打开
    app 每次写操作前均定位到文件末尾
    ate 打开文件后立即定位到文件末尾
    trunc 截断文件
    binary 以二进制方式IO

    无论用哪种方式打开文件,我们都可以指定文件模式,指定文件模式有如下限制:

    • 只可以对ofstream或fstream对象设定out模式。
    • 只可以对ifstream或fstream对象设定in模式。
    • 只有当out也被设定时才可设定trunc模式。
    • 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显示指定out模式,文件也总是以输出方式被打开。
    • 默认情况下,即使没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
    • ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用

    每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。

    以out模式打开文件会丢失已有数据

    默认情况下,我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:

    //在这几条语句中,file1都被截断
    ofstream out("file1"); //隐含以输出模式打开文件并截断文件
    ofstream out2("file1",ofstream::out); //隐含地截断文件
    ofstream out3("file1",ofstream::out||ofstream::trunc);
    
    //为了保留文件内容,我们必须显示指定app模式
    ofstream app("file2",ofstream::app); //隐含为输出模式
    ofstream app2("file2", ofstream::out | ofstream::app);
    

    ==保留被ofstream打开文件中已有数据的唯一方法是显示指定app或in模式。==

    每次调用open时都会确定文件模式

    对于一个给定流,每当打开文件时,都可以该变其文件模式:

    ofstream out; //未指定文件打开模式
    out.open("scratchpad"); //模式隐含设置为输出和截断
    out.close(); //关闭out,以便我们将其用于其他文件
    out.open("precious",ofstream::app); //模式为输出和追加
    out.close();
    

    第一个open调用没指定输出模式,文件隐式地以out模式打开。通常情况下,out模式意味着同时使用trunc模式。因此,当前目录名为scratchpad的文件的内容将被清空。当打开名为precious的文件时,我们指定了append模式。文件中已有的数据都得以保留,所有写操作都在文件末尾进行。

    ==每次打开文件时,都要设置文件模式,可能是显示地设置,也可能是隐式地设置。当程序未指定模式时,就使用默认值。==


    #3. string流

    istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可从string读取数据也可向string写数据。

    stringstream特有的操作 含义
    sstream strm; strm是一个未绑定的stringstream对象。
    strm.str() 返回strm所保存的string的拷贝
    strm.str(s) 将string s拷贝到strm中。返回void

    3.1 使用istringstream

    struct PersonInfo {
        string name;
        vector<string> phones;
    };
    
    string line, word; //分别保存来自输入的一行和单词
    vector<PersonInfo> people; //保存来自输入的所有记录
    //逐行从输入读取数据,直至cin遇到文件尾
    while (getline(cin,line)) {
        PersonInfo info; //创建一个保存此记录的数据对象
        istringstream record(line); //将记录绑定到刚读入的行
        record >> info.name; //读取名字
        while (record >> word) { //读取电话号码
            info.phones.push_back(word); //保存它们
        }
        people.push_back(info); //将此记录追加到people末尾
    }
    

    3.2 使用ostringstream

    但我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。

    for(const auto &entry:people) { //对于people中的每一项
        ostringstream formatted,badNums; //每个循环步创建的对象
        for(const auto &nums:entry.phones) {
            if(!valid(nums)) {
                badNums << " " << nums; //将数的字符串形式存入badNums
            }else {
                formatted << " " << format(nums);
            }
        }
        if(badNums.str().empty()) { //没有错误的数
            os << entry.name << " "
            <<formatted.str() << endl;
        }else {
            cerr << "input error: " << entry.name 
                <<" invalid numbers " << badNums.str() << endl;
        }
    }
    

    相关文章

      网友评论

          本文标题:第8章:IO库

          本文链接:https://www.haomeiwen.com/subject/cxbpaftx.html