《第七章输入输出流2.ppt》由会员分享,可在线阅读,更多相关《第七章输入输出流2.ppt(45页珍藏版)》请在三一办公上搜索。
1、在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。所谓“文件”,一般指存储在外部介质上数据的集合。一批数据是以文件的形式存放在外部介质上的。操作系统是以文件为单位对数据进行管理的。要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据。,7.4 文件操作与文件流 7.4.1 文件的概念,外存文件包括磁盘文件、光盘文件和U盘文件。目前使用最广泛的是磁盘文件。对用户来说,常用到的文件有两大类,一类是程序文件(program file)。一类是数据文件(data file)。程序中的输入和输出的对象就是数据文件。根据文件中数据的组织形式,可分为AS
2、CII文件和二进制文件。对于字符信息,在内存中是以ASCII代码形式存放的,因此,无论用ASCII文件输出还是用二进制文件输出,其数据形式是一样的。但是对于数值数据,二者是不同的。,图7.5 C+提供低级的I/O功能和高级的I/O功能。高级的I/O功能是把若干个字节组合为一个有意义的单位,然后以ASCII字符形式输入和输出。传输大容量的文件时由于数据格式转换,速度较慢,效率不高。所谓低级的I/O功能是以字节为单位输入和输出的,在输入和输出时不进行数据格式的转换。,文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都
3、有一个内存缓冲区与之对应。请区分文件流与文件的概念。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。在C+的I/O类库中定义了几种文件类,专门用于对磁盘文件的输入输出操作。在图7.2中可以看到3个用于文件操作的文件类:,7.4.2 文件流类与文件流对象,(1)ifstream类,它是从istream类派生的。用来支持从磁盘文件的输入。(2)ofstream类,它是从ostream类派生的。用来支持向磁盘文件的输出。(3)fstream类,它是从iostream类派生的。用来支持对磁盘文件的输入输出。要以磁盘文件为对象进行输入输出,必须定义一个
4、文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者通过文件流对象从磁盘文件将数据输入到内存。其实在用标准设备为对象的输入输出中,也是要定义流对象的,如cin,cout就是流对象,C+是通过流对象进行输入输出的。,在用磁盘文件时,由于情况各异,无法事先统一定义,必须由用户自己定义。可以用下面的方法建立一个输出文件流对象:ofstream outfile;现在在程序中定义了outfile为ofstream类(输出文件流类)的对象。但是有一个问题还未解决:在定义cout时已将它和标准输出设备建立关联,而现在虽然建立了一个输出文件流对象,但是还未指定它向哪一个磁盘文件输出,需要在使用时加以
5、指定。,1.打开磁盘文件 打开文件是指在文件读写之前做必要的准备工作,包括:(1)为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。(2)指定文件的工作方式。以上工作可以通过两种不同的方法实现。a.调用文件流的成员函数open。如ofstream outfile;/定义ofstream类(输出文件流类)对象outfileoutfile.open(f1.dat,ios:out);/使文件流与f1.dat文件建立关联调用成员函数open的一般形式为,7.4.3 文件的打开与关闭,文件流对象.open(磁盘文件名,输入输出方式);磁盘文件名可以包括路径,如c:newf1.dat,
6、如缺省路径,则默认为当前目录下的文件。b.在定义文件流对象时指定参数 在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,可以在定义文件流对象时指定参数,调用文件流类的构造函数来实现打开文件的功能。如 ostream outfile(f1.dat,ios:out);一般多用此形式,比较方便。作用与open函数相同。输入输出方式是在ios类中定义的,它们是枚举常量,有多种选择,见书中表7.6。,说明:新版本的I/O类库中不提供ios:nocreate和ios:noreplace。每一个打开的文件都有一个文件指针。可以用“位或”运算符“|”对输入输出方式进行组合。如果打开操
7、作失败,open函数的返回值为0(假),如果是用调用构造函数的方式打开文件的,则流对象的值为0。,2.关闭磁盘文件 在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。如 outfile.close();/将输出文件流所关联的磁盘文件关闭 所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。此时可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入或输出。如 outfile.open(f2.dat,ios:app|ios:nocreate);此时文件流outfile与f2.dat建立关
8、联,并指定了f2.dat的工作方式。,如果文件的每一个字节中均以ASCII代码形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称字符文件)。程序可以从ASCII文件中读入若干个字符,也可以向它输出一些字符。对ASCII文件的读写操作可以用以下两种方法:(1)用流插入运算符“”输入输出标准类型的数据。(2)用本章7.2.3节和7.3.2节中介绍的文件流的put,get,geiline等成员函数进行字符的输入输出。,7.4.4 对ASCII文件的操作,例7.11 有一个整型数组,含10个元素,从键盘输入10个整数给数组,将此数组送到磁盘文件中存放。#include int a1
9、0;ofstream outfile(f1.dat,ios:out);/定义文件流对象,打开磁盘文件 if(!outfile)/如果打开失败,outfile返回0值 cerrai;outfileai;/向磁盘文件f1.dat输出数据 outfile.close();/关闭磁盘文件f1.dat return 0;,运行情况如下:enter 10 integer numbers:1 3 5 2 4 6 10 8 7 9 请注意:在向磁盘文件输出一个数据后,要输出一个(或几个)空格或换行符,以作为数据间的分隔,否则以后从磁盘文件读数据时,10个整数的数字连成一片无法区分。,例7.12 从例7.11建
10、立的数据文件f1.dat中读入10个整数放在数组中,找出并输出10个数中的最大者和它在数组中的序号。例7.13 从键盘读入一行字符,把其中的字母字符依次存放在磁盘文件f2.dat中。再把它从磁盘文件读入程序,将其中的小写字母改为大写字母,再存入磁盘文件f3.dat。,#include using namespace std;/save_to_file函数从键盘读入一行字符,并将其中的字母存入磁盘文件void save_to_file()ofstream outfile(f2.dat);/定义输出文件流对象outfile,以输出方式打开磁盘文件f2.dat if(!outfile)cerr=65
11、/关闭f2.dat,/从磁盘文件f2.dat读入字母字符,将其中的小写字母改为大写字母,再存入f3.datvoid get_from_file()char ch;ifstream infile(f2.dat,ios:in|ios:nocreate);/定义输入文件流outfile,以输入方式打开磁盘文件f2.dat if(!infile)cerropen f2.dat error!endl;exit(1);ofstream outfile(f3.dat);/定义输出文件流outfile,以输出方式打开磁盘文件f3.dat if(!outfile)cerropen f3.dat error!en
12、dl;exit(1);,while(infile.get(ch)/当读取字符成功时执行下面的复合语句 if(ch=97 运行情况如下:New Beijing,Great Olypic,2008,China.NewBeijingGreatOlypicChina(将字母写入磁盘文件f2.dat,同时在屏幕显示)NEWBEIJINGGREATOLYPICCHINA(改为大写字母),磁盘文件f3.dat的内容虽然是ASCII字符,但人们是不能直接看到的,如果想从显示器上观看磁盘上ASCII文件的内容,可以采用以下两个方法:(1)在DOS环境下用TYPE命令,如D:C+TYPE f3.dat(假设当前目
13、录是D:C+)在显示屏上会输出NEWBEIJINGGREATOLYPICCHINA如果用GCC编译环境,可选择File菜单中的DOS Shell菜单项,即可进入DOS环境。想从DOS返回GCC主窗口,从键盘输入exit即可。(2)编一程序将磁盘文件内容读入内存,然后输出到显示器。可以编一个专用函数。#include using namespace std;,void display_file(char*filename)ifstream infile(filename,ios:in|ios:nocreate);if(!infile)cerropen error!endl;exit(1);cha
14、r ch;while(infile.get(ch)cout.put(ch);coutendl;infile.close();然后在调用时给出文件名即可:int main()display_file(f3.dat);/将f3.dat的入口地址传给形参filename return 0;运行时输出f3.dat中的字符:NEWBEIJINGGREATOLYPICCHINA,二进制文件不是以ASCII代码存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。对二进制文件的操作也需要先
15、打开文件,用完后要关闭文件。在打开时要用ios:binary指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。这是和ASCII文件不同的地方。,7.4.5 对二进制文件的操作,1.用成员函数read和write读写二进制文件 对二进制文件的读写主要用istream类的成员函数read和write来实现。这两个成员函数的原型为 istream,例7.14 将一批数据以二进制形式存放在磁盘文件中。#include using namespace std;struct studentchar name20;int num;int age;cha
16、r sex;int main()student stud3=Li,1001,18,f,Fun,1002,19,m,Wang,1004,17,f;ofstream outfile(stud.dat,ios:binary);if(!outfile)cerropen error!endl;abort();/退出程序,for(int i=0;i3;i+)outfile.write(char*)执行一次write函数即输出了结构体数组的全部数据。可以看到,用这种方法一次可以输出一批数据,效率较高。在输出的数据之间不必加入空格,在一次输出之后也不必加回车换行符。在以后从该文件读入数据时不是靠空格作为数据的
17、间隔,而是用字节数来控制。,例7.15 将刚才以二进制形式存放在磁盘文件中的数据读入内存并在显示器上显示。#include using namespace std;struct studentstring name;int num;int age;char sex;int main()student stud3;int i;ifstream infile(stud.dat,ios:binary);if(!infile)cerropen error!endl;abort();,for(i=0;i3;i+)infile.read(char*)运行时在显示器上显示:NO.1name:Linum:10
18、01age:18sex:f,NO.2name:Funnum:1001age:19sex:mNO.3name:Wangnum:1004age:17sex:f请思考:能否一次读入文件中的全部数据,如infile.read(char*),2.与文件指针有关的流成员函数 在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。对于二进制文件,允许对指针进行控制,使它按用户的意图移动到所需的位置,以便在该位置上进行读写。文件流提供一些有关文件指针的成员函数。为了查阅方便,将它们归纳为书中表7.7,并作必要的说明。说明:(1)这些函数名的第一个字母或最后一个字母不是g就是p。(2)函数参数中的“文件中的
19、位置”和“位移量”已被指定为long型整数,以字节为单位。“参照位置”可以是下面三者之一:,ios:beg文件开头(beg是begin的缩写),这是默认值。ios:cur指针当前的位置(cur是current的缩写)。ios:end文件末尾。它们是在ios类中定义的枚举常量。举例如下:infile.seekg(100);/输入文件中的指针向前移到100字节位置infile.seekg(-50,ios:cur);/输入文件中的指针从当前位置后移50字节outfile.seekp(-75,ios:end);/输出文件中的指针从文件尾后移50字节,3.随机访问二进制数据文件一般情况下读写是顺序进行的
20、,即逐个字节进行读写。但是对于二进制数据文件来说,可以利用上面的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。例7.16 有5个学生的数据,要求:(1)把它们存到磁盘文件中;(2)将磁盘文件中的第1,3,5个学生数据读入程序,并显示出来;(3)将第3个学生的数据修改后存回磁盘文件中的原有位置。(4)从磁盘文件读入修改后的5个学生的数据并显示出来。,要实现以上要求,需要解决3个问题:(1)由于同一磁盘文件在程序中需要频繁地进行输入和输出,因此可将文件的工作方式指定为输入输出文件,即ios:in|ios:out|ios:binary。(2)正确计算好每次访问时指针的定
21、位,即正确使用seekg或seekp函数。(3)正确进行文件中数据的重写(更新)。可写出以下程序:#include using namespace std;struct studentint num;char name20;float score;,int main()student stud5=1001,Li,85,1002,Fun,97.5,1004,Wang,54,1006,Tan,76.5,1010,ling,96;fstream iofile(stud.dat,ios:in|ios:out|ios:binary);/用fstream类定义输入输出二进制文件流对象iofile if(!
22、iofile)cerropen error!endl;abort();for(int i=0;i5;i+)/向磁盘文件输出5个学生的数据 iofile.write(char*)/输出stud10,stud1和stud2各成员的值,coutendl;stud2.num=1012;/修改第3个学生(序号为2)的数据 strcpy(stud2.name,Wu);stud2.score=60;iofile.seekp(2*sizeof(stud0),ios:beg);/定位于第3个学生数据的开头 iofile.write(char*),运行情况如下:1001 Li 85(第1个学生数据)1004 Wa
23、ng 54(第3个学生数据)1010 ling 96(第5个学生数据)1001 Li 85(输出修改后5个学生数据)1002 Fun 97.51012 Wu 60(已修改的第3个学生数据)1006 Tan 76.51010 ling 96本程序将磁盘文件stud.dat指定为输入输出型的二进制文件。这样,不仅可以向文件添加新的数据或读入数据,还可以修改(更新)数据。利用这些功能,可以实现比较复杂的输入输出任务。请注意,不能用ifstream或ofstream类定义输入输出的二进制文件流对象,而应当用fstream类。,文件流是以外存文件为输入输出对象的数据流,字符串流不是以外存文件为输入输出的
24、对象,而以内存中用户定义的字符数组(字符串)为输入输出的对象,即将数据输出到内存中的字符数组,或者从字符数组(字符串)将数据读入。字符串流也称为内存流。字符串流也有相应的缓冲区,开始时流缓冲区是空的。如果向字符数组存入数据,随着向流插入数据,流缓冲区中的数据不断增加,待缓冲区满了(或遇换行符),一起存入字符数组。如果是从字符数组读数据,先将字符数组中的数据送到流缓冲区,然后从缓冲区中提取数据赋给有关变量。,7.5 字符串流,在字符数组中可以存放字符,也可以存放整数、浮点数以及其他类型的数据。在向字符数组存入数据之前,要先将数据从二进制形式转换为ASCII代码,然后存放在缓冲区,再从缓冲区送到字
25、符数组。从字符数组读数据时,先将字符数组中的数据送到缓冲区,在赋给变量前要先将ASCII代码转换为二进制形式。总之,流缓冲区中的数据格式与字符数组相同。文件流类有ifstream,ofstream和fstream,而字符串流类有istrstream,ostrstream和strstream。文件流类和字符串流类都是ostream,istream和iostream类的派生类,因此对它们的操作方法是基本相同的。向内存中的一个字符数组写数据就如同向文件写数据一样,但有3点不同:,(1)输出时数据不是流向外存文件,而是流向内存中的一个存储空间。输入时从内存中的存储空间读取数据。(2)字符串流对象关联的
26、不是文件,而是内存中的一个字符数组,因此不需要打开和关闭文件。(3)每个文件的最后都有一个文件结束符,表示文件的结束。而字符串流所关联的字符数组中没有相应的结束标志,用户要指定一个特殊字符作为结束符,在向字符数组写入全部数据后要写入此字符。字符串流类没有open成员函数,因此要在建立字符串流对象时通过给定参数来确立字符串流与字符数组的关联。即通过调用构造函数来解决此问题。建立字符串流对象的方法与含义如下:,1.建立输出字符串流对象ostrstream类提供的构造函数的原型为ostrstream:ostrstream(char*buffer,int n,int mode=ios:out);buf
27、fer是指向字符数组首元素的指针,n为指定的流缓冲区的大小(一般选与字符数组的大小相同,也可以不同),第3个参数是可选的,默认为ios:out方式。可以用以下语句建立输出字符串流对象并与字符数组建立关联:ostrstream strout(ch1,20);作用是建立输出字符串流对象strout,并使strout与字符数组ch1关联(通过字符串流将数据输出到字符数组ch1),流缓冲区大小为20。,2.建立输入字符串流对象istrstream类提供了两个带参的构造函数,原型为istrstream:istrstream(char*buffer);istrstream:istrstream(char*
28、buffer,int n);buffer是指向字符数组首元素的指针,用它来初始化流对象(使流对象与字符数组建立关联)。可以用以下语句建立输入字符串流对象:istrstream strin(ch2);作用是建立输入字符串流对象strin,将字符数组ch2中的全部数据作为输入字符串流的内容。istrstream strin(ch2,20);流缓冲区大小为20,因此只将字符数组ch2中的前20个字符作为输入字符串流的内容。,3.建立输入输出字符串流对象strstream类提供的构造函数的原型为strstream:strstream(char*buffer,int n,int mode);可以用以下语
29、句建立输入输出字符串流对象:strstream strio(ch3,sizeof(ch3),ios:in|ios:out);作用是建立输入输出字符串流对象,以字符数组ch3为输入输出对象,流缓冲区大小与数组ch3相同。以上3个字符串流类是在头文件strstream中定义的,因此程序中在用到istrstream,ostrstream和strstream类时应包含头文件strstream(在GCC中,用头文件strstream)。,例7.17 将一组数据保存在字符数组中。#include using namespace std;struct studentint num;char name20;f
30、loat score;int main()student stud3=1001,Li,78,1002,Wang,89.5,1004,Fun,90;char c50;/用户定义的字符数组 ostrstream strout(c,30);/建立输出字符串流,与数组c建立关联,缓冲区长30 for(int i=0;i3;i+)/向字符数组c写3个学生的数据 stroutstudi.numstudi.namestudi.score;stroutends;/ends是C+的I/O操作符,插入一个0 coutarray c:cendl;/显示字符数组c中的字符,运行时在显示器上的输出如下:array c:
31、1001Li781002Wang89.51004Fun90以上就是字符数组c中的字符。可以看到:(1)字符数组c中的数据全部是以ASCII代码形式存放的字符,而不是以二进制形式表示的数据。(2)一般都把流缓冲区的大小指定与字符数组的大小相同。(3)字符数组c中的数据之间没有空格,连成一片,这是由输出的方式决定的。如果以后想将这些数据读回赋给程序中相应的变量,就会出现问题,因为无法分隔两个相邻的数据。为解决此问题,可在输出时人为地加入空格。如,for(int i=0;iusing namespace std;int main()char c50=12 34 65-23-32 33 61 99 3
32、21 32;int a10,i,j,t;coutarray c:cendl;/显示字符数组中的字符串 istrstream strin(c,sizeof(c);/建立输入串流对象strin并与字符数组c关联,for(i=0;iai;/从字符数组c读入10个整数赋给整型数组a coutaj+1)t=aj;aj=aj+1;aj+1=t;ostrstream strout(c,sizeof(c);/建立输出串流对象strout并与字符数组c关联 for(i=0;i10;i+)stroutai;/将10个整数存放在字符数组c stroutends;/加入0 coutarray c:cendl;/显示字
33、符数组c return 0;,运行结果如下:array c:12 34 65-23-32 33 61 99 321 32(字符数组c原来的内容)array a:12 34 65-23-32 33 61 99 321 32(整型数组a的内容)array c:-32 23 12 32 33 34 61 65 99 321(字符数组c最后的内容)可以看到:(1)用字符串流时不需要打开和关闭文件。(2)通过字符串流从字符数组读数据就如同从键盘读数据一样,可以从字符数组读入字符数据,也可以读入整数、浮点数或其他类型数据。(3)程序中先后建立了两个字符串流strin和strout,与字符数组c关联。str
34、in从字符数组c中获取数据,strout将数据传送给字符数组。分别对同一字符数组进行操作。甚至可以对字符数组交叉进行读写。,(4)用输出字符串流向字符数组c写数据时,是从数组的首地址开始的,因此更新了数组的内容。(5)字符串流关联的字符数组并不一定是专为字符串流而定义的数组,它与一般的字符数组无异,可以对该数组进行其他各种操作。与字符串流关联的字符数组相当于内存中的临时仓库,可以用来存放各种类型的数据(以ASCII形式存放),在需要时再从中读回来。它的用法相当于标准设备(显示器与键盘),但标准设备不能保存数据。它比外存文件使用方便,不必建立文件(不需打开与关闭),存取速度快。但它的生命周期与其所在的模块(如主函数)相同。因此只能作为临时存储空间。,