第9章 C++的文件操作 “文件”,一般是指内存以外的存储介质上一批数据的集合。C++在语言层次上提供了文件操作的一系列函数用于完成文件的操作,打开、关闭文件,读取、写入文件数据等操作。 1。字符文件与二进制文件 字符文件:又称ASCII文件或文本TEXT文件,它是以一个字节存放一个ASCII码,代表一个字符。例如,32767需要使用5个字节表示,即: 51 50 55 54 55 (十进制表示的ASCII),而浮点数136.56需要使用6个字节表示,即49 51 54 46 53 54。
二进制文件:是指以数据在内存中存储形式原样输出(存放)到文件上去,例136 二进制文件:是指以数据在内存中存储形式原样输出(存放)到文件上去,例136.56是一个float型实数,它在内存中占有4个字节长度。将该4个字节按其在内存中的原来形式存放到文件中。无论该float型数有多大,都只占用4个字节。 ASCII形式表示 00110011 00111110 00110111 00110110 00110111 整数32767在内存 中的存储形式 3 2 7 6 7 01111111 11111111 01111111 11111111 二进制形式表示
可见用ASCII码形式输出与字符一一对应,一个字节代表一个字符,因而便于对字符进行逐个处理,也便于输出字符,缺点是占存储空间较多。二进制文件节省存储空间,而且输入输出速度快,节省时间。 2。缓冲文件系统和非缓冲文件系统 缓冲文件系统是指:系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘文件中去。如果从磁盘向内存读入数据,则一次从磁盘文件中将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序数据区。 非缓冲文件系统是指:系统不自动开辟确定大小的缓冲区,而由程序员为每个文件设定缓冲区。
输出 输入 输出文件缓冲区 程序数据区 输入文件缓冲区 文件信息区 内存 外设 文件类对象 磁盘
3。文件操作 为了调用磁盘文件,缓冲文件系统为每一个文件开辟了一个“文件信息区”,用来存放与文件操作有关的信息。 在C++中,头文件fstream.h中定义了文件类(型),包括: ifstream: 它支持从输入文件中提取数据的各种操作; ofstream:它支持把数据写入文件中的各种操作; fstream: 支持数据的输入与输出操作。 C++中文件操作的步骤 (1). 定义一个文件类(型)的对象: 方法如下: ifstream infile; ofstream outfile; fstream iofile;
(2). 打开文件:建立文件对象与磁盘文件之间的联系; (3). 对打开文件的操作:可以使用成员函数或“>>”“<<”运算符对文件进行读写操作。 (4). 文件操作完成后,应该用成员函数关闭打开的文件。 文件的打开和关闭 (1)文件的打开 (open) 所谓“打开”,实际上是在程序和操作系统之间建立起联系,通过文件的打开操作为所处理的文件定义一个文件指针,文件指针并不是直接指向所处理的文件本身,而是指向内存中用于存放文件信息的文件信息区,在文件打开后文件信息区就与该文件建立了一对一的联系。然后使用文件类所提供的成员函数(类似于成员变量),将数据写入到对应的文件中去。
打开文件的方法可以有: 定义的文件类的对象(变量)后,使用成员函数打开文件: ifstream infile; ofsream outfile; fstream file; infile.open(“myfile_in.txt”); //打开一个只读文件 outfile.open(“myfile_out.txt”); //打开一个只写文件 file.open(“myfile.txt”, ios::in); //以只读方式打开文件 在定义文件类对象时直接打开文件: ifstream infile(“myfile_in.txt”); ofstream outfile(“myfile_out.txt”); fstream file(“myfile.txt”, ios::in);
(2)文件的打开方式 在头文件ios.h中,定义了文件打开方式的枚举成员: enum open_mode{ in = 0x01, //00000001按读方式打开文件 out = 0x02, //00000010按写方式打开文件 ate = 0x04, //00000100打开文件时将文件指针移到 app = 0x08, //00001000 文件的结尾处 trunc = 0x10, //00010000 打开文件时清除原有内容 nocreate = ox20, //00100000 noreplace = 0x40, //01000000 binary = 0x80 //10000000 以二进制方式打开文件 };
每一种打开方式是以一个二进制位来表示的,所以可以用二进制位或运算“|”将允许的几种方式组合起来使用。 如: ios::out | ios::ate //打开一个输出文件并将指针移到文件尾 ios::in | ios::binary //打开一个二进制文件用于输入 ios::out | ios::trunc //打开一个输出文件并清除原有内容
枚举常量名 含义 ios::in ios::out ios::ate ios::app ios::trunc ios::nocreate 打开文件用于输入(读)操作。如果文件存在,将不被截掉,文件定位指针位于文件首,新的数据可写到文件中任何位置。 ios::out 打开文件用于输出(写)操作。如果文件存在,并且没有设置ios::app, ios::ate,ios::in值,则文件被截掉。 ios::ate 如果文件存在,将不被截掉。文件定位指针位于文件尾,新的数据可写到文件中任何位置。 ios::app 如果文件存在,将不被截掉。文件定位指针位于文件尾,新的数据附加到文件尾(不可重写已存在的文件数据)。 ios::trunc 如果文件已存在,则被截为0;如果指定ios::out,同时没有设置ios::app, ios::ate, ios::in,则该模式是隐含的。 ios::nocreate 如果文件不存在,则打开文件失败(不创建新文件)。 ios::noreplace 如果文件存在,打开时只能设置为ios::ate及ios::app方式。 ios::binary 指定文件以二进制方式打开,而不是缺省说明的文本方式。
ifstream类的成员函数open的原型为: 由文件打开方式的说明可知: ifstream类的成员函数open的原型为: open(const char *, int = ios::in, int = filebuf::openprot); 其缺省的文件打开方式为ios::in; ofstream类的成员函数open的原型为: open(const char *, int = ios::out, int = filebuf::openprot); 其缺省的文件打开方式为ios::out; fstream类的成员函数open的原型为: open(const char *, int, int = filebuf::openprot); 其缺省的文件打开方式必须指定。 (3) 文件的关闭(close) “关闭”就是使文件指针变量不指向该文件,也就是使文件指针变量与文件“脱钩”,此后不能再通过该指针对其相连的文件进行读写操作,除非再次打开。
无论是ifstream、ofstream还是fstream,关闭文件的操作都是: close( ); 如:infile.close( ), outfile.close( ), file.close( ); 关闭文件时, 系统把与该文件相关联的内存缓冲区中的数据写到文件中,收回与该文件相关的文件信息区,把文件名与文件对象之间建立的关联断开。 (4)文件操作的保护 在打开文件后,通常要判断打开是否成功。若打开成功,则文件类对象的值为非零值;若打开不成功,则其值为0。为此,打开文件的格式可以写为: ifstream f1(“file.dat”); if(!f1) { cout<<“不能打开文件:”<<“file.dat”<<‘\n’; exit(1); }
char filename[256]; cout<<“输入文件名”; cin>>filename; ofstream f5; f5.open(filename); if(!f5) { cout<<“不能打开文件:”<<“file.dat”<<‘\n’; exit(1); } (5)C++的文本文件操作 C++的三个文件类ifstream、ofstream和fstream由于继承了cin和cout的特性,因此具有与cin和cout相同的成员函数,如与cin.get(char);对应的有:infile.get(char); cin.getline(char *, int size);对应的: infile.getline(char *, int size); cout.put(char); 对应的:outfile.put(char); 此外还有用于标准输入输出设备的提取“>>”和插入“<<”运算符。
例:编写程序,读取源程序文件并复制到目的文件中去。 #include<fstream.h> #include<stdlib.h> void main(void) { char filename_in[256], filename_out[256]; cout<<“输入源文件名:"; cin>>filename_in; cout<<“输入目的文件名:"; cin>>filename_out; ifstream infile(filename_in); //ifstream infile; if(!infile){ //infile.open(filename_in); cout<<“不能打开输入文件:"<<filename_in<<'\n'; exit(-1); } ofstream outfile(filename_out); if(!outfile){ cout<<"不能打开输出文件:"<<filename_out<<'\n'; exit(-2); infile.unsetf(ios::skipws); //设置为不要跳过文件的空格 char ch; while(infile>>ch) //当到达文件结尾时,infile>>ch的返回值为0,结束循环; outfile<<ch; //否则其返回值不为0,继续循环。 infile.close( ); outfile.close( );
处理文件复制问题的另外两个方案: 方案2:(适用于任何类型的文件) char ch; //成员函数完成从源文件中字符读取, while(infile.get(ch)) //这样将不会自动跳过空格。 outfile.put(ch); //将ch中的字符写到目的文件中。 infile.close( ); outfile.close( ); 方案3:(只适用于文本文件) char buff[300]; while(infile.getline(buff,300)) //从源文件中读取一行字符,不含‘\n’ outfile<<buff<<'\n'; //字符;
(6)C++的二进制文件操作 C++的文件类对象可能通过成员函数read( )和write( )实现对二进制文件的读写操作是的,函数的原型为: read(char *buf, int size); 功能:从文件中读取由size所指定的字节数据到buf所指向的存储单元中。 write(char *buf, int size); 功能:将由buf所指向的存储单元中的size个字节数据写入到文件中。
例:产生一个二进制文件,将1~500之间的所有偶数写入文件data.dat中。 #include<fstream.h> #include<stdlib.h> void main(void) { ofstream outfile("d:\\source\\data.dat",ios::out|ios::binary); if(!outfile){ cout<<"不能打开输出文件:data.dat\n"; exit(-1); } for(int i=2;i<500;i+=2) outfile.write((char *)&i,sizeof(int)); //将整数的地址强制转换成字符型指针 outfile.close( );
例:从上例中产生的数据文件data.dat中读取二进制数据,并在显示器上按每行10个数据 的形式显示。 #include<fstream.h> #include<stdlib.h> void main(void) { ifstream infile("d:\\source\\data.dat",ios::binary); if(!infile){ cout<<"不能打开输入文件:data.dat\n"; exit(-1); } int a[250]; infile.read((char *)a,sizeof(int)*249); for(int i=0;i<249;i++){ cout<<a[i]<<'\t'; if((i+1)%10==0) cout<<'\n'; cout<<'\n'; infile.close( );
处理文件复制问题的另一个方案: 方案4: While(!infile.eof( )){ infile.read(buf, 4096); outfile.write(buf, 4096); } //eof( ):用于测试文件结束,当文件结束时,函数返回非0;否则返回值为0; 该程序可以实现任意文件类型的拷贝,包括文本文件、数据文件甚至可执行文件。
(7)文件的随机访问 在文件信息区中存在一个用于指示当前文件读取位置的指针变量,称为文件定位指针,C++语言不仅允许按文件中信息的先后顺序来进行读写,同时也允许从文件中的任何位置开始进行数据的读写操作,这种读写方式就称为文件的随机访问。C++的istream和ostream类中分别提供了几个支持文件随机访问的成员函数。分别是: istream (输入文件)类: seekg(long streampos); 将文件定位指针移动到strampos所指定的位置。 seekg(long streamoff, seek_dir); 将文件定位指针按seek_dir确定的方向移动streamoff位置; tellg( ); 返回输入文件中,文件定位指针当前的位置。
ostream (输出文件)类: seekp(long streampos); 将文件定位指针移动到strampos所指定的位置。 seekp(long streamoff, seek_dir); 将文件定位指针按seek_dir确定的方向移动streamoff位置; tellp( ); 返回输出文件中,文件定位指针当前的位置。 Seek_dir 枚举常量值 功能 ios::beg 文件开头 ios::cur 1 文件指针的当前位置 ios::end 2 文件尾
例如: infile.seekg(500); //文件定位指针移到距文件头500个字节处 infile.seekg(-100,ios::cur); //文件定位指针从当前位置前移100个字节 infile.seekg(100,ios::cur); //文件定位指针从当前位置后移100个字节 outfile.seekp(-100,ios::end); //文件定位指针从文件尾开始前移100个字节 注: 1. 前移:指文件定位指针从文件尾向文件头方向移动(streamoff为负值); 后移:指文件定位指针从文件头向文件尾方向移动; 2. 在移动文件指针时,必须保证移动后的指针值大于0且小于等于文件尾字节编号,否则将导致接着的输入输出操作失败。
例:产生一个5~1000之间的奇数文件(二进制文件),将文件中的第20~30之间的数读出后输出。 #include<fstream.h> #include<iostream.h> void main(void) { ofstream outfile(“data.dat”,ios::out|ios::binary); int i; for(i=5;i<1000;i+=2) outfile.write((char *)&i, sizeof(int)); outfile.close( ); ifstream infile(“data.dat”,ios::in|ios::binary); int x; infile.seekg(20*sizeof(int)); for(i=0;i<10;i++) { infile.read((char *)&x,sizeof(int)); cout<<x<<‘\t’; } infile.close( );
例:设在A盘上已经存在一个二进制格式的文件,其中存放了由1开始的公差为2的一批数,下面的程序判断这批数的最后一个是否是99,如果不是的话,则在文件中已有的数的基础上以相同的规律将数补充到99为止。 #include<iostream.h> #include<fstream.h> #include<stdlib.h> void main(void) { int num, i; fstream fp; fp.open("a:\\data1.txt",ios::in|ios::out|ios::binary); if(!fp) { cout<<"file can't open!"; exit(-1); } fp.seekg(-(long)sizeof(int),ios::end); fp.read((char *)&num,(long)sizeof(int)); for(i=num+2;i<=99;i+=2) fp.write((char *)&i,(long)sizeof(int)); fp.close( );
【例9.9】文本式数据文件的创建与读取数据。典型的C++数据存入文件和由文件获得数据的方法是把对象存入文件和由文件重构对象。本例对提取和插入运算符进行了重载,只用一个“>>”完成重构对象,而只用一个“<<”完成对象存入文件。 class inventory{ string Description; string No; int Quantity; double Cost; double Retail; public: inventory(string="#",string="0",int=0,double=0,double=0); friend ostream&operator<<(ostream&dist,inventory&iv); friend istream&operator>>(istream&sour,inventory&iv); }; //流类作为形式参数必须是引用
inventory::inventory(string des,string no,int quan, double cost,double ret){ Description=des; No=no; Quantity=quan; Cost=cost; Retail=ret;} ostream &operator<<(ostream&dist,inventory&iv){ dist<<left<<setw(20)<<iv.Description<<setw(10)<<iv.No; dist<<right<<setw(10)<<iv.Quantity<<setw(10)<<iv.Cost <<setw(10)<<iv.Retail<<endl; return dist; } //写入文件是自动把数转为数字串后写入 istream&operator>>(istream&sour,inventory&iv){ sour>>iv.Description>>iv.No>>iv.Quantity >>iv.Cost>>iv.Retail; return sour; } //从文件读出是自动把数字串转为数读出,函数体内>>功能不变
int main( ){ inventory car1("夏利2000","805637928",156,80000,105000),car2; inventory motor1("金城125","93612575",302,10000,13000),motor2; ofstream distfile("d:\\Ex9_9.data"); distfile<<car1<<motor1; //注意ofstream是ostream的派生类 distfile.close(); cout<<car1; cout<<motor1; cout<<car2; cout<<motor2; ifstream sourfile("d:\\Ex9_9.data"); //这样分两次打开,可避免读文件时,误改了源文件 sourfile>>car2>>motor2; sourfile.close(); return 0; }