C++Primer 第八章 IO库

已经介绍的大部分IO库设施:
istream(输入流)类型,提供输入操作
ostream(输出流)类型,输出
cin,一个istream对象,从标准输入读取数据
cout,一个ostream对象,写入
cerr,ostream,用于输出程序错误信息,写入到标准错误
“>>”运算符,用来从一个istream对象读取输入数据
"<<"用来向一个ostream对象写入输出数据
getline函数,从一个给定的istream读取一行数据,存入一个给定的stream对象

IO类

默认情况下,我们已经使用过的IO类型和对象都是关联到控制台的。iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
在这里插入图片描述

IO对象无拷贝或赋值

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

条件状态

IO库条件状态

管理条件状态

流对象的rdstate成员返回一个iostate值,对应流的当前状态。setstate操作将给定条件位置位,表示发生了对应的错误。clear成员是一个重载的成员。
clear不接受参数的版本清除(复位)所有错误标志位。执行clear()后,调用good()会返回true,使用方法:

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

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

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
练习8.1

接受一个istream&参数,返回类型也是istream&。此函数从给定流中读取数据,直至遇到文件结束标识时停止。他将读取的数据打印在标准输出,完成之后,在返回流之前,对流进行复位,使其处于有效状态

istream &func(istream &in)
{
    std::string buf;
    while(in>>buf)
        std::cout<<buf<<std::endl;
    in.clear();
    return in;
}

int main()
{
    func(cin);
}

管理输出缓冲

刷新输出缓冲区

IO库还有两个与endl相似的操纵符:flush、ends,flush刷新缓冲区,不附加任何额外字符;ends向缓冲区插入一个空字符,然后刷新缓冲区
cout<<“hi”<<flush;
cout<<“hi”<<ends;

unitbuf操纵符

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

cout<<unitbuf;
cout<<nounitbuf;

关联输入和输出流

