C++学习笔记(IIX)之 IO库

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

 

1.IO类


   为了支持不同的IO处理操作,在istream和ostream之外,标准库还定义了其他一些IO类型,他们分别被定义在三个独立的头文件中:iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存对象的类型。以下列出了表格

  
头文件 类型
iostream

istream,wistream 从流读取数据
ostream,wostream 向流写入数据
iostream,wiostream 读写流

fstream

ifstream, wifstream 从文件读取数据
ofstream, wostream 向文件写入数据
fstream, wfstream 读写文件

sstream

istringstream, wistringstream 从string读取数据
ostringstream, wostringstream 向string 写入数据
stringstream, wstringstream 读写string

   为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。例如,wcin、wcout和wcerr。

下图是他们类间关系:

 IO对象无拷贝或赋值

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

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

条件状态

       状态位  说明
strm::iostate strm是一种IO类型,在上表中已经列出。iostate是一种机器相关的类型,提供了表达条件状态的完整功能
strm::badbit strm::badbit用来指出流已崩溃
strm::failbit strm::failbit用来指出一个IO操作失败 
strm::eofbit strm::eofbit用来指出流到达了文件结束 
strm::goodbit 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

   一个流一旦发生错误,其后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以对其读写数据。因此前通常应该在使用一个流之前检查它是否处于良好状态。一般是将它当作一个条件来使用。如:

#include <iostream>
using namespace std;
int main()
{
    int nVal;
    while(cin >> nVal)
    {
        //读操作成功
    }   
    return 0;
}

goodbit的值为0,表示流未发生错误。
badbit表示系统级错误,通常情况下,一旦badbit被置位,流就无法再使用了。
failbit在发生可恢复错误后被置位。如期望读取数据却读出一个字符等错误。
eofbit指到达文件结束位置,此时eofbit和failbit都会被置位。
如果badbit、failbit、eofbit中任何一个被置位,则检测状态的条件会失败。下面是流状态及函数对照表:

iostate value
(member constant)
indicatesfunctions to check state flags
good()eof()fail()bad()rdstate()
goodbit No errors (zero value iostate) true false false false goodbit
eofbit End-of-File reached on input operation false true false false eofbit
failbit Logical error on i/o operation false false true false failbit
badbit Read/writing error on i/o operation false false true true badbit

  由上表知,在badbit被置位时,fail()也会返回true。使用good或fail是确定流的总体状态的正确方法。实际上,我们将流当作条件使用的代码就等价于!fail()。而eof和bad操作只能表示特定的错误。

输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。

导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:

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

操纵符

操纵符 含义
endl 完成换行并刷新缓冲区
ends 向缓冲区插入一个空字符并刷新缓冲区
flush 不输出任何额外字符只刷新缓冲区
unitbuf 接下来每次写操作后进行一次flush操作
nounitbuf 重置流,恢复正常缓冲区刷新机制

Eg:

#include <iostream>
using namespace std;
int main()
{
    cout<<"carpenter's"<<ends;//输出carpenter's和一个空字符,然后刷新缓冲区
    cout<<"ink"<<flush;//输出ink然后刷新缓冲区
    cout<<"marker"<<endl;//输出marker和一个换行符,然后刷新缓冲区
    cout<<unitbuf;//所有输出操作后都会立即刷新缓冲区
    //任何输出都立即刷新,无缓冲
    cout<<nounitbuf;//回到正常的缓冲机制
    return 0;
}

效果图:

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

 关联输入和输出流

用来关联输入与输出流的函数是tie,此函数有两个重载版本:
版本一,不带参数,返回指向输出流的指针。若关联返回指向关联输出流,否则返回空指针。
版本二,接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。x既可是istream也可是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间的正常关联

每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。

  

2.文件输入输出


  除了继承自iostream类型的行为之外,fstream中定义的类型还增加了一些新的成员来管理与流关联的文件。如下表:

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

使用文件流对象  

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

创建文件对象时,可以提供文件名(可选的)。如果提供了一个文件名,则open会自动被调用:

ifstream ifs("D:\\test.txt");//构造一个ifstream并打开给定的文件
ifstream ifs;//输出文件流未关联到任何文件
ifs.open("D:\\test.txt");//打开给定的文件

用fstream代替iostream&

  要求使用基类型对象的地方,可以用继承类型的对象来替代。这意味着,接受一个iostream类型引用(或指针)参数的函数,可以用一个对应的fstream(或sstream)类型来调用。

成员函数open与close

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

如果调用open失败,failbit会被置位。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯

if(out)//检查open是否成功
        //open成功,可以使用文件

  一旦一个文件流已经打开,它就保持与对应文件的关联。为了将文件流关联到另外一个文件,必须入选关闭已经关联的文件。一旦文件成功关闭,才可以打开新的文件:

ifs.close();    //关闭文件
ifs.open(ifile +"6");    //打开另一个文件

如果open成功,则open会设置流的状态,使得good()为true。

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

