第 14章 I/O流
14.1 C++流的概念
14.2 格式化 I/O
14.3 检测流操作的错误
14.4 文件流
14.5 字符串流
C语言中没有提供专门的输入输出语句,同
样,C++语言中也没有专门的输入 /输出( I/O)
语句,C++中的 I/O操作是通过一组标准 I/O函
数和 I/O流来实现的。 C++的标准 I/O函数是从 C
语言继承而来的,同时对 C语言的标准 I/O函数
进行了扩充。 C++的 I/O流不仅拥有标准 I/O函
数的功能,而且比标准 I/O函数功能更强、更方
便、更可靠。
14.1 C++流的概念
在 C++语言中, 数据的输入和输出 ( 简写为 I/O)
包括对标准输入设备键盘和标准输出设备显示器, 对
在外存磁盘上的文件和对内存中指定的字符串存储空
间进行输入输出这三个方面 。 对标准输入设备和标准
输出设备的输入输出简称为标准 I/O,对在外存磁盘上
文件的输入输出简称为文件 I/O,对内存中指定的字符
串存储空间的输入输出简称为串 I/O。
C++中把数据之间的传输操作称作流 。 在 C++中,
流既可以表示数据从内存传送到某个载体或设备中,
即输出流;也可以表示数据从某个载体或设备传送到
内存缓冲区变量中, 即输入流 。 在进行 I/O操作时, 首
先打开操作, 使流和文件发生联系, 建立联系后的文
件才允许数据流入或流出, 输入或输出结束后, 使用
关闭操作使文件与流断开联系 。
C++为实现数据的输入和输出定义了一个庞大的类
库, 它包括的类主要有 ios,istream,ostream,iostream,
ifstream,ofstream,fstream,istrstream,ostrstream,
strstream等 。 其中 ios为根基类, 它直接派生四个类:
输入流类 istream,输出流类 ostream,文件流基类
fstreambase和字符串流基类 strstreambase。 输入文件流
类同时继承了输入流类和文件流基类 ( 当然对于根基
类是间接继承 ), 输出文件流类 ofstream同时继承了输
出流类和文件流基类, 输入字符串流类 istrstream同时
继承了输入流类和字符串流基类, 输出字符串流类
ostrstream同时继承了输出流类和字符串流基类, 输入
输出流类 iostream同时继承了输入流类和输出流类, 输
入输出文件流类 fstream同时继承了输入输出流类和文
件流基类, 输入输出字符串流类 strstream同时继承了输
入输出流类和字符串流基类 。
C++ 系 统 中 的 I/O 类库, 其 所 有 类 被 包 含 在
iostream.h,fstream.h和 strstrea.h这三个系统头文件中,
各头文件包含的类如下:
iostream.h包含有,ios,iostream,istream,
ostream,iostream_withassign,istream_withassign,
ostream_withassign等 。
fstream.h包含有,fstream,ifstream,ofstream和
fstreambase,以及 iostream.h中的所有类 。
strstrea.h包含有,strstream,istrstream,ostrstream
和 strstreambase,以及 iostream.h中的所有类。
在一个程序或一个编译单元(即一个程序文件)
中当需要进行标准 I/O操作时,则必须包含头文件
iostream.h,当需要进行文件 I/O操作时,则必须包含头
文件 fstream.h,同样,当需要进行串 I/O操作时,则必
须包含头文件 strstrea.h。在一个程序或编译单元中包含
一个头文件的命令格式为,#include<头文件名 >”,当
然若头文件是用户建立的,则头文件名的两侧不是使
用尖括号,而是使用双引号。当系统编译一个 C++文
件对 #include命令进行处理时,是把该命令中指定的文
件中的全部内容嵌入到该命令的位置,然后再编译整
个 C++文件生成相应的目标代码文件。
C++不仅定义有现成的 I/O类库供用户使用,而且
还为用户进行标准 I/O操作定义了四个类对象,它们分
别是 cin,cout,cerr和 clog,其中 cin为
istream_withassign流类的对象,代表标准输入设备键盘,
也称为 cin流或标准输入流,后三个为
ostream_withassign流类的对象,cout代表标准输出设备
显示器,也称为 cout流或标准输出流,cerr和 clog含义
相同,均代表错误信息输出设备显示器。因此当进行
键盘输入时使用 cin流,当进行显示器输出时使用 cout
流,当进行错误信息输出时使用 cerr或 clog。
C++的流通过重载运算符, <<”和, >>”执行输入
和输出操作 。 输出操作是向流中插入一个字符序列,
因此, 在流操作中, 将运算符, <<”称为插入运算符 。
输出操作是从流中提取一个字符序列, 因此, 将运算
符, >>”称为提取运算符 。
1,cout
在 ostream输出流类中定义有对左移操作符 <<重载
的一组公用成员函数, 函数的具体声明格式为:
ostream& operator<<(简单类型标识符);
简单类型标识符除了与在 istream流类中声明右移
操作符重载函数给出的所有简单类型标识符相同以外,
还增加一个 void* 类型,用于输出任何指针(但不能是
字符指针,因为它将被作为字符串处理,即输出所指
向存储空间中保存的一个字符串)的值。由于左移操
作符重载用于向流中输出表达式的值,所以又称为插
入操作符。如当输出流是 cout时,则就把表达式的值
插入到显示器上,即输出到显示器显示出来。
2,cin
在 istream输入流类中定义有对右移操作符 >>重载
的一组公用成员函数, 函数的具体声明格式为:
istream& operator>>( 简单类型标识符 &) ;
简单类型标识符可以为 char,signed char,
unsigned char,short,unsigned short,int,unsigned
int,long,unsigned long,float,double,long
double,char*,signed char*,unsigned char*之中的
任何一种,对于每一种类型都对应着一个右移操作符
重载函数。由于右移操作符重载用于给变量输入数据
的操作,所以又称为提取操作符,即从流中提取出数
据赋给变量。
3,cerr
cerr类似标准错误文件 。 cerr与 cout的差别在于:
( 1) cerr是不能重定向的;
( 2) cerr不能被缓冲, 它的输出总是直接传达到
标准输出设备上 。
错误信息是写到 cerr的项 。 即使在各种其他输出语
句中, 如果使用下列语句, 则错误信息, Error”总能保
证在显示器上显示出来:
cerr <<,Error” <<,\n”;
4,clog
clog是不能重定向的, 但是可以被缓冲 。 在某些系
统中, 由于缓冲, 使用 clog代替 cerr可以改进显示速度:
clog <<,Error” <<,\n”;
14.2 格式化 I/O
14.2.1 ios类中的枚举常量
在根基类 ios中定义有三个用户需要使用的枚举类型,
由于它们是在公用成员部分定义的, 所以其中的每个枚举
类型常量在加上 ios::前缀后都可以为本类成员函数和所有外
部函数访问 。 在三个枚举类型中有一个无名枚举类型, 其
中定义的每个枚举常量都是用于设置控制输入输出格式的
标志使用的 。 该枚举类型定义如下:
enum {skipws,left,right,internal,dec,oct,hex,showbase,showpoint,
uppercase,showpos,scientific,fixed,unitbuf,stdio
};
各枚举常量的含义如下:
( 1) skipws
利用它设置对应标志后, 从流中输入数据时跳过
当前位置及后面的所有连续的空白字符, 从第一个非
空白字符起读数, 否则不跳过空白字符 。 空格, 制表
符 ‘ \t’,回车符 ‘ \r’和换行符 ‘ \n’统称为空白符 。 默
认为设置 。
( 2) left,right,internal
left在指定的域宽内按左对齐输出,right按右对齐
输出,而 internal使数值的符号按左对齐、数值本身按
右对齐输出。域宽内剩余的字符位置用填充符填充。
默认为 right设置。在任一时刻只有一种有效。
( 3) dec,oct,hex
设置 dec对应标志后, 使以后的数值按十进制输
出, 设置 oct后按八进制输出, 而设置 hex后则按十六进
制输出 。 默认为 dec设置 。
( 4) showbase
设置对应标志后使数值输出的前面加上, 基指示
符,, 八进制数的基指示符为数字 0,十六进制数的基
指示符为 0x,十进制数没有基指示符 。 默认为不设置,
即在数值输出的前面不加基指示符 。
( 5) showpoint
强制输出的浮点数中带有小数点和小数尾部的无
效数字 0。默认为不设置。
( 6) uppercase
使输出的十六进制数和浮点数中使用的字母为大
写 。 默认为不设置 。 即输出的十六进制数和浮点数中
使用的字母为小写 。
( 7) showpos
使输出的正数前带有正号, +”。 默认为不设置 。
即输出的正数前不带任何符号 。
( 8) scientific,fixed
进行 scientific设置后使浮点数按科学表示法输出,
进行 fixed设置后使浮点数按定点表示法输出。只能任
设其一。缺省时由系统根据输出的数值选用合适的表
示输出。
( 9) unitbuf,stdio
这两个常量很少使用, 所以不予介绍 。
在 ios中定义的第二个枚举类型为:
enum open_mode {in,out,ate,app,trunc,nocreate,
noreplace,binany};
其中的每个枚举常量规定一种文件打开的方式,
在定义文件流对象和打开文件时使用 。
在 ios中定义的第三个枚举类型为:
enum seek_dir {beg,cur,end};
其中的每个枚举常量用于对文件指针的定位操作
上。
14.2.2 ios类中的成员函数
ios类提供成员函数对流的状态进行检测和进行输入输
出格式控制等操作, 每个成员函数的声明格式如下:
int bad( ) ; int eo( ) ;
int fail( ) ; void clear( ) ;
char fill( ) ; char fill( char c) ;
long flags( ) ; long flags( long f) ;
int good( ) ; int precision( ) ;
int rdstate( ) ; int precision( int n) ;
int width( ) ; long setf( long f) ;
int width( int w) ; long unsetf( long f) ;
因为所有 I/O流类都是 ios的派生类,所以它们的对
象都可以调用 ios类中的成员函数和使用 ios类中的格式
化常量进行输入输出格式控制。下面以标准输出流对
象 cout为例说明输出的格式化控制。
例 14-1
14.2.3 格式控制操作符
数据输入输出的格式控制还有更简便的形式,就
是使用系统头文件 iomanip.h中提供的操纵符。使用这
些操纵符不需要调用成员函数,只要把它们作为插入
操作符 <<(个别作为提取操作符 >>)的输出对象即可。
这些操纵符及功能如下:
dec oct hex ws
endl ends flush
setiosflags( long f) resetiosflags( long f)
setfill( int c) setprecision( int n)
setw( int w)
在上面的操纵符中,dec,oce,hex,endl,ends,
flush和 ws除了在 iomanip.h中有定义外,在 iostream.h中
也有定义。所以当程序或编译单元中只需要使用这些
不带参数的操纵符时,可以只包含 iostream.h文件,而
不需要包含 iomanip.h文件。
下面以标准输出流对象 cout为例, 说明使用操作符
进行的输出格式化控制 。
例 14-4:
14.3 检测流操作的错误
在 I/O流的操作过程中可能出现各种错误, 每一个
流都有一个状态标志字, 以指示是否发生了错误以及
出现了哪种类型的错误, 这种处理技术与格式控制标
志字是相同的 。 ios类定义了以下枚举类型:
enum io_state
{
goodbit =0x00,//不设置任何位, 一切正常
eofbit =0x01,//输入流已经结束, 无字符可读入
failbit =0x02,//上次读 /写操作失败, 但流仍可使用
badbit =0x04,//试图作无效的读 /写操作, 流不再可用
hardfail=0x80 //不可恢复的严重错误
};
对应于这个标志字各状态位, ios类还提供了以下
成员函数来检测或设置流的状态:
int rdstate(); //返回流的当前状态标志字
int eof(); //返回非 0值表示到达文件尾
int fail(); //返回非 0值表示操作失败
int bad(); //返回非 0值表示出现错误
int good(); //返回非 0值表示流操作正常
int clear(int flag=0); //将流的状态设置为 flag
为提高程序的可靠性,应在程序中检测 I/O流的操
作是否正常。当检测到流操作出现错误时,可以通过
异常处理来解决问题。
14.4 文件流
14.4.1 文件的概念
在磁盘上保存的信息是按文件的形式组织的,每
个文件都对应一个文件名,并且属于某个物理盘或逻
辑盘的目录层次结构中一个确定的目录之下。一个文
件名由文件主名和扩展名两部分组成,它们之间用圆
点(即小数点)分开,扩展名可以省略,当省略时也
要省略掉前面的圆点。文件主名是由用户命名的一个
有效的 C++标识符,为了同其他软件系统兼容,一般
让文件主名为不超过 8个有效字符的标识符,同时为了
便于记忆和使用,最好使文件主名的含义与所存的文
件内容相一致。
文件扩展名也是由用户命名的,1至 3个字符组成
的、有效的 C++标识符,通常用它来区分文件的类型。
如在 C++系统中,用扩展名 h表示头文件,用扩展名 cpp
表示程序文件,用 obj表示程序文件被编译后生成的目
标文件,用 exe表示连接整个程序中所有目标文件后生
成的可执行文件。对于用户建立的用于保存数据的文
件,通常用 dat表示扩展名,若它是由字符构成的文本
文件则也用 txt作为扩展名,若它是由字节构成的、能
够进行随机存取的内部格式文件则可用 ran表示扩展名。
在 C++程序中使用的保存数据的文件按存储格式分
为两种类型,一种为字符格式文件,简称字符文件,
另一种为内部格式文件,简称字节文件。字符文件又
称 ASCII码文件或文本文件,字节文件又称二进制文件。
在字符文件中,每个字节单元的内容为字符的 ASCII码,
被读出后能够直接送到显示器或打印机上显示或打印
出对应的字符,供人们直接阅读。在字节文件中,文
件内容是数据的内部表示,是从内存中直接复制过来
的。当然对于字符信息,数据的内部表示就是 ASCII码
表示,所以在字符文件和在字节文件中保存的字符信
息没有差别,但对于数值信息,数据的内部表示和
ASCII码表示截然不同,所以在字符文件和在字节文件
中保存的数值信息也截然不同。
要在程序中使用文件时,首先要在开始包含
#include<fstream.h>命令。由它提供的输入文件流类
ifstream、输出文件流类 ofstream和输入输出文件流类
fstream定义用户所需要的文件流对象,然后利用该对
象调用相应类中的 open成员函数,按照一定的打开方
式打开一个文件。文件被打开后,就可以通过流对象
访问它了,访问结束后再通过流对象关闭它。
14.4.2 文件的打开与关闭
流可以分为 3类:输入流, 输出流以及输入 /输出
流, 相应地必须将流说明为 ifstream,ofstream以及
fstream类的对象 。 例如:
ifstream ifile; //说明一个输入流
ofstream ofile; //说明一个输出流
fstream iofile; //说明一个输入 /输出流
说明了流对象之后, 可使用函数 open( ) 打开文
件 。 文件的打开即是在流与文件之间建立一个连接 。
open( ) 的函数原型为:
void open(const char * filename,int mode,int prot=filebuf::openprot);
其中, filename是文件名字, 它可包含路径说明 。
mode说明文件打开的模式, 它对文件的操作影响
重大, mode的取值必须是以下值之一:
ios::in 打开文件进行读操作
ios::out 打开文件进行写操作
ios::ate 打开时文件指针定位到文件尾
ios::app 添加模式, 所有增加都在文件尾部
进行
ios::trunc 如果文件已存在则清空原文件
ios::nocreate 如果文件不存在则打开失败
ios::noreplace 如果文件存在则打开失败
ios::binary 二进制文件(非文本文件)
对于 ifstream流, mode的默认值为 ios::in;对于
ofstream流, mode的默认值为 ios::out。
prot决定文件的访问方式, 取值为:
0 普通文件
1 只读文件
2 隐含文件
4 系统文件
一般情况下,该访问方式使用默认值。
与其他状态标志一样,mode的符号常量可以用位
或运算,|”组合在一起,如 ios::in|ios::binary表示以只读
方式打开二进制文件。
注意:打开文件操作并不能保证总是正确的, 如
文件不存在, 磁盘损坏等原因可能造成打开文件失败 。
如果打开文件失败后, 程序还继续执行文件的读 /写操
作, 将会产生严重错误 。 在这种情况下, 应使用异常
处理以提高程序的可靠性 。
如果使用构造函数或 open()打开文件失败,流
状态标志字中的 failbit,badbit或 hardbit将被置为 1,并
且在 ios类中重载的运算符“!”将返回非 0值。通常可
以利用这一点检测文件打开操作是否成功,如果不成
功则作特殊处理。
每个文件流类中都提供有一个关闭文件的成员函
数 close( ), 当打开的文件操作结束后, 就需要关闭
它, 使文件流与对应的物理文件断开联系, 并能够保
证最后输出到文件缓冲区中的内容, 无论是否已满,
都将立即写入到对应的物理文件中 。 文件流对应的文
件被关闭后, 还可以利用该文件流调用 open成员函数
打开其他的文件 。
关闭任何一个流对象所对应的文件, 就是用这个
流对象调用 close( ) 成员函数即可 。 如要关闭 fout流所
对应的 a:\xxk.dat文件, 则关闭语句为:
fout.close();
14.4.3 文件的读写
1,文件读写方法
( 1) 使用流运算符直接读写 。
文件的读 /写操作可以直接使用流的插入运算符
,<<”和提取运算符, >>”,这些运算符将完成文件的
字符转换工作 。
( 2) 使用流成员函数
常用的输出流成员函数为,put函数, write函数
常用的输入流成员函数如下,get函数, getline函
数, read函数
2,文本文件的读写
文本文件只适用于那些解释为 ASCII码的文件。处
理文本文件时将自动作一些字符转换,如输出换行字
符 0x0A时将转换为回车 0x0D与换行 0x0A两个字符存入
文本文件,读入时也会将回车与换行两个字符合并为
一个换行字符,这样内存中的字符与写入文件中的字
符之间就不再是一一对应关系。文本文件的结束以
ASCII码的控制字符 0x1A表示。
3,二进制文件的读写
二进制文件不同于文本文件, 它可用于任何类型
的文件 ( 包括文本文件 ), 读写二进制文件的字符不
作任何转换, 读写的字符与文件之间是完全一致的 。
一般地,对二进制文件的读写可采用两种方法:
一种是使用 get()和 put();另一种是使用 read()
和 write()。
4,文件的随机读写
( 1) 输出流随机访问函数 。
输出流随机访问函数有 seekp和 tellp。
( 2) 输入流随机访问函数 。
输入流随机访问函数有 seekg和 tellg。
14.5 字符串流
字符串流类包括输入字符串流类 istrstream,输出
字符串流类 ostrstream和输入输出字符串流类 strstream
三种。它们都被定义在系统头文件 strstrea.h中。只要在
程序中带有该头文件,就可以使用任一种字符串流类
定义字符串流对象。每个字符串流对象简称为字符串
流。
三种字符串流类的构造函数声明格式分别如下:
istrstream(const char* buffer);
ostrstream(char* buffer,int n);
strstream(char* buffer,int n,int mode);
对字符串流的操作方法通常与对字符文件流的操
作方法相同。