C++学习笔记8:标准IO库

作者: 云中布衣   分类:  学习笔记    热度: (387℃)   时间: 2017-7-17 16:24   标签: #始于C++而不止于C++    

C++的输入/输出(input/output)由标准库提供。标准库定义了一族类型,支持对文件和控制窗口等设备的读写(IO)。还定义了其他一些类型,使string对象能够像文件一样操作,从而使我们无需IO就能实现数据与字符之间的转换。这些IO类型都定义了如何读写内置数据类型的值。此外一般来说,类的设计者还可以很方便地使用IO标准库设施读写自定义类的对象。类类型通常使用IO标准库为内置类型定义的操作符和规则来读写。

回顾一下前面程序已经用到的多种IO标准库:istream(输入流)类型,提供输入操作;ostream(输出流)类型,提供输出操作;cin:读入标准输入的istream对象;cout:写到标准输出的ostream对象;cerr:输出到标准错误的ostream对象。cerr常用于程序错误信息;>>操作符,用于从istream对象中读入输入;<<操作符,用于把输出写到ostream对象中;getline函数,以istream类型和string对象作为两个引用形参,读入整行文本,返回istream对象。

1.面向对象的标准库

迄今为止,我们已经使用IO类型和对象读写数据流,他们常用于与用户控制窗口的交互。当然实际的程序不能仅限于对控制窗口的IO,通常还需要读或写已命名的文件,此外程序还应该能方便的使用IO操作格式化内存中的数据,从而避免读写磁盘或其他设备的复杂性和运行代价。应用程序还需要支持宽字符(wide-character)语言的读写。

在C++中这些操作均可以使用>>操作符来实现,乍看起来有些复杂,为了管理这样的复杂性,标准库使用了继承(inheritance)来定义一组面向对象(objec-oriented)类。如果两种类型存在继承关系,则可以说一个类继承了其父类的行为-接口。C++中所提及的父类成为基类(base class),而继承而来的类成为派生类(derived class)。

IO类型在三个独立的头文件中定义:iostream定义读写控制窗口的类型,fstream定义读写已命名文件的类型,而sstream所定义的类型则用于读写存储在内存中的string对象。在fstream和sstream里定义的每种类型都是从iostream头文件中定义的相关类型派生而来的。下表是IO标准库类型和头文件。

IO标准库类型和头文件
Header
Type
iostream
  • istream 从流中读取
  • ostream 写到流中去
  • iostream对流进行读写,从istream和ostream派生而来
fstream
  • ifstream 从文件中读取,由istream派生而来
  • ofstream 写到文件中去,由ostream派生而来
  • fstream读写文件,由iostream派生而来
sstream
  • istringstream 从string对象中读取,由istream派生而来
  • ostringstream写到string对象中去,由ostream派生而来
  • stringstream对string对象进行读写,由iostream派生而来

国际字符的支持迄今为止,所描述的流类(stream class)读写的是由char类型组成的流。此外,标准库还定义了一组相关的类型,支持wchar_t。每个类都加上"w"前缀,以此与char类型的版本区分开来。例如wostream、wistream、wiostream……。标准库还定义了从标准输入输出读写宽字符的对象,这些对象加上"w"前缀,以此与char类型版本区分,例如wcin、wcout以及wcerr。每一个IO头文件,都定义了char和wchar_t类型的类和标准输入输出对象,例如iostream、fstream以及sstream。

IO对象不可复制或赋值:出于某些原因,标准库不允许做复制或赋值操作。这里有两层含义,第一,由于流对象不能复制,因此不能存储在vector(或其他)容器中(即不存在存储流对象的vector或其他容器)。第二个含义是,形参或返回类型也不能为流类型。如果需要传递或返回IO对象,则必须传递或返回指向该对象的指针或引用。

2.条件状态

实现IO的继承正式错误发生的根源。一些错误是可恢复的,一些错误则发生在系统底层,位于程序可修正的范围之外。IO标准库管理一系列条件状态(condition state)成员,用来标记给定的IO对象是否处于可用状态,或者碰到了哪种特定的错误。下表列出了标准库定义的一组函数和标记,提供访问和操纵流状态的手段。

IO标准库的条件状态
strm::iostate
机器相关的整型名,由各个iostream类定义,用于定义条件状态
strm::badbit
strm::iostate类型的值,用于指出被破坏的流(系统级故障,不可恢复的错误,例如无法读写)
strm::failbit
strm::iostate类型的值,用于指出失败的IO操作(可恢复的错误,如希望获得数值型时输入了字符)
strm::eofbit
strm::iostate类型的值,用于指出流已经到达文件的结束符(遇到文件结束符时设置,同时还设置了failbit)
s.eof()
如果设置了流s的eofbit值,则该函数返回true
s.fail()
如果设置了流s的failbit值,则该函数返回true
s.bad()
如果设置了流s的badbit值,则该函数返回true
s.good()
如果流s处于有效状态,则该函数返回true
s.clear()
将流s中的所有状态值都重设为有效状态
s.clear(flag)
将流s中的某个指定条件状态设置为有效。flag的类型是strm::iostate
s.setstate(flag)
给流s添加指定条件。flag的类型是strm::iostate
s.rdstate()
返回流s的当前条件,返回类型为strm::iostate
流必须处于无错误状态,才能用于输入输出。检查流是否可用的最简单的方法是检查其真值。if语句直接检查流的状态,而while语句则检测条件表达式返回的流,从而间接地检查了流的状态。如果成功输入,则条件检测为true。所有流对象都包含一个条件状态成员,该成员由setstate和clear操作管理,这个状态成员为iostate类型,这是由各个iostream类分别定义的机器相关的整型,它是以二进制位(bit)的形式使用。

