【C++笔记】8. IO类

8. IO类

8.1 其他IO类型

  1. 为支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据,宽字符版本的类型和函数的名字以一个w开始。
    如wcin、wcout、wcerrcin就是分别对应cout、cerr的宽字符版本。
    宽字符版本与普通版本都在同一个头文件中。

    头文件类型
    iostreamistream, wistream, ostream, wostream, iostream, wiotream
    ftreamifstream, wifstream, ofstream, wofstream, fstream, wftream
    sstreamistringstream, wistringstream, ostringstream, wostringstream, stringstream, wstringtream
  2. 继承机制 使我们可以声明一个特定的类继承自另一个类,通常可以把派生类对象当作基类对象来使用。
    iftream和istringstream都继承自istream。

8.1.1 IO对象无拷贝或赋值

  1. IO操作的函数通常是以引用的方式传递和返回流。

  2. 读写一个IO会改变其状态,因此传递和返回的宁死不能是const的。

8.1.2 条件状态

  1. IO类定义了一些函数和标志:
    注:strm在下表中代表某种特定的IO类型,这个类型应作为一个位集合来使用。

    标志或函数说明
    strm::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中的所有条件状态位复位,将流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
  2. 一个流一旦发生错误,其上后续的IO操作都会失败。

  3. 查询流的状态,即通过iostate类型的状态位来查看,其使用方式类似于quiz1的位操作的使用方式。

  4. badbit:表示系统级错误,一般情况下,一旦被置位,流就无法使用了。
    failbit:表示可恢复错误,如期望读取数值却读出了一个字符。
    eofbit:表示到达文件结束,被置位时failbit也会跟着被置位。
    goodbit:值为0,表示流未发生错误。
    badbit、failbit、eofbit任一个被置位,则检测流状态的条件会失败。

  5. good():在所有错误位均未置位的情况下返回true。
    bad()、fail()、eof()在对应的错误位被置位时返回true。
    badbit被置位时,fail()也会返回true。
    总结:good()和fail()是确定是流总体状态的正确方法。
    而eof()和bad()操作只能表示特定的错误。

  6. 使用示例:

    auto old_state = cin.rdstate(); // 记住cin当前状态
    cin.clear();                    // 使cin有效
    process_input(cin);             // 使用cin
    cin.setstate(old_state);        // 将cin置为原有状态
    // 复位failbit和badbit,保持其他标志位不变
    cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
    

8.1.3 管理输出缓冲

  1. 缓冲区的存在,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
    导致缓冲刷新的原因:

    • 程序正常结束。
    • 缓冲区满时。
    • 使用操纵符显式刷新缓冲区。
    • 在每个输出操作后,可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
    • 一个输出流可能被关联到另一个操作流,这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,cin和cerr都是关联到cout,因此,读cin或写cerr都会导致cout的缓冲区被刷新。
  2. 刷新输出缓冲区:
    endl:换行并刷新缓冲区。
    flush:刷新缓冲区但不输出任何字符。
    ends:向缓冲区插入一个空字符,然后刷新缓冲区。

  3. unibuf操纵符:
    如果想每次输出操作后都刷新缓冲区,可以使用unitbuf操纵符。
    而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。

  4. 警告:如果程序崩溃,输出缓冲区不会被刷新。
    当调试一个已崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行,而实际上代码已经执行了,只是程序崩溃后缓冲区没有刷新,输出数据被挂起没有打印而已。

  5. 交互式系统通常应该关联输入流和输出流,这将使得所有输出,包括用户提示信息,都会在读操作之前被打印出来。

  6. tie()有两个重载的版本:
    (1)不带参数,返回指向输出流的指针。
    如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针。
    如果未关联到流,则返回空指针。
    (2)接受一个指向ostream的指针,将自己关联到此ostream。
    x.tie(&o)将x关联到输出流o。

  7. 在下面这段代码中,为了将一个给定的流关联到一个新的输出流,我们将新流的指针传递给了tie。
    为了彻底开放流的关联,我们传递了一个空指针。
    每个流最多关联到一个流,但多个流可以同时关联到同一个ostream。

    cin.tie(&cout);  // 仅仅是用来展示:标准库将cin和cout关联在一起
    
    // old_tie指向当前关联到cin的流(如果有的话)
    ostream *old_tie = cin.tie(nullptr); // cin不再与其他流关
    
    // 将cin与cerr关联;这不是一个好主意,因为cin应关联到cout
    cin.tie(&cerr);  // 读取cin会刷新cerr而不是cout
    cin.tie(old_tie);  // 重建cin和cout间的正常关联
    

