C++学习笔记(24)——文件IO

上篇笔记中总结的输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。磁盘是计算机的外部存储器,它能够长期保留信息,能读能写,可以刷新重写,方便携带,因而得到广泛使用。

一、文件

文件(file)是程序设计中一个重要的概念。所谓“文件”,一般指存储在外部介质上数据的集合。一批数据是以文件的形式存放在外部介质(如磁盘、光盘和U盘)上的。操作系统是以文件为单位对数据进行管理的,也就是说,如果想找存在外部介质上的数据,必须先按文件名找到所指定的文件,然后再从该文件中读取数据。要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据。

对用户来说,常用到的文件有两大类,一类是程序文件(program file),如C++的源程序文件(.cpp)、目标文件(.obj)、可执行文件(.exe)等。一类是数据文件(data file), 在程序运行时,常常需要将一些数据(运行的最终结果或中间数据)输出到磁盘上存放起来,以后需要时再从磁盘中输入到计算机内存。这种磁盘文件就是数据文件。程序中的输入和输出的对象就是数据文件。

根据文件中数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件又称文本(text)文件或字符文件,它的每一个字节放一个ASCII代码,代表一个字符。二进制文件又称内部格式文件或字节文件,是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。

对于字符信息,在内存中是以ASCII代码形式存放的,因此,无论用ASCII文件输出还是用二进制文件输出,其数据形式是一样的。但是对于数值数据,二者是不同的。例如有一个长整数100000,在内存中占4个字节,如果按内部格式直接输出,在磁盘文件中占 4个字节,如果将它转换为ASCII码形式输出,则要占6个字节。

用ASCII码形式输出的数据是与字符一一对应的,一个字节代表一个字符,可以直接在屏幕上显示或打印出来。这种方式使用方便,比较直观,便于阅读,便于对字符逐个进行输入输出。但一般占存储空间较多,而且要花费转换时间(二进制形式与ASCII码间的转换)。用内部格式(二进制形式)输出数值,可以节省外存空间,而且不需要转换时间,但一个字节并不对应一个字符,不能直接显示文件中的内容。如果在程序运行过程中有些中间结果数据暂时保存在磁盘文件中,以后又需要输入到内存的,这时用二进制文件保存是最合适的。如果是为了能显示和打印以供阅读,则应按ASCII码形式输出。此时得到的是ASCII文件,它的内容可以直接在显示屏上观看。

二、文件流和缓冲区

文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。

请区分文件流与文件的概念,不用误以为文件流是由若干个文件组成的流。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。

三、文件输入输出类和流对象

在C++的I/O类库中定义了几种文件类,专门用于对磁盘文件的输入输出操作。下图中可以看到除了标准输入输出流类istream、ostream和iostream类外,还有3个用于文件操作的文件类:

http://c.biancheng.net/cpp/uploads/allimg/140527/1-14052GP142452.png

•    ifstream类,它是从istream类派生的,用来支持从磁盘文件的输入。

•    ofstream类,它是从ostream类派生的,用来支持向磁盘文件的输出。

•    fstream类,它是从iostream类派生的,用来支持对磁盘文件的输入输出。

要以磁盘文件为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者通过文件流对象从磁盘文件将数据输入到内存。

其实在用标准设备为对象的输入输出中,也是要定义流对象的,如cin、cout就是流对象,C++是通过流对象进行输入输出的。由于cin、cout已在iostream中事先定义,所以用户不需自己定义。在用磁盘文件时,由于情况各异,无法事先统一定义,必须由用户自己定义。此外,对磁盘文件的操作是通过文件流对象(而不是cin和cout)实现的。文件流对象是用文件流类定义的,而不是用istream和ostream类来定义的。可以用下面的方法建立一个输出文件流对象:

ofstream outfile;

