012 IO 库

IO 类

图片来自网络,侵删,谢谢原作者

IO 库类型和头文件

头文件 类型
iostream istream,wistream 从流读取数据
ostream,wostream 向流写入数据
iostream,wiostream 读写流
fstream ifstream,wifstream 从文件读取数据
ofstream,wofstream 向文件写入数据
fstream,wfstream 读写文件
sstream istringstream,wistringstream 从 string 读取数据
ostringstream,wostringstream 向 string 写入数据
stringstream,wstringstream 读写 string

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

IO 类型间的关系

IO 对象无拷贝或赋值

条件状态

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

函数和标志 说明
strm::iostate strm 是一种 IO 类型,iostate 是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit 用来指出流已崩溃
strm::failbit 用来指出一个 IO 操作失败了
strm::eofbit 用来指出流到达了文件结束
strm::goodbit 用来指出流未处于错误状态,此值为 0
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

在下面的测试代码中

int nTest;
cin >> nTest;

如果我们在标准输入中输入 test,读操作就会失败。代码中的输入运算符期待读取一个 int,但是却得到了一个字符 t。这样,cin 就会进入错误状态。类似的,如果我们输入一个文件结束标识,cin 也会进入错误状态。

一个流一旦发生错误,其上后续的 IO 操作就会失败。只要当一个流处于无错误状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用:

while (cin >> nTest) {
    // todo something
}
查询流的状态

将流作为条件状态使用,只能告诉我们流是否有效,而无法告诉我们具体发生了什么。有时我们也需要知道流为啥失败。例如,在输入文件结束符之后我们该如何处理,可能与一个 IO 设备错误的处理方式是不同的。

IO 库定义了一个与机器无关的 iostate 类型,它提供了表达流状态的完整功能。这个类型应作为一个位集合来使用。IO 库定义了 4 个 iostate 类型的 constexpr 值,表示特定的位模式。这些值用来表示特定类型的 IO 条件,可以与位运算符一起使用来一次性检测或设置多个标志位。

badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦 badbit 被置位,流就无法再使用了。在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误,这种问题通常是可以修正的,流还可以继续用。如果到达文件结束位置,eofbit 和 failbit 都会被置位。goodbit 的值为 0,表示流未发生错误。如果 badbit、failbit 和 eofbit 任意一个被置位,则检测流状态的条件会失败。

标准库还定义了一组函数来查询这些标志位的状态。操作 good 在所有错误位均未置位的情况下返回 true,而 bad、fail 和 eof 则在对应的错误被置位时才返回 true。此外,在 badbit 被置位时,fail 也会返回 true。这意味着,使用 good 或 fail 是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于 !fail()。而 eof 和 bad 操作只能表示特定的错误。

管理条件状态

流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态。setstate 操作将给定条件位置位,表示发生了对应错误。clear 成员是一个重载的成员:它有一个不接受参数的版本和一个接受一个 iostate 类型参数的版本。

不带参数的版本清除(复位)所有错误标志位。执行 clear() 后,调用 good 会返回 true。我们可以这样使用这些成员:

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

带参数的 clear 版本接受一个 iostate 值,表示流的新状态。为了复位单一的条件状态位,我们首先用 rdstate 读出当前状态,然后用位操作将所需位复位来生成新的状态。例如,下面的代码将 failbit 和 badbit 复位,但是保持 eofbit 不变:

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

管理输出缓冲

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

os << "please enter a value: ";

文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
导致缓冲刷新(即,将数据真正写到输出设备或文件)的原因有很多:

  • 程序正常结束,作为 main 函数的 return 操作的一部分,缓冲刷新被执行。
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
  • 我们可以使用操作符如 endl 来显示地刷新缓冲区。
  • 在每个输出操作之后,我们可以用操作符 unitbuf 设置流的内部状态,来清空缓冲区。默认情况下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容都是立即刷新的。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin 和 cerr 都关联到 cout。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新。
刷新输出缓冲区

我们已经使用过操作符 endl,它完成换行并刷新缓冲区的工作。IO 库中还有两个类似的操作符:flush 和 ends。flush 刷新缓冲区,但不输出任何额外的字符;ends 向缓冲区插入一个空字符,然后刷新缓冲区:

    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 的缓冲区被刷新。

tie 有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie 的第二版本接受一个指向 ostream 的指针,将自己关联到此 ostream。即,x.tie(&o) 将流 x 关联到输出流 o。
我们既可以将一个 istream 对象关联到另一个 ostream,也可以将一个 ostream 关联到另一个 ostream:

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

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

文件的输入输出

    #include <fstream>
    ofstream         //文件写操作 内存写入存储设备 
    ifstream         //文件读操作,存储设备读区到内存中
    fstream          //读写操作,对打开的文件可进行读写操作

