第 6章 I/O流类库
第 6章 I/O流类库
?流类库及其结构
?流的格式化控制
?流的出错处理
?文件流操作
流类库及其结构
?从文件到流
?流类库结构
?定义流对象
?流对象 cin和 cout的操作
从文件到流
? C语言是用文件系统作为程序与输入 /输出设备间交
互的接口,所有的输入 /输出操作都抽象为对文件指
针的操作和直接对文件的读写
? C++提供了更高抽象层次的输入 /输出机制,它用流
( Stream)作为程序的系统调用接口
? 流确定了数据的生产者与数据的消费者之间的联系:
从流中获取数据,称为提取操作;把数据送入流中,
称为插入操作
? 流包含了四层概念:流类库、流类、流对象和流类
成员
流类库结构
ios
ostream istream
iostream
ifstream
istream_withassign
istrstream
filebuf
strstreambuf
stdiostreambuf
fstream
strstream
stdiostream
iostreambuf
ofstream
ostream_withassign
ostrstream
Iostream_init
基本类
?·streambuf类:用以管理流的缓冲区
?·ios类:提供格式、错误检测和状态信息;
?·ostream类:用来创建输出流;
?·istream类:用来创建输入流;
?·iostream类:用来创建输入/输出流
流类库中的类及其对应的头文件
头 文 件 类 名 含 义
iostream.h ios 流抽象类,I/O在用户方的接口,提供基本 I/O高层操作,包含一个指向 streambuf的指针
streambuf 抽象流缓冲区类,I/O在物理设备方的接口,具有缓冲区起始地址、读写指针、缓冲区读
写操作,
istream 通用输入流类,提供各种输入方式和提取操作 —— 从缓冲区取字符
ostream 通用输出流类,提供各种输出方式和插入操作 —— 向缓冲区存字符
iostream istream和 ostream的组合
istream_withassign 标准输入流类
ostream_withassign 标准输出流类
iostream_initifstream 预定义的流初始化类
fstream.h ifstream 输入文件流类
ofstream 输出文件流类
fstream 输入 /输出文件流类
filebuf 磁盘文件的流缓冲区类
strstrea.h istrstream 输入字符串流类
ostrstream 输出字符串流类
strstream 输入 /输出字符串流类
strstreambuf 字符串流缓冲区类
stdiostr.h stdiostream 标准 I/O文件的输入 /输出类
stdiostreambuf 标准 I/O文件的流缓冲区类
定义流对象
? 在 iostream.h文件中,把 4个预定义的开放流声明为
withassign类的对象,
? extern istream_withassign cin;
? extern ostream_withassign cout;
? extern ostream_withassign cerr;
? extern ostream_withassign clog;
? · cin是 istream-withassign类的对象,称为标准输入流,缺省
时键盘为数据源,也可以重定向为其他设备;
? · cout是 ostream-withassign类的对象,称为标准输出流,缺
省时显示器为数据池,也可以重定向为其他设备;
? · cerr和 clog是 ostream-withassign类的对象,称为标准错误
输出流,固定关联到显示器
流对象 cin和 cout的操作
?流对象 cin和 cout的操作主要由定义它们的类
istream和 ostream的成员函数决定。了解了
这两个类定义的成员函数,就会知道流对象
cin和 cout可以进行的主要操作
类 istream的定义
?1)提取运算符,>>”定义在 istream类中,并
且对所有的预定义类型都给出了其重载定义
?2)对于 istream类中定义的其他成员函数,
流对象可以使用分量运算符引用,即采用格
式,
cin, 成员函数名 ()
例 6.1.1
static filebuf * stdin_filebuf;
stdin_filebuf = new filebuf(F_stdin);
// F_stdin:标准输入所用的文件描述字
cin = stdin_filebuf;
// 将缓冲区与 cin相连
cin.tie(& cout);
// 将 cin与 cout联系起来
例 6.1.2
#include <iostream.h>
void main()
{
int i=0;
char buf[20];
while(i<20 && cin.get(buf[i]) && buf[i]!='\n')
i++;
buf[i]='\0';
cout<<buf<<endl;
}
例 6.1.3
#include <iostream.h>
void main()
{
int c;
while((c=cin.get())!=EOF);
cout<<"Ok!\n";
}
类 ostream的定义
?1)插入运算符,<<”定义在 ostream类中,并
且对所有的预定义类型都给出了其重载定义
?2)对于 ostream类中定义的其他成员函数,
流对象可以使用分量运算符引用
例 6.1.4
#include <iostream.h>
void main()
{
int fun();
cout<<"The value of function fun is,"<< fun()<< endl;//(1)
}
int fun()
{
cout << "Sorry,I am first! \n";
return 250;
}
例 6.1.5
#include <iostream.h>
#include <string.h>
void main()
{
char *s="string";
for(int i=0;i<strlen(s);i++)
cout.put(*(s+i));
cout.put('\n');
}
例 6.1.6
#include <iostream.h>
void main()
{
char * s="This is a string.";
cout.write(s+10,3);
cout.put('\n');
}
流的格式化控制
?使用 ios类定义的格式化成员函数
?使用 I/O操纵算子
使用 ios类定义的格式化成员函数
?数据格式可以有:跳过空白、左对齐、右对
齐、填充字符进制转换、表示形式等
?对不同的需求,常常要采用它们的某种组合
?为了有效地表示进行格式控制,C++采用一
个 long int类型( 16位)的字作为格式状态字
ios类中定义的几个用于格式化 I/O的
成员函数
函 数 原 型 功 能
long ios::setf(long flags); 设置格式标志 flags
long ios::unsetf(long flags); 清除格式标志,并返回前标志
long ios::flags(); 测试格式标志
long ios::flags(long flags); 设置标志 flags,并返回前标志
int ios::width(int w); 指定最小域宽 w
int ios::precision(int p); 设置浮点数的小数位数 p
char ios::fill(char ch); 指定填充字符 ch
ios::flags函数
#include <iostream.h>
void showflags(long f) // 输出格式状态字函数
{ for(long i = 0x4000; i; i = i >>1) // 将 "1"逐位右移,直至全 "0"
cout << ((i&f)?"1":"0");
cout << endl;
}
void main()
{ const int i = 156;
char c1 = 'a',c2 = 'b';
showflags(cout.flags()); // 测试当前格式状态
cout << "i = " << i << ",c1=" << c1 << ",c2=" << c2 << endl;
showflags(cin.flags(cin.flags() & ~ios::skipws)); // 清 skipws
cin >> c1 >> c2;
showflags(cin.flags());
cout << "i = " << i << ",c1=" << c1 << ",c2=" << c2 << endl;
showflags(cout.flags(cout.flags() | ios::uppercase | ios::hex)); // 设置十六进制基且符号大写
showflags(cout.flags());
cout << "i = "<< i << ",c1 = "<< c1 << ",c2 = " << c2 << endl;
}
说明
?a) showflags()是一个输出格式状态字函数
?b)设 flags为当前格式状态字
?c)带参 flags函数先返回当前格式状态字,再
设置新的格式状态字
?d)输入流与输出流有不同的格式状态字
ios::setf()函数与 ios::unsetf()函数
#include <iostream.h>
void showflags(long f)
{
for(long i=0x4000;i;i=i>>1)
cout<<((i&f)?"1":"0");
cout<<endl;
}
void main()
{
const int i=156;
char c1='a',c2='b';
showflags(cout.flags());
cout<<"i="<<i<<",c1="<<c1<<",c2="<<c2<<endl;
showflags(cin.setf(0,ios::skipws)); // 清 skipws
cin>>c1>>c2;
showflags(cin.flags());
cout<<"i="<<i<<",c1="<<c1<<",c2="<<c2<<endl;
showflags(cout.unsetf(ios::dec)); // 清 dec
showflags(cout.setf(ios::hex)); // 设置 hex
showflags(cout.flags());
cout<<"i="<<i<<",c1="<<c1<<",c2="<<c2<<endl;
}
int ios::width(int)函数
#include <iostream.h>
void main()
{
int w;
char buffer1 [10],buffer2 [10];
w = cin.width (3);
cin >> buffer1 >> buffer2;
cout.width (5);
cout << buffer1 << ',' << buffer2 << "\r\n";
}
int ios::precision(int)函数
#include <iostream.h>
void main()
{ char name[3][15];
float salary[3];
for(int i=0;i<3;i++)
{ cout<<"name:";
cin>>name[i];
cout<<"salary:";
cin>>salary[i];
}
cout<<"123456789012345678901234567890\n";
int w;
for(int j=0;j<3;j++)
{ w = cout.width(15); // 设置域宽
cout.setf(ios::left,ios::adjustfield); // 域中左对齐
cout<<name[j];
w = cout.width(10); // 设置域宽
cout.setf(ios::right,ios::adjustfield); // 域中右对齐
cout.setf(ios::fixed,ios::floatfield); // 浮点数定点格式
cout.precision(2); // 小数点后两位
cout.fill('$'); // 用 '$'填充
cout<<salary[j]<<endl;
cout.fill(' '); // 清除填充字符
}
}
使用 I/O操纵算子
?预定义的 I/O操纵算子
?自定义 I/O操纵算子
预定义的 I/O操纵算子
?每个操纵算子有一个流引用参数并返回一个
指向同一流的引用,因而可以嵌入到插入或
提取表达式中来改变流的状态。这些操纵算
子的原型说明一部分(无参的)定义或声明
在 iostream.h中,一些(有参的)定义或声明
在头文件 iomanip.h中
C++预定义的操纵算子
操纵算子 功 能
dec I/O:格式为十进制数据
endl O:插入一个换行符并刷新此流
ends O:插入一个空字符
flush O:刷新一个流
hex I/O:格式为十六进制数据
oct I/O:格式为八进制数据
ws I:跳过开头的空白符
resetiosflags(long f) I/O:关闭 f声明的格式化标志
setbase(int base) O:设置数据的基指示符为 base
setfill(int ch) I/O:设置填充符为 ch
setiosflags(long f) I/O:设置 f声明的格式化标志
setprecision(int p) I/O:设置数据显示小数点后 p位
setw(int w) I/O:设置域宽为 w
例 6.2.6
#include <iostream.h>
#include <iomanip.h>
void main()
{ char name[3][15];
float salary[3];
for(int i=0;i<3;i++)
{ cout<<"name:";
cin>>name[i];
cout<<"salary:";
cin>>salary[i];
}
cout<<"123456789012345678901234567890\n";
for(int j=0;j<3;j++)
{ cout<<resetiosflags(ios::right)<<setiosflags(ios::left)<<setfill(' ')<<setw(15)<<name[j];
cout<<resetiosflags(ios::left)<<setiosflags(ios::right)<<setw(10)<<setiosflags(ios::fixed)
<<setprecision(2)<<setfill('$')<<salary[j]<<endl;
}
}
自定义 I/O操纵算子
?C++允许程序员自定义 I/O操纵算子,把程序
中频繁使用的 I/O操作集成,使 I/O密集的程序
变得更加清晰高效,并可以避免意外错误。
I/O操纵算子用函数来实现。下面从有无参数
两个方面分别介绍自定义 I/O操纵算子的基本
方法
自定义无参 I/O操纵算子
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
char *password="Hello!manipulator.";
char pw[30];
istream & getpass(istream & stream) // 定义无参操纵算子 getpass
{
cout<<"\a"; // 响铃
cout<<"Enter password:";
stream>>pw;
if(! strcmp(password,pw)) // 比较
return stream;
cout<<"Illegal user!\n";
exit(1);
}
int main()
{
cin>>getpass; // 引用 getpass
cout<<"Login complete!\n";
return 0;
}
说明
?a)操纵算子返回 stream是一个关键,否则操
纵算子就不能用在流的 I/O操纵序列中
?b)所谓无参是指操纵算子无参数,而不是指
函数无参
?c)上述框架是自定义提取(输入)操纵算子
的函数框架,当把 istream换为 ostream时,
即成为自定义插入(输出)操纵算子的函数
框架
自定义带参 I/O操纵算子
? 带参 I/O操纵算子要由类 SMANIP,OMANIP,IMANIP
或 IOMANIP创建。这四个类分别对应着类 ios,
ostream,istream与 iostream。由于它们定义在头
文件 iomanip.h中,所以自定义带参的 I/O操纵算子
时,必须包含头文件 iomanip.h
? 此外,定义一个带参的 I/O操纵算子必须创建两个重
载的操纵算子函数:一个定义由参数决定的操作;
一个使用 iomanip.h定义的某个类的构造函数
例 6.2.9
#include <iomanip.h>
ostream & stars (ostream & s,int n)
{
for(int i=0;i<n;i++)
s<<'*';
return s;
}
OMANIP(int) stars(int n)
{
return OMANIP(int)(stars,n);
}
void main()
{
cout<<"begin"<<stars(8)<<"end";
}
流的出错处理
?流的出错状态
?测试与设置出错状态位的 ios成员函数
流的出错状态
?为了在流操作过程中指示流是否发生了错误
以及出现错误的类型,C++在 ios类中定义了
一个枚举类型 io_state,用其每个元素描述出
错误状态字的一个出错状态位
?一个流处在一个出错状态时,所有对该流的
I/O请求都将被忽略,直到错误情况被纠正并
把出错状态位清除为止
测试与设置出错状态位的 ios成员函数
成员函数 功 能
int rdstate(); 返回当前出错状态字
void clear(int i=0); 置出错位为 i,例如 str.clear(ios::failbit| str.rdstate());置流 str中的 failbit位,
不影响其它位
int good(); 若出错状态字没有置位,则返回非 0
int fail(); 若 failbit被置位,则返回非 0
int bad(); 若 badbit被置位,则返回非 0
int eof(); 若 istream的 eofbit被置位,则返回非 0
int operator!(); 若 failbit,badbit或 hardbit被置位,则返回非 0
int operator void*(); 若 failbit,badbit或 hardbit被置位,则返回 0
文件流操作
?文件流
?文件的打开与关闭
?文本文件的读/写
?二进制文件的读/写
?文件的随机读/写
?设备文件的使用
文件流
? 在 C/C++中,文件有两个层次的概念:一个是广义
的,泛指与 I/O设备和与主机交换数据,另一个是狭
义的,单指被存储数据的集合。这里讨论的是后者
? C++的 I/O操作是基于流的,进行文件 I/O,必须创
建与之相应的文件流,
·文件输入流;
·文件输出流;
·文件输入/输出流。
有关文件流的类层次结构
ios
ostream istream
iostream
ifstream filebuf fstream
iostreambuf
ofstream
ios
fstreambase
文件
? 按照存储属性,可以将文件分为文本文件和二进制
文件两种。严格地讲,在计算机内部数据都是以二
进制形式存放的。所以分为文本文件和二进制文件,
是机器对数据的解释方式不同
? 文本文件将数据解释为字符序列( ASCII代码),
存取以字符为单位进行,然后再将字符转换为二进
制码( ASCII代码)存储
? 二进制文件的存取单位是字节
? 二进制文件比文本文件具有较高的存储效率
文件的打开与关闭
? 流是 C++程序建立的一种输入 /输出机制,而文件是一种数据
载体,要让流作用于文件,必须建立流与文件之间的连接。
建立这种连接的过程称为文件的打开
? 打开文件的具体操作就是把磁盘目录区中指定文件的 FCB读
到相应的文件缓冲区中,以控制文件的读写,同时建立一个
相应的流对象与文件缓冲区相连接
? 关闭文件就是把文件的 FCB中已改变内容,如文件长度,写
回到磁盘中的文件目录中,以便下次使用;对流状态字清零
并切断流对象与文件之间的联结
文件的参数
? 1) 文件名:磁盘设备与控制台设备不同的一点是,
一个设备中可以存储多个文件
? 2) 文件的存储位置:为了便于在磁盘上找到所需的
文件,每个文件名都有一个对应的地址
? 3) 文件长度与文件指针:文件指针指示了文件操作
的位置
? 4) 文件的存储模式:文本文件还是二进制文件
? 5) 文件的存取属性
? 6 )磁盘中的各文件的 FCB被集中在 0磁道上,形成
文件的目录区
打开文件的方法
?用成员函数 open()在 C++程序中,文件的打
开操作用函数 open()显式地进行
?用构造函数(创建对象)
open()函数
? open()函数是类 fstreambase的成员函数,又在其派生类
ifstream,ofstream,fstream中被重定义。它的原型为,
void open(const char * filename,int filemode,int =
filebuf,,openprot);
? 其中,
· 参数 filename 为文件名,包含文件路径。
· 参数 filebuf::openprot是文件的存取属性,一般缺省。
· 参数 filemode 为文件的打开模式。文件的打开模式对文件
的操作影响重大,为此 ios定义了一些枚举常量来描述文件打
开的基本模式
例 6.4.1
#include <fstream.h>
void main()
{
char filename[8];
ifstream input; // 创建输入流
ofstream output; // 创建输出流
cerr<<"Enter the input filename:";
cin>>filename;
input.open(filename); // 打开方式隐含为 in
cerr<<"Enter the output filename:";
cin>>filename;
output.open(filename); // 打开方式隐含为 out
int number;
while(input>>number) // 读输入文件
output<<10*number;// 写输出文件
}
用构造函数打开文件
? 在 ifstream类,ofstream类和 fstream类中各有一个
构造函数,
ifstream::ifstream(char *,int = ios::in,int =
filebuf::openprot);
ofstream::ofstream(char *,int = ios::out,int =
filebuf::openprot);
fstream::fstream(char *,int,int = filebuf::openprot);
? 这里,三个参数意义与前面介绍的 open()函数的三
个参数的意义完全相同,只是在创建流对象的同时
可以打开一个相应的文件
例 6.4.2
#include <fstream.h>
#include <stdlib.h>
void main(int argc,char *argv[])
{ if(argc!=3)
{ cerr<<"Error:usage:prog file1 file2<CR>"<<endl;
exit(1);
}
ifstream input(argv[1]);
if(!input)
{ cerr<<"Can't open file"<<argv[1]<<endl;
exit(1);
}
ofstream output(argv[2]);
if(!output)
{ cerr<<"Can't open file"<<argv[2]<<endl;
exit(1);
}
int number;
while(input>>number)
output<<10*number;
}
关闭文件
? 当与文件相连接的流对象的生命期结束时,它们的
释放函数将关闭与这些流对象相联结的文件。另外,
也可以使用 close()函数显式地关闭文件。 close()函
数的原型为
void fstreambase::close();
? 它是一个无参且不须指定返回值的成员函数。在
ex651与 ex652中也可以增加如下两个语句
input.close();
output.close();
文本文件的读/写
?一个文件被打开后,就与对应的流连接起来
了。这时文件的读操作,就是从流中提取一
个元素,文件的写操作就是向流中插入一个
元素。只要建立了与文件相连接的流,向文
本文件的读/写,就会像控制台 I/O一样方便
例 6.4.3
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main()
{
ofstream out("SALRBOOK"); // 创建输出流并打开帐文件
if(!out)
{
cerr<<"Can't open SALARYBOOK file.\n";
exit(1);
}
out<<"Zhang "<<556.55<<endl;
out<<"Wang "<<444.44<<endl;
out<<"Li "<<333.33<<endl;
out.close();
return 0;
}
查工资帐程序
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main()
{ ifstream in("SALRBOOK"); // 创建输入流并打开帐
文件
if(!in)
{ cerr<<"Can't open SALARYBOOK file.\n";
exit(1);
}
char name[20];
float salary;
cout.precision(2);
in>>name>>salary;
cout<<name<<","<<salary<<"\n";
in>>name>>salary;
cout<<name<<","<<salary<<"\n";
in>>name>>salary;
cout<<name<<","<<salary<<"\n";
in,close();
return 0;
}
二进制文件的读/写
?C++的 I/O流是基于字符的,其默认的存储模
式是文本方式。二进制文件与文本文件对数
据的解释及存储形式不同,要往二进制文件
中写数据,应将每个字节的内容当作一个字
符去写
向二进制文件写一个 float数
px = &x float x
内存 Write(&x,4)
文件
*px *( px+2) *( px+1) *( px+3)
例 6.4.4
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
class bfostream,public ofstream
{
public,
bfostream(const char * fn)
,ofstream(fn,ios::out | ios::binary){ }
void wrBytes(const void *,int);
bfostream & operator << (float);
};
inline bfostream & bfostream::operator << (float d)
{
wrBytes( & d,sizeof(d));
return * this;
}
int main(int argc,char * argv[])
{
bfostream bfout(argv[1]); // 文件名从命令行程序名后输入
if( ! bfout)
{
cerr << "Error,filename missing !\n";
exit(1);
}
float x = 2.71828;
bfout << x;
bfout << 3.14156;
x = 34.567E-8;
bfout << x;
cout << "1 file created!\n";
return 0;
}
void bfostream::wrBytes(const void * p,int len)
{
if (! p) return;
if ( len <= 0 ) return;
write ((char *) p,len);
}
文件的随机读/写
? 在缺省的情况下,文件以写方式打开时,文件指针
总是指向文件尾;以读方式打开时,文件指针总是
指向文件首,并且总是每读几个字符,文件指针后
移几个字符位置
? 这样的读/写操作是很被动的,为了增加对文件访
问的灵活性,C++在 istream类及 ostream类中定义
了与在输入/输出流中随机移动文件指针相关的三
对成员函数,
·文件指针的随机偏移
·测试文件指针当前位置
·文件指针的绝对位移
文件指针的随机偏移
?为移动一个文件的指针,要指明以下三点,
?1) 流对象名
?2) 偏移量 文件指针偏移是文件指针相对于当
前位置向前或向后移动一个偏移量 ─字节数
?3) 偏移参照位置
测试文件指针当前位置
?在与输入流/输出流中相联的文件中测试文
件指针当前位置,分别采用 tellg()/ tellp()函
数。它们的原型分别为,
stream istream::tellg();
stream ostream::tellp();
文件指针绝对位移
?文件指针绝对位移是给出文件指针的绝对地
址。在C ++中,把文件指针的绝对地址定义
为 streampos型。定义如下,
typedef long streampos;
?相对于输入流/输出流的一对使文件指针按
绝对地址位移的成员函数的原型为,
istream & istream::seekg (streampos);
ostream & ostream::seekp (streampos);
例 6.4.5
#include <fstream.h>
void main()
{
fstream f("MYDATA",ios::in | ios::out | ios::binary);
int i;
for(i=0;i<10;i++) // 写 10个二进制位无格式整数
f.write((char *)&i,sizeof(int));
streampos pos=f.tellp(); // 记下当前写指针位置
for(i=10;i<20;i++) // 再写 10个二进制位无
格式整数
f.write((char *)&i,sizeof(int));
f.seekg(pos); // 将读指针定位到 pos位置
f.read((char *)&i,sizeof(int)); // 读数据
cout<<"The data stored is:"<<i<<endl;
f.seekp(0,ios::beg); // 移写指针到文件首
for(i=100;i<120;i++) // 重写文件
f.write((char *)&i,sizeof(int));
f.seekg(pos); // 再将读指针定位到 pos位置
f.read((char *)&i,sizeof(int)); //读数据
cout<<"The data stored is:"<<i<<endl;
}
设备文件的使用
?操作系统下的重定向
?程序中的使用
操作系统下的重定向
?当用户根据需要改变标准流时,要利用操作
系统提供的重定向技术
?系统默认的打印机流名为 PRN,所以命令
C>prg1>PRN
将以打印机为标准输出设备。
标准错误输出不可以重定向,它一定要输出
到屏幕上。
程序中的使用
? 由于设备也是一种文件,在 C++程序中,可以通过
创建与 I/O设备相联系的流,来使用设备。如语句
ofstream prt("LPT1");
将创建一个与打印机相联系的流对象 prt,相当于打
开文件,LPT1”的操作
注意,这里使用的赋值号是在 istream的派生类
istream_withassign和 ostream的派生类
ostream_withassign中重载了的
习题