#include <fstream>
#include <iostream>
int main()
{
    std::ifstream ifs;
    ifs.open("D:\\test.txt",std::ifstream::in);//以读的方式打开test.txt文件
    char ch = ifs.get();//从ifs流读取一个字符
    while (ifs.good())
    {
        std::cout << ch;
        ch = ifs.get();
    }
    ifs.close();
    return 0;
}

自动构造和析构

//对每个传递给程序的文件执行循环操作
for (auto p = argv + 1; p != argv + argc; ++p)
{
    ifstream input(*p);//创建输入流并打开文件
    if(input)//如果文件打开成功,“处理”此文件
    {
        process(input);
     }
    else
    {
        cerr<<"couldn't open:" + string(*p);
    }
}//每个循环步input都会离开作用域,因此会被销毁

因为input是for循环的局部变量,它在每个循环步中都要创建和销毁一次。当一个fstream对象其作用域时,与之关联的文件会自动关闭。

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

2.2 文件模式

每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。下表列出文件模式和它们的含义。

文件模式表
文件模式 含义
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);

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

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

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

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

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

 

3.string流


  sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像stirng是一个IO流一样。

istringstream 从string读取数据,ostringstream向string写入数据,而stringstream既可从string读数据也可向string写数据。下表是stringstream对象所特有的操作。

stringstream特有的操作
操作 说明
sstream strm; strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型
sstream strm(s); strm是一个sstream对象,保存string s的一个拷贝。此构造函数是explicit的
strm.str() 返回strm所保存的string的拷贝
strm.str(s) 将string s拷贝到strm中。返回void

使用istringstream

 当我们某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通过可以使用istringstream。下面举一个粟子:

假定,一个文件中存放的是通讯录,每条记录以一个人名开始,后面跟随一个或多个电话号码,如下:

William 0558-1234567 15300000000
view 0571-1234567
Sun 6095550132 2015550175 8005550000

将其读取到类对象中。

#include <string>
#include <vector>
#include <fstream>//文件流
#include <sstream>//字符串流
using namespace std;
struct PersonInfo //存放用户电话信息的类
{
    string name;
    vector<string> phones;
};
int main()
{
    std::ifstream ifs;
    ifs.open("D:\\test.txt", std::ifstream::in);//以读的方式打开test.txt文件
    string line, word;//分别保存来自输入的一行和单词
    vector<PersonInfo> people;//保存来自输入的所有记录
    //逐行从输入读取数据,直至ifs遇到文件尾(或其他错误)
    while (getline(ifs,line))
    {
        PersonInfo info;//创建一个保存此记录数据的对象
        istringstream record(line);//将记录绑定到刚读入的行
        record >> info.name;//读取名字
        while (record >> word)//读取电话号码
        {
            info.phones.push_back(word);//保持它们
        }
        people.push_back(info);//将此记录追加到people末尾
    }
    return 0;
}

使用ostringstream

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

#include <string>
#include <vector>
#include <fstream>//文件流
#include <sstream>//字符串流
#include <iostream>
using namespace std;
struct PersonInfo
{
    string name;
    vector<string> phones;
};
//验证电话号码的有效性,并不完善
bool validTel(const string& strTel)
{
    if (!strTel.length())
        return false;
    if ('0' == strTel.at(0))//固话
    {
        if (-1 == strTel.find('-'))
            return false;
        return true;
    }
    else if ('1' == strTel.at(0) 
        && 11 == strTel.length() )//手机
    {
        return true;
    }
    return false;
}
int main()
{
    std::ifstream ifs;
    ifs.open("D:\\test.txt", std::ifstream::in);//以读的方式打开test.txt文件
    string line, word;//分别保存来自输入的一行和单词
    vector<PersonInfo> people;//保存来自输入的所有记录
    //逐行从输入读取数据,直至ifs遇到文件尾(或其他错误)
    while (getline(ifs,line))
    {
        PersonInfo info;//创建一个保存此记录数据的对象
        istringstream record(line);//将记录绑定到刚读入的行
        record >> info.name;//读取名字
        while (record >> word)//读取电话号码
        {
            info.phones.push_back(word);//保持它们
        }
        people.push_back(info);//将此记录追加到people末尾
    }

    for (const auto &entry : people)//对people中每一项
    {
        ostringstream formatted, badNums;//每个循环步创建一个对象
        for (const auto &nums : entry.phones)//对每个数
        {
            if (!validTel(nums))
            {
                badNums << "\t" << nums;//将数的字符串形式存入badNums
            }
            else//将格式化的字符串"写入"formatted
            {
                formatted << "\t" << (nums);
            }
        }
        if (badNums.str().empty())//没有错误的数
        {
            cout << entry.name << "\t"//打印名字
                << formatted.str() << endl;//与格式化的数
        }
        else//否则,打印名字和错误的数
        {
            cerr << "input error: " << entry.name
                << " invalid number(s) " << badNums.str() << endl;
        }
    }
    return 0;
}

下面是测试数据的运行结果:

 

小结:

C++使用标准库类来处理面向流的输入和输出:

iostream处理控制台IO
fstream处理命名文件IO
stringstream完成内存string的IO

 

posted @ 2018-01-31 10:32  vsxw  阅读(430)  评论(0编辑  收藏  举报