C++系统流库的I/O操纵算子
dec,hex、oct、setbase(int n)(设置基数)、ws(提出空白字符)、ends(插入一个nul字符)、endl(插入一个新行并刷新流)、flush(刷新流)、resetiosflags(long)等等。
ios类的错误性质通常由下列标志指示:
goodbit
没有位置位,状态为正常状态
eofbit
到达文件末尾
failbit
I/O操作失败
badbit
试图进行非法操作
hardfail
致命性错误
有几个函数可用来检查一个流对象的当前状态,它们都是ios类的成员函数:
int rdstate();
返回当前的流状态字
int eof();
如果提取操作已到达文件尾,则返回非零值
int fail();
若failbit位置位,返回非零值
int bad();
若badbit位置位,返回非零值
int good();
若状态字没有位置位,返回非零值
8.1 创建文件流
操作系统中另一个常配置的设备是文件设备(磁盘、磁带等),在文件设备上可建立一个文件,对文件进行I/O操作,在对文件处理完后还应关闭文件。
C++系统通过对流类进一步扩展,提供了支持文件I/O的能力,这使得程序员在建立和使用文件时,就象使用cin和cout一样方便。图8—1表示扩展后处理文件I/O的类等级,新派生的五个类用于文件处理。
Filebuf类是streambuf的派生类,它提供了对文件缓冲区管理的能力,我们一般不涉及这个类。Fstreambase类提供了文件处理所需要的全部成员函数,在它的派生类中没有提供新的成员函数;ifstream类用于文件的输入操作;而ofstream类用于文件的输出操作;fstream类允许对文件在两个方向上进行操作,这几个类也同时继承了前面介绍的流类的基本类等级中定义的成员函数。
在使用这些类时,必须在程序中嵌入文件fstream.h。
8.1.1 文件的打开和关闭
通过打开一个文件,可以将一个流与一个文件相联结。一个文件在打开时,必须指定文件打开方式。在ios类中定义的一组枚举常量名给出了可允许的文件打开方式,如表8—1所示。
表8—1 文件的打开方式常量名
含义
in
打开一个文件进行读操作
out
打开一个文件进行写操作
ate
文件打开时将文件指针定位于文件末尾
app
添加,向文件输出的内容都添加到文件尾部
trunc
如果文件已经存在,将其长度截断为0,并清除原有内容
nocreate
如果文件不存在,则打开操作失败
noreplace
如果文件存在,除非设置ios::ate或ios::app,否则打开失败
binary
指定文件以二进制方式打开,缺省时为文本方式
除ios::app方式之外,文件刚打开时,指示当前读写位置的文件指针定位于文件的开始位置,而ios::app使文件当前的写指针定位于文件尾。
在打开一个文件时,若指定方式ios::out,而未指定ios::ate或ios::app,则隐含方式为ios::trunc。
可以将几种方式通过“或”操作结合起来,例如,打开一个供读和写的文件,其方式可定义为ios::in|ios::out.
文本文件和二进制文件最根本的区别在于进行I/O操作时对‘\n’字符的解释方式。在C++中,这个字符表示的ASCII码是0X0A的字符(换行)。当文件以文本方式打开时,流类在向文件缓冲区中插入字符时,凡遇到代码为0X0A的字符,都将其扩展为两个字符,即0X0D和0X0A。反之,当从流中提取一个字符时,当流类遇到字符0X0D时,流类都将它和其后的字符0X0A合并为一个字符‘\n’。当文件以二进制方式打开时,所有的字符都按一个二进制字节处理,不再对0X0A字符作变换处理。
可以有两种方法以一定方式打开一个文件,一种方式是在建立对象时使用构造函数将一个文件和这个流对象联结起来。在ifstream类、ofstream类和fstream类中各定义有一个构造函数,它们是:
ifstream::ifstream(char *,int=ios::in,int =filebuf::openport);
ofstream::ofstream(char *,int=ios::out,int =filebuf::openport);
fstream::fstream(char *,int,int =filebuf::openport);
在这三个构造函数中,第一个参数表示要联结的文件名,第二个参数指定文件打开的方式,ifstream类和ofstream类的构造函数提供了缺省值。最后一个参数指定文件的保护方式,这个值和具体的操作系统有关,我们一般只使用缺省指定的filebuf::openport值,它是在filebuf类中定义的一个公有的静态数据成员。
例8_1:将一个文件的内容拷贝到另一个文件EX8_1.CPP。
这个程序在遇到文件结束标志时终止。当流对象input和output的生命期结束时,它们的析构函数分别关闭和这两个对象相联系的文件。可以使用从fstreambase类继承的成员函数:
void fstream::close();
来关闭文件流。例如,可以在EX8_1.CPP程序中的while语句之后增加下面两条语句:
input.close();
output.close();
当使用成员函数close()关闭一个文件流之后,反映流状态的状态字被清零,并且使一个流对象和文件相脱离。
可以使用成员函数open()将一个流对象和一个文件相联结,成员函数open在fstreambase类中定义,在它的派生类中被重定义为:
void ifstream::open(char *,int=ios::in,int =filebuf::openport);
void ofstream::open(char *,int=ios::out,int =filebuf::openport);
void fstream::open(char *,int,int =filebuf::openport);
成员函数open的参数及其意义和前面介绍的相应类的构造函数的参数完全相同。
有时,在建立一个流对象时,并不知道这个流对象和那个文件相联系,这时可以使用ifstream类、ofstream类和fstream类定义的缺省构造函数建立一个流对象,当以后需要将这个流对象和一个文件相联系时,可以再使用函数open()。
例8_2:从一个整数组成的文件中读入一个整数,然后将其扩大一倍之后,存入另一个文件(EX8_2.CPP)。
8.1.2 在流内随机移动
istream类中提供了三个成员函数,用于在输入流内随机移动文件的当前指针,它们的原型是:
istream& istream::seekg(streampos);//character g stand for get
istream& istream::seekg(streamoff,seek_dir);
istream& istream::tellg();//返回当前文件指针的位置。
其中streampos和streamoff在iostream.h中使用typedef定义为:
typedef long streampos;
typedef long streamoff;
seekdir是一个枚举类型,它在ios类中定义为:
enum seek_dir{beg=0;cur=1;end=2};
下面解释成员函数:
istream& istream::seekg(streamoff,seek_dir);
其中,seek_dir规定了在输入流中移动文件指针时节参照位置,streamof指定了相对于该参照位置的偏移量。Seek_dir的值具有的意义如表8—2所示。
表8—2 seek_dir值的意义符号
含义
cur
相对于文件指针的当前位置
beg
相对于文件的开始位置
end
相对于文件的结束位置
例如:input.seekg(-10,ios::cur)
intput seekg(-10,ios::end)
注意:不能将文件指针移到文件开始之前,也不能将文件指针移到文件结束标志之后。
Istream类使用tellg()和seekg()所管理的文件指针称为读指针。
ostream类也定义三个成员函数用来管理写指针,它们的原型是:
ostream& ostream::seekp(streampos);//character p stand for put
ostream& ostream::seekp(streamoff,seek_dir);
ostream& ostream::tellp();//返回当前文件指针的位置。
这三个成员函数的含义和对应的istream类中的成员函数的含义类似。
例8_3:文件的随机访问EX8_1.CPP。
注意:1、当进行文件随机访问时,文件的打开方式最好指定为二进制方式,以防止将值为0X0A的字符转换成0X0D和0X0A两个字符,影响程序对文件的正确定位。
2、对于键盘、显示终端以及磁带这样的设备是不能进行随机访问的。
8.1.3 将输出流系到输入流上仍可以使用ios类中定义的成员函数tie将一个输出流系到一个输入流上,例如:
ifstream in(“infile”);
ofstream out(“outfile”);
in.tie(&out);
在以后从输入流in提取数据之前,系统首先自动刷新输出流out的缓冲区。程序员也可以使用ostream类中定义的成员函数flush()来刷新一个流的缓冲区,如:out.flush();或使用操纵算子flush,如:out<<flush;
8.1.4 指向流的指针
下面这个程序是EX8_1.CPP的修订版,它在用户只在命令行给出一个参数的情况下,将这个命令行参数所指定的文件的内容拷贝到屏幕上。
例8_4:EX8_4.CPP。
由于在ios类中将赋值和初始化语句说明为私有的,这就使得两个流对象之间不能进行赋值和初始化操作,因而流对象不能用作函数的参数或返回值。但可将一个流对象的指针或引用作为函数的参数或返回类型是可以的,而且也应该这样使用。
从istream类、ostream类和iostream类中公有派生了三个类:
class istream_withassign:public istream
class ostream_withassign:public ostream
class iostream_withassign:public iostream
在这三个派生的类中分别定义了赋值操作
istream_withassign& istream_withassign::operator=(istream&);
ostream_withassign& ostream_withassign:,operator=(ostream&);
iostream_withassign& iostream_withassign:,operator=(iostream&);
cin被定义为istream_withassign类的对象,cout\cerr和clog都是ostrem_withassign类的对象,所以这四个预定义的流可以用于赋值操作。
8.1.5 流类的派生用法
本节将说明建立流类的派生类,以扩充流类适应于具体的应用环境。这个程序通过重载下标运算符建立一个虚拟文件数组,使在程序中对文件的使用就旬对内存中的数组的使用一样。
考虑一个int类型的数组
int array[10];
当我们引用数组中的一个元素array[i]时,C++编译器按表达式*(array+i)来引用这个元素,数组名array作为基准地址。如果将这个数组实现为一个文件,则基准地址就是文件的开始位置。
使用下标运算符引用的一个数组元素既可以用作左值,又可以用作右值,因此,当重载下标运算符操作文件时,必须注意对这种情况的处理。设建立的文件数组的类名为filearray,从下面的程序片段:
int I;
filearray a;
a[1]=100;
i=a[1];
可以重载赋值运算符,用于将一个数据作为数组的元素写入到文件中,而当从文件数组读入一个元素时,我们就不能重载赋值运算符,但在将a[1]置给一个变量i时,它的类型发生了变化,因此我们可以定义一个类型转换函数,用于从文件数组中读一个元素。下面给出相应的程序。
例8_5:EX8_5.CPP。