如同在头文件iostream中定义了流对象cout —样,现在在程序中定义了outfile为 ofstream类(输出文件流类)的对象。但是有一个问题还未解决:在定义 cout 时已将它和标准输出设备(显示器)建立关联,而现在虽然建立了一个输出文件流对象,但是还未指定它向哪一个磁盘文件输出,需要在使用时加以指定。

四、打开文件

所谓打开(open)文件是一种形象的说法,如同打开房门就可以进入房间活动一样。 打开文件是指在文件读写之前做必要的准备工作,包括:

•为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。

•指定文件的工作方式,如,该文件是作为输入文件还是输出文件,是ASCII文件还是二进制文件等。

以上工作可以通过两种不同的方法实现。

1) 调用文件流的成员函数open。如

ofstream outfile;  //定义ofstream类(输出文件流类)对象outfile

outfile.open("f1.dat",ios::out);  //使文件流与f1.dat文件建立关联

第2行是调用输出文件流的成员函数open打开磁盘文件f1.dat,并指定它为输出文件,文件流对象outfile将向磁盘文件f1.dat输出数据。ios::out是I/O模式的一种,表示以输出方式打开一个文件。或者简单地说,此时f1.dat是一个输出文件,接收从内存输出的数据。

调用成员函数open的一般形式为:

文件流对象.open(磁盘文件名, 输入输出方式);

磁盘文件名可以包括路径,如"c:\new\\f1.dat",如缺省路径,则默认为当前目录下的文件。

2) 在定义文件流对象时指定参数

在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,可以在定义文件流对象时指定参数,调用文件流类的构造函数来实现打开文件的功能。如

ofstream outfile("f1.dat",ios::out);

一般多用此形式,比较方便。作用与open函数相同。

输入输出方式是在ios类中定义的,它们是枚举常量,有多种选择,见表4。

表4 文件输入输出方式设置值

方 式

作用

ios::in

以输入方式打开文件

ios::out

以输出方式打开文件(这是默认方式),如果已有此名字的文件,则将其原有内容全部清除

ios::app

以输出方式打开文件,写入的数据添加在文件末尾

ios::ate

打开一个已有的文件,文件指针指向文件末尾

ios: :trunc

打开一个文件,如果文件已存在,则删除其中全部数据,如文件不存在,则建立新文件。如已指定了 ios::out 方式,而未指定ios: :app,ios::ate,ios: :in,则同时默认此方式

ios:: binary

以二进制方式打开一个文件,如不指定此方式则默认为ASCII方式

ios::in l ios::out

以输入和输出方式打开文件,文件可读可写

ios:: out | ios::binary

以二进制方式打开一个输出文件

ios::in l ios::binary

以二进制方式打开一个输入文件

几点说明:

1) 每一个打开的文件都有一个文件指针,该指针的初始位置由I/O方式指定,每次读写都从文件指针的当前位置开始。每读入一个字节,指针就后移一个字节。当文件指针移到最后,就会遇到文件结束EOF(文件结束符也占一个字节,其值为-1),此时流对象的成员函数eof的值为非0值(一般设为1),表示文件结束了。

2) 可以用“位或”运算符“|”对输入输出方式进行组合,如表4中最后3行所示那样。还可以举出下面一些例子:

3) 如果打开操作失败,open函数的返回值为0(假),如果是用调用构造函数的方式打开文件的,则流对象的值为0。可以据此测试打开是否成功。如

if(outfile.open("f1.bat", ios::app) ==0)

   cout <<"open error";

if( !outfile.open("f1.bat", ios::app) )

   cout <<"open error";

五、关闭磁盘文件

在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。如

outfile.close( );  //将输出文件流所关联的磁盘文件关闭

所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。

六、文件读写

1 ASCII文件读写

如果文件的每一个字节中均以ASCII代码形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称字符文件)。程序可以从ASCII文件中读入若干个字符,也可以向它输出一些字符。

对ASCII文件的读写操作可以用以下两种方法:

a) 用流插入运算符“<<”和流提取运算符“>>”输入输出标准类型的数据。“<<”和“ >>”都巳在iostream中被重载为能用于ostream和istream类对象的标准类型的输入输出。由于ifstream和ofstream分别是ostream和istream类的派生类,因此它们从ostream和istream类继承了公用的重载函数,所以在对磁盘文件的操作中,可以通过文件流对象和流插入运算符“<<”及流提取运算符“>>”实现对磁盘 文件的读写,如同用cin、cout和<<、>>对标准设备进行读写一样。

b) 用文件流的put、get、geiline等成员函数进行字符的输入输出,前面已介绍,请查看:用C++流成员函数put输出单个字符、C++ get()函数读入一个字符和C++ getline()函数读入一行字符。

2用二进制方式读写

二进制文件不是以ASCII代码存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。

对二进制文件的操作也需要先打开文件,用完后要关闭文件。在打开时要用ios::binary指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。这是和ASCII文件不同的地方。

用成员函数read和write读写二进制文件

对二进制文件的读写主要用istream类的成员函数read和write来实现。这两个成员函数的原型为

istream& read(char *buffer,int len);

ostream& write(const char * buffer,int len);

字符指针buffer指向内存中一段存储空间。len是读写的字节数。调用的方式为:

a. write(p1,50);

b. read(p2,30);

上面第一行中的a是输出文件流对象,write函数将字符指针p1所给出的地址开始的50个字节的内容不加转换地写到磁盘文件中。在第二行中,b是输入文件流对象,read 函数从b所关联的磁盘文件中,读入30个字节(或遇EOF结束),存放在字符指针p2所指的一段空间内。

七、与文件指针有关的流成员函数

在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。在输入时每读入 一个宇节,指针就向后移动一个字节。在输出时每向文件输出一个字节,指针就向后移动 一个字节,随着输出文件中字节不断增加,指针不断后移。对于二进制文件,允许对指针进行控制,使它按用户的意图移动到所需的位置,以便在该位置上进行读写。文件流提供 一些有关文件指针的成员函数。为了查阅方便,将它们归纳为表5,并作必要的说明。

表5 文件流与文件指针有关的成员函数

成员函数

作 用

gcount()

返回最后一次输入所读入的字节数

tellg()

返回输入文件指针的当前位置

seekg(文件中的位置)

将输入文件中指针移到指定的位置

seekg(位移量, 参照位置)

以参照位置为基础移动若干字节

tellp()

返回输出文件指针当前的位置

seekp(文件中的位置)

将输出文件中指针移到指定的位置

seekp(位移量, 参照位置)

以参照位置为基础移动若干字节

几点说明:

1) 这些函数名的第一个字母或最后一个字母不是g就是p。带 g的是用于输入的函数(g是get的第一个字母,以g作为输入的标识,容易理解和记忆), 带p的是用于输出的函数(P是put的第一个字母,以P作为输出的标识)。例如有两个 tell 函数,tellg用于输入文件,tellp用于输出文件。同样,seekg用于输入文件,seekp用于输出文件。以上函数见名知意,一看就明白,不必死记。

如果是既可输入又可输出的文件,则任意用seekg或seekp。

2) 函数参数中的“文件中的位置”和“位移量”已被指定为long型整数,以字节为单位。“参照位置”可以是下面三者之一:

ios::beg  文件开头(beg是begin的缩写),这是默认值。

ios::cur  指针当前的位置(cur是current的缩写)。

ios::end  文件末尾。

它们是在ios类中定义的枚举常量。举例如下:

infile.seekg(100);  //输入文件中的指针向前移到字节位置

infile.seekg(-50,ios::cur);  //输入文件中的指针从当前位置后移字节

outfile.seekp(-75,ios::end);  //输出文件中的指针从文件尾后移字节

 

 

发布了76 篇原创文章 · 获赞 63 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/bjtuwayne/article/details/89480062