8.2 文件输入输出

  1. ifstream、ofstream、fstream类型,所提供的操作与之前使用过的cin和cout的操作一样。
    特别是,可以用IO运算符(<<和>>)来读写文件,可以用getline()从一个ifstream读取数据。

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

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

8.2.1 使用文件流对象

  1. 创建文件流对象时,我们可以提供文件名。如果提供了一个文件名,则open函数自动被使用。
    ifstream in(ifile);:构造一个ifstream并打开给定文件。
    ofstream out;:输出文件流并未关联到任何文件。

  2. 自动构造和析构
    如果一个程序的main函数接受一个要处理的文件列表,这种程序可能会有如下的循环:

    for (auto p = argv + 1; p != argv + argc; ++p){
        ifstream input(*p);  // 创建输出流并打开文件
        if (input){
            process(input);
        }else{
            cerr << "couldn't open: " + string(*p);
        }
    }  // 每个循环步input都会离开作用域,因此会被销毁
    
  3. 上面的程序中,input是while循环的局部变量,在每个循环步中都要创建和销毁一次。
    当一个fstream对象被销毁时,close会自动被调用。

8.2.2 文件模式

  1. 每个流都有一个关联的文件模式。

    模式说明
    in以读方式打开
    out以写方式打开
    app每次写操作前均应定位到文件末尾
    ate打开文件后立即定位到文件末尾
    trunc截断文件
    binary以二进制方式进行IO
  2. 指定文件模式有以下限制:

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

    // 以下语句中,file1都被截断
    // 隐含以输出模式打开文件并截断文件
    ofstream out("file1");
    // 隐含的截断文件
    ofstream out2("file1", ofstream::out);
    ofstream out2("file1", ofstream::out | ofstream::trunc);
    // 为了保留文件内容,我们必须显式指定app模式
    ofstream app("file2", ofstream::app);
    ofstream app2("file2", ofstream::out | ofstream::app);
    
  4. 每次调用open时都会确定文件模式
    对于一个给定流,每当打开文件时,都可以改变其文件模式。
    下面的代码中的第一个open,未显式指定输出模式,文件隐式地以out模式打开。且通常情况下,out模式意味着同时使用trunc模式。因此,当前目录名下scratchpad文件内容将被清空。
    当打开precious的文件时,我们指定了append模式,文件中已有的数据都得以保留,所有写操作都在文件末尾进行。

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

8.3 string流

  1. sstream头文件定义了三个类型来支持内存IO:istringstream、ostringtream、stringstream。
    头文件sstream中定义的类型都继承自iostream。
    stringstream增加的操作如下表,其中stream是某种特定类型:

    操作说明
    sstream strmstrm是一个未绑定的stringstream对象
    sstream strm(s)strm是一个对象,保存string s的一个拷贝,此构造函数是explicit的
    strm.str()返回strm所保存的string的拷贝

8.3.1 使用istringstream

  1. 当某些工作是对整行文本进行处理,而其他工作是处理行内单词时,通常使用istringstream。

    string line, word;
    vector<PersonInfo> people;
    while (getline(cin, line)) {
        PersonInfo info;
        istringstream record(line);
        record >> info.name;
        while (record >> word)
            info.phones.push_back(word);
        people.push_back(info);
    }
    

8.3.2 使用ostringstream

  1. 使用场景:逐步构造输出,最后一起打印。
    下面的程序功能:逐个验证电话号码并改变格式,若有效则保留,无效则打印一条包含人名和无效信息的错误信息。
    在此程序中,我们使用标准输出运算符(<<)向这些对象写入数据,但这些写入操作实际上转换为string操作,分别向formatted和badNums中的string对象添加字符。

    for (const auto &entry : people) {
        ostringstream formatted, badNums;
        for (const auto &nums : entry.phones) {
            if (!valid(nums)) {
                badNums << " " << nums;
            } else {
                formatted << " " << format(nums);
            }
        }
        if (badNums.str().empty()) {
            os << entry.name << " "
                << formatted.str() << endl;
        } else {
            cerr << "input error: " << entry.name
                << " invalid number(s) " << badNums.str() << endl;
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值