fstream 的一些常见的操作方法:

    #include <fstream>
    // ...
    string fname("c:/Users/toby/Downloads/test.txt");
    ios_base::openmode mode = ios_base::in;
    fstream fstrm;                  // 创建一个未绑定的文件流。
    fstream fstrm1(fname);          // 创建一个 fstream,并打开名为 fname 的文件
    fstream fstrm2(fname, mode);    // 创建一个 fstream,并以 mode 指定的方式打开名为 fname 的文件
    fstrm2.open(fname);
    if (fstrm2.is_open()) {  // 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭
        fstrm2.close();  // 关闭与 fstrm 绑定的文件
    }

使用文件流对象

#include <fstream>
#include <string>
#include <iostream>

struct Sales_data {
    std::string bookNo;
    unsigned units_sold;
    double revenue;

    std::string isbn() const { return bookNo; }
    Sales_data &combine(const Sales_data&);
};

Sales_data& Sales_data::combine(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

std::istream &read(std::istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

std::ostream &print(std::ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue;
    return os;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::ifstream input(argv[1]);           // 打开销售记录文件
    std::ofstream output(argv[2]);          // 打开输出文件
    Sales_data total;                       // 保存销售总额的变量
    if (read(input, total)) {               // 读取第一条销售记录
        Sales_data trans;                   // 保存下一条销售记录的变量
        while (read(input, trans)) {        // 读取剩余记录
            if (total.isbn() == trans.isbn()) { // 检查 ISBN
                total.combine(trans);       // 更新销售总额
            } else {
                print(output, total) << std::endl;  // 打印结果
                total = trans;              // 处理下一本书
            }
        }
        print(output, total) << std::endl;  // 打印最后一本书的销售额
    } else {
        std::cerr << "Not data!" << std::endl;  // 文件中无输入数据
    }

    return a.exec();
}

指定文件模式

模式 说明
ios::in 以读方式打开文件
ios::out 以写方式打开文件
ios::ate 打开文件后立即定位到文件末尾
ios::app 每次写操作前均定位到文件末尾
ios::trunc 如果文件已存在则先删除该文件
ios::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 都被截断
    std::ofstream out("file1");     // 隐含以输出模式打开文件并截断文件
    std::ofstream out1("file1", std::ofstream::out);    // 隐含地截断文件
    std::ofstream out2("file1", std::ofstream::out | std::ofstream::trunc);
    // 为了保留文件内容,我们必须显式地指定 app 模式
    std::ofstream out3("file1", std::ofstream::app);    // 隐含为输出模式
    std::ofstream out4("file1", std::ofstream::out | std::ofstream::app);
每次调用 open 时都会确定文件模式

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

    std::ofstream out;      // 未指定文件打开模式
    out.open("testfile");   // 模式隐含设置为输出和截断
    out.close();            // 关闭 out,以便我们将其用于其他文件
    out.open("testfile1", std::ofstream::app);  // 模式为输出和追加,所有写操作都在文件末尾进行
    out.close();

string 流

使用 istringstream

#include <string>
#include <sstream>

struct PersonInfo {
    std::string name;
    std::vector<std::string> phones;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::vector<PersonInfo> person;
    std::string line, word;

    while (getline(std::cin, line)) {
        PersonInfo info;
        std::istringstream record(line);
        record >> info.name;
        while (record >> word) {
            info.phones.push_back(word);
        }
        person.push_back(info);
    }

    return a.exec();
}

使用 ostringstream

#include <string>
#include <sstream>

struct PersonInfo {
    std::string name;
    std::vector<std::string> phones;
};

bool valid(const std::string &str)
{
    return isdigit(str[0]);
}

std::string format(const std::string &str)
{
    return str.substr(0,3) + "-" + str.substr(3,3) + "-" + str.substr(6);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::vector<PersonInfo> person;
    std::string line, word;

    while (getline(std::cin, line)) {
        PersonInfo info;
        std::istringstream record(line);
        record >> info.name;
        while (record >> word) {
            info.phones.push_back(word);
        }
        person.push_back(info);
    }


    for (const auto &entry : person) {
        std::ostringstream formatted, badNums;
        for (const auto &phone : entry.phones) {
            if (valid(phone)) {
                formatted << " " << format(phone);
            } else {
                badNums << " " << phone;
            }
        }

        if (badNums.str().empty())
            std::cout << entry.name << formatted.str() << std::endl;
        else
            std::cerr << "input error: " << entry.name
                 << " invalid numbers " << badNums.str() << std::endl;
    }

    return a.exec();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容

  • #1.IO类IO对象无拷贝或赋值条件状态管理输出缓冲 #2.文件输入输出使用文件流对象文件模式 #3.string...
    MrDecoder阅读 343评论 0 0
  • 1 C++缓冲区 在学习标准IO库之前,我们先了解C++中缓冲区的使用。关于操作系统中缓冲区的学习与理解,请查看操...
    saviochen阅读 884评论 0 4
  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,304评论 0 22
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,843评论 0 5
  • 第三章 文件i/o 3.1引言 不带缓冲的io(unix系统在内核中设有缓冲区,这个不带缓冲意思是用户不自己缓冲)...
    m风满楼阅读 996评论 0 0