#include <iostream>
using namespace std;
int main(){
    int ival;
    while(cin >> ival, !cin.eof()){
        cout << cin.rdstate() <<endl;
        if(cin.bad())
            throw runtime_error("IO stream corrupted");
        if(cin.fail()){
            cerr << "bad data, try again";
            cin.clear(istream::failbit);
            continue;
        }
        cout << ival << endl;;
    }
    return 0;
}

上面的这个循环不断读入cin,直到到达文件结束符或者发生不可恢复的读取错误为止。循环条件使用了逗号操作符,对于逗号操作符来讲,首先计算它的每一个操作数,然后返回最右边操作数作为整个操作的结果。

3.输出缓冲区的管理

每个IO对象管理一个缓冲区,用于存储程序写的数据。如有下面语句:

os << "please enter a value:";
系统将字符串字面值存储在与流os关联的缓冲区中。下面几种情况将导致缓冲区内容被刷新,即写入到真实的输出设备或者文件中:

1)程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区

2)在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新

3)用操纵符显式地刷新缓冲区,例如行结束符endl、flush以及比较少用的ends(在缓冲区中插入字符null,并刷新它)

4)在每次输出操作执行完后,用unitbuf操作符设置流的内部状态,从而清空缓冲区

5)可将输出流与输入流关联(tie)起来。这时,在读入流时,将刷新其关联的输出流缓冲区

cout << "hi!" <<flush;
cout << "hi!" << ends;
cout << "hi!" << endl;
cout << unibuf << ""first" << "second"<<nounitbuf;
cout << "first" << flush << "second" << flush; 
将输入和输出绑在一起:当输入流与输出流绑在一起时,任何读输入输出流的尝试,都将首先刷新其输出流关联的缓冲区。标准库将cout与cin绑在一起。因此当执行cin >> ival;时将导致cout关联的缓冲区被刷新。tie函数可用istream或ostream对对象调用,使用一个指向ostream对象的指针形参。调用tie函数,将实参流绑在调用该函数的对象上。如果一个流调用的tie函数将其本身绑在传递给tie的ostream实参对象上,则该流上任何IO操作都会刷新实参所关联的缓冲区。
cin.tie(&cout); // illustration only: the library ties cin and coutfor us
ostream *old_tie = cin.tie();
cin.tie(0); // break tie to cout, cout no longer flushed when cin isread
cin.tie(&cerr); // ties cin and cerr, not necessarily a good idea!
// ...
cin.tie(0); // break tie between cin and cerr
cin.tie(old_tie); // restablish normal tie between cin and cout
一个ostream对象每次只能与一个istream对象绑在一起。如果在调用tie函数时传递实参0,则打破该流上已存在的捆绑。

4.文件的输入和输出

fstream头文件中定义了三种支持文件IO的类型:ifstream(提供读文件的功能)、ofstream(提供写文件的功能)以及fstream(提供读写文件的功能)。这些类型都由相应的iostream类型派生而来,fstream类型除了继承下来的行为外,还定义了两个自己的新操作-open和close,以及形参为要打开的文件名的构造函数。

迄今为止,我们的程序已经使用过标准库定义的对象:cin、cout和cerr。需要读写文件时,则必须定义自己的对象,并将它们绑定在需要的文件上。假设ifile和ofile是存储希望读写的文件名的strings对象,则可如下编写代码:

ifstream infile(ifile.c_str());
oftream outfile(ofile.c_str());
上述语句定义并打开了一对fstream对象。infile是读的流,而outfile是写的流。为ifstream或者ofstream对象提供文件名作为初始化式,就相当于打开了特定的文件。或者我们也可以先定义流对象,但不提供初始化,后面再调用open成员函数将已存在的fstream对象与特定文件绑定。为了实现读写,需要将指定的文件打开并定位,open函数完成系统指定的所有需要的操作。
ifstream infile;
ofstream outfile;
infile.open(ifile.c_str());
outfile.open(ofile.c_str());
打开文件后,通常要用if检查打开是否成功。这个操作与之前测试cin是否到达文件尾或遇到某些其他错误的条件类似。
//check that the open succeeded
if(!infile){
    cerr << "error:unable to open input file:"
             << ifile << endl;
    return -1;
}
清除文件流的状态:如果程序员要重用文件流读写多个文件,必须在读另一个文件之前调用clear清楚该流的状态,关闭流并不能改变流对象的内部状态。