当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起。
tie有两个重载版本,一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到ostream。即x.tie(&o)将流x关联到输出流o

    cin.tie(&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间的正常关联

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

文件输入输出

在这里插入图片描述

使用文件流对象

练习8.4

编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main()
{//E:/123.txt 为文件data的文件名及其相对路径
 //(是相对于可执行程序所在目录的相对路径)
    ifstream in("E:/123.txt");
    if(!in)
    {
        cerr<<"无法打开输入文件"<<endl;
        return -1;
    }
    string line;
    vector<string> words;
    while(getline(in,line))
    {
        words.push_back(line);
    }
    in.close();

    vector<string>::const_iterator it=words.begin();
    while(it!=words.end())
    {
        cout<<*it<<endl;
        ++it;
    }
}

练习8.5

将每一个单词作为一个独立的元素存储

while(in>>line)
练习8.6

重写书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给main

#include <iostream>
#include <fstream>
#include "Sales_data.h"
using namespace std;

int main(int argc, char *argv[]) {
    if (argc != 2) {
        cerr << "请给出文件名" << endl;
        return -1;
    }
    ifstream in(argv[1]);
    if (!in) {
        cerr << "无法打开输入文件" << endl;
        return -1;
    }

    Sales_data total;               // 保存当前求和结果的变量
    if (total.read(in, total)) {             // 读入第一笔交易记录
        Sales_data trans;          // 保存下一条交易数据的变量
        while (trans.read(in, trans)) {     // 读入剩余的交易
            if (total.isbn() == trans.isbn())  // 检查 isbn
                total = total.add(total, trans);     // 更新变量 total 当前的值
            else {
                total.print(cout, total) << endl;    // 输出结果
                total = trans;                 // 处理下一本
            }
        }
        total.print(cout, total) << endl;            // 输出最后一条交易
    }
    else {                              // 没有输入任何信息
        cerr << "没有数据" << endl;    // 通知用户
        return -1;
    }
    return 0;
}

文件模式

每个流都有一个关联的文件模式,用来指出如何使用文件
在这里插入图片描述
无论用哪种方式打开文件,我们都可以指定文件模式,调用open打开文件时或者用一个文件名初始化流来隐式打开文件也可以:

只可以对ofstream或fstream对象设定out模式
只可以对ifstream或fstream对象设定in模式
只有当out也被设定时才可设定trunc模式
只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式打开
ate和binary模式可用于任何类型的文件流对象,且可以与其它任何文件模式组合使用

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

以out模式打开文件会丢弃已有数据
    //file1都会被截断
    ofstream out("file1");
    ofstream out("file1",ofstream::out);
    ofstream out("file1",ofstream::out | ofstream::trunc);
    //为了保留文件内容,显式指定app模式
    ofstream out("file2",ofstream::app);
    ofstream out("file2",ofstream::out | ofstream::app);

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

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

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

    ofstream out;
    out.open("scratchpad");//模式隐含设置为输出和截断
    out.close();
    out.open("precious",ofstream::app);//模式为输出和追加
    out.close();

第一个文件隐式地以out模式打开。out模式意味着同时使用trunc模式。因此当前目录下为scratchpad的文件将被清空。当打开precious的文件时,指定了append模式,文件已有数据得以保留,所有写操作在文件末尾

string流

sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream都可以
在这里插入图片描述

使用istringstream

当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时可以使用
假定有一个文件,列出了一些人和它们的电话号码。某些人只有一个电话号码,而另一些人有多个。我们输入格式为:
在这里插入图片描述
文件中每条记录都以一个人名开始,后面跟随一个或多个电话号码,首先定义一个简单的类来描述输入数据:

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

我们的程序会读取数据文件,并创建一个PersonInfo的vector。在一个循环中处理输入数据:

    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.9

打印一个istringstream的内容

#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>
#include "Sales_data.h"
using namespace std;

istream &f(istream &in)
{
    string v;
    while(in>>v,!in.eof())
    {
        if(in.bad())
            throw runtime_error("IO流错误");
        if(in.fail())
        {
            cerr << "数据错误,请重试" << endl;
            in.clear();
            in.ignore(100,'\0');
            continue;
        }
        cout<<v<<endl;
    }
    in.clear();
    return in;
}

int main(int argc,char *argv[])
{
    ostringstream msg;
    msg<<"C++ P 第五"<<endl;
    istringstream in(msg.str());
    f(in);
    return 0;
}
练习8.10

将来自文件中的一行保存在一个vector</string/>中,然后使用一个istringstream从vector读取数据元素,每次读取一个单词

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

int main(int argc,char *argv[])
{
    ifstream in("E:/2.txt");
    if(!in)
    {
        cerr<<"无法打开输入文件"<<endl;
        return -1;
    }

    string line;
    vector<string> words;
    while(getline(in,line))
        words.push_back(line);

    in.close();

    vector<string>::const_iterator it=words.begin();
    while(it!=words.end())
    {
        istringstream line_str(*it);
        string word;
        while(line_str>>word)
            cout<<word<<'\n';
        ++it;
    }
}
练习8.11

本节的程序在外层while循环定义了istringstream对象。如果record对象定义在循环之外,需要怎样修改

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

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

int main(int argc,char *argv[])
{
    string line, word;              // 分别保存来自输入的一行和单词
    vector<PersonInfo> people;
    istringstream record;

    // 逐行从输入读取数据,直至 cin 遇到文件尾(或其他错误)
    while (getline(cin, line) && line != "Q") {
    PersonInfo info;
    record.clear();
    record.str(line);
    record >> info.name;
    while (record >> word)
        info.phones.push_back(word);
    people.push_back(info);
    }

    for(auto &p:people)
    {
        cout<<p.name<<" ";
        for(auto &s:p.phones)
        {
            std::cout<<s<<" ";
        }
        cout<<endl;
    }
}

使用ostringstream

当我们逐步构造输出,希望最后一期打印时,使用ostringstream。对于上一节例子,我们可能想逐个验证电话号码并改变其格式。如果所有号码都是有效的,我们希望输出一个新的文件,包含改变格式后的号码。对于无效号码,我们不会将它们输出到新文件中,而是打印一条包含人名和无效号码的错误信息
对于每个人,直到验证完所有的电话号码后才可以进行输出操作,我们可以先将输出内容写入到一个内存ostringstream中:

    for(const auto &entry:people)
    {
        ostringstream formatted,badNums;
        for(const auto &nums:entry.phones)
        {
            if(!valid(nums)){
                badNums<<" "<<nums;//将数的字符串形式存入badNums
            }
            else
                //将格式化的字符串”写入“formatted
                formatted<<" "<<format(nums);
            
        }
        if(badNums.str().empty())
            os<<entry.name<<" "
              <<formatted.str()<<endl;
        else
            cerr<<"input error: "<<entry.name
            <<"invalid number:"<<badNums.str()<<endl;
    }

假定已有两个函数,valid和format,分别完成电话号码验证和改变格式的功能。

练习8.13

从一个文件读取数据并输出

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

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

string format(const string &s)
{
    return s;
}

bool valid(const string &s)
{
    return true;
}

int main(int argc,char *argv[])
{
    string line, word;              // 分别保存来自输入的一行和单词
    vector<PersonInfo> people;
    istringstream record;

    ifstream in("E:/2.txt");
    if (!in) {
        cerr << "无法打开输入文件" << endl;
        return -1;
    }

    while (getline(in, line)) {
        PersonInfo info;            // 创建一个保存此记录数据的对象
        record.clear();             // 重复使用字符串流时,每次都要调用 clear
        record.str(line);           // 将记录绑定到刚读入的行(将 line 拷贝到 record 中)
        record >> info.name;        // 读取名字
        while (record >> word)      // 读取电话号码
            info.phones.push_back(word);    // 保持它们
        people.push_back(info);     // 将此记录追加到 people 末尾
    }
    ostringstream os;
    for (const auto &entry : people) {  // 对 people 中每一项
        ostringstream formatted, badNums;   // 每个循环步创建的对象
        for (const auto &nums : entry.phones) {     // 对每个数
            if (!valid(nums))
                badNums << " " << nums;     // 将数的字符串形式存入 badNums
            else
                // 将格式化的字符串"写入" formatted
                formatted << " " << format(nums);
        }
        if (badNums.str().empty())      // 没有错误的数
            // 打印名字和格式化的数
            os << entry.name << " " << formatted.str() << endl;
        else
            // 否则,打印名字和错误的数
            cerr << "input error: " << entry.name
            << " invalid numbers(s) " << badNums.str() << endl;
    }
    cout<<os.str()<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值