文件模式:在打开文件时,无论是调用open还是以文件名作为流初始化的一部分,都需要指定文件模式(filemode)。每个fstream类都定义了一组表示不同模式的值,用于指定流打开的不同模式。与条件状态标志一样,文件模式也是整型常量,在打开指定文件时,可用位操作符设置一个或多个模式。文件流构造函数和open函数都提供了默认实参设置文件模式。默认值因流类型的不同而不同,此外还可以显式的以模式打开文件。

文件模式
模式
含义
适用对象
in
打开文件做读操作
ifstream、fstream
out
打开文件做写操作(写之前文件会被清空)
ofstream、fstream
app
在每次写之前找到文件尾(在尾部添加append)
ofstream、fstream
ate
打开文件后立即将文件定位在文件尾
istream、ofstream、fstream
trunc
打开文件时清空以存在的文件流
ofstream、fstream
binary
以二进制模式进行IO操作
istream、ofstream、fstream

对于用ofstream打开的文件,要保存文件中存在的数据,唯一的方法是显示地指定app模式。但是fstream对象既可以读也可以写它所关联的文件,此时如果我们同时以in和out打开时,所关联的文件不清空,如果指定了trunc模式,则文件一定清空。outfile2的定义使用了按位或操作符将相应的文件同时以out和trunc模式打开。

ofstream outfile1("file1");//out mode
ofstream outfile2("file1, ofstream::out | ofstream::trunc");// equivalent to out mode
ofstream appfile("file2", ofstream::app);
fstream inOut("coptOut", fstream::in | fstream::out); // open for input and output

模式是文件的属性而不是流的属性。因此只要调用open函数,就要设置文件模式,其模式的设置可以是显示的也可以是隐式的。如果没有指定文件模式,将使用默认值。

打开模式的有效组合:并不是所有的打开模式都可以同时指定。有些模式组合是没有意义的,例如同时以in和trunc模式打开文件,准备读取所生成的流,但却因为trunc操作而导致无数据可读。

文件模式的组合
模式组合
功能
out
打开文件做写操作,删除文件中已有的数据
out | app
打开文件做写操作,在文件尾写入
out | trunc
与out模式相同
in
打开文件做读操作
in | out
打开文件做读、写操作,并定位于文件开头处
int | out | trunc
打开文件做读、写操作,删除文件中已有的数据

5.字符串流

iostream标准库支持内存中的输入/输出,只要将流与存储在程序内存中的string对象捆绑起来即可。此时,可使用iostream输入输出操作读写这个string对象。标准库定义了三种类型的字符串流:istringstream(提供读string的功能)、ostringstream(提供写string的功能)以及stringstream(提供读写string的功能)。要使用这是三个类,必须包含sstream头文件。

与fstream类型一样,上述类型有iostream类型派生而来,这意味着iostream上所有的操作适用于sstream中的类型。sstream类型除了继承的操作以外,还自定义了一个有string形参的构造函数,这个构造函数将string类型的实参赋值给stringstream对象。对stringstream的读写操作实际上读写的就是该对象中的string对象。这些类还定义了名为str的成员,用来读取或设置stringstream对象所操纵的string值。

注意到尽管fstream和sstream共享相同的基类,但它们没有其他相互的关系。特别是stringstream对象不能使用open和close函数,而fstream对象则不允许使用str。

stringstream特定的操作
操作
描述
stringstream strm;
创建自由的stringstream对象
stringstream strm(s);
创建存储s的副本的stringstream对象,其中s为string类型
strm.str();
返回strm中存储的string类型的对象
strm.str(s);
将string类型的s复制给strm,返回void

下面使用getline函数从输入读取整行内容,然后为了获得每行中的单词,将一个istringstream对象与所读的行绑定起来,这样只需要使用普通的string输入操作即可读出每行中的单词。

string line, word;
while(getline(cin,line)){
    istringstream stream(line);
    while(stream >> word){
        cout << word <<endl;
    }
}
stringstream提供的转换和化:stringstream对象的一个常见的用法是,需要在多种数据类型之间实现自动格式化时使用该类型。例如, 有一个数值型数据集合,要获取它们的 string 表示形式,或反之。sstream 输入和输出操作可自动地把算术类型转化为相应的 string 表示形式,反过来也可以。

int val1=512, val2=1024,val3,val4;
ostringstream format_message;
format_message << "val1:" << val1 << "val2:" << val2 << "\n";
cout << format_message.str() <<endl;
istringstream input_istring(format_message.str());
cout << input_istring.str() <<endl;
//val1=0,val2=0;
string dump;
input_istring >> dump >> val1 >> dump >> val2;
cout  << val1 << " " << val2 << endl;
(完)

56.8K

发表评论:

© 云中布衣 2015 | Driven by EMLOG  | SiteMap | RunTime: 12.50ms&RSS  | MORE  |   | TOP

文章数量【258】 评论数量【238】 稳定运行【1208天】

Visitor IP Address【54.196.190.32】

Email:ieeflsyu#outlook.com