C++ primer plus 第17 章 输入、输出和文件:文件输入和输出04:随机存取
17.4.6 随机存取
程序清单17.20 random.cpp
文章目录
17.4.6 随机存取
在最后一个文件示例中,将探讨随机存取。随机存取指的是直接移动(不是依次移动)到文件的任何位置。随机存取常被用于数据库文件,程序维护一个独立的索引文件,该文件指出数据在主数据文件中的位置。这样,程序便可以直接跳到这个位置,读取(还可能修改)其中的数据。如果文件由长度相同的记录组成,这种方法实现起来最简单。每条记录表示一组相关的数据。例如,在程序清单17.19的示例中,每条文件记录将表示关于特定行星的全部数据。很自然,文件记录对应于程序结构或类。我们将以程序清单 17.19中的二进制文件程序为基础,充分利用planet 结构为文件了记录模式,来创建这个例子。为使编程更具创造性,该示例将以读写模式打开文件,以便能够读取和修改记录。为此,可以创建一个 fstream 对象。fstream 类是从 iostream 类派生而来的,而后者基于istream 和 ostream 两个类,因此它继承了它们的方法。它还继承了两个缓冲区,一个用于输入,一个用于输出,并能同步化这两个缓冲区的处理。也就是说,当程序读写文件时,它将协调地移动输入缓冲区中的输入指针和输出缓冲区中的输出指针。
该示例将完成以下工作:
1.显示planets.dat 文件当前的内容;
2.询问要修改哪条记录:
3.修改该记录;
4.显示修改后的文件。
更复杂的程序将使用菜单和循环,使得能在操作列表中不断地进行选择。但这里的版本只能执行每种操作一次。这种简化让您能够检验读写文件的多个方面,而不陷入程序设计事务之中。
警告:这个程序假设 planets.dat 文件已经存在,该文件是由程序清单 17.19 中的 binary.cpp 程序创建的。
要回答的第一个问题是:应使用哪种文件模式。为读取文件,需要使用ios base::in 模式。为执行二进制 I0,需要使用 ios base:binary 模式(在某些非标准系统上,可以省略这种模式,事实上,可能必须省略这种模式)。为写入文件,需要ios base:out或ios base:app 模式。然而,追加模式只允许程序将数据添加到文件尾,文件的其他部分是只读的:也就是说,可以读取原始数据,但不能修改它:要修改数据,必须使用 ios base:out。表 17.8 表明,同时使用in 模式和 out 模式将得到读/写模式,因此只需添加二进制元素即可。如前所述,要使用运算符来组合模式。因此,需要使用下面的语句:
finout.open(file,ios base::inios | base::outios | base::binary)
接下来,需要一种在文件中移动的方式。fstream类为此继承了两个方法:seekg()和seekp(),前者将输入指针移到指定的文件位置,后者将输出指针移到指定的文件位置(实际上,由于fstrea 类使用缓冲区来存储中间数据,因此指针指向的是缓冲区中的位置,而不是实际的文件)。也可以将seekg()用于 ifstream对象,将seekp()用于ofream对象。下面是seekg()的原型:
basic_istream<charT,traits>& seekq(off_type, ios base::seekdir);
basic_istream<charT,traits>& seekg(pos_type);
正如您看到的,它们都是模板。本章将使用char 类型的模板具体化。对于char 具体化,上面两个原型等同于下面的代码:
istream &seekg(streamoff,ios base::seekdir);
istream & seekq(streampos);
第一个原型定位到离第二个参数指定的文件位置特定距离(单位为字节)的位置;第二个原型定位到离文件开头特定距离(单位为字节)的位置。
类型升级在C++早期,scckg()方法比较简单。StrcamoП和strcampos类型是一些标准整型(如long)的typedef但为创建可移植标准,必须处理这样的现实情况:对于有些文件系统,整数参数无法提供足够的信息,因此 sircamoT和 sireampos 允许是结构或类类型,条件是它们允许一些基本的操作,如使用整数值作为初始值等。随后,老版本的istream类被 basic istrcam模板取代,strcampos和strcamof被 basic istrcam 模板取代。然而,strcampos和streamo继续存在,作为pos_lype和ofTtype的char 的具体化。同样,如果将seekg()用于wistream对象,可以使用wstreampos和wslreamof类型。
来看 seekg()的第一个原型的参数。streamo㎡值被用来度量相对于文件特定位置的偏移量(单位为字节)。streamo㎡ 参数表示相对于三个位置之一的偏移量为特定值(以字节为单位)的文件位置(类型可定义为整型或类)。seck dir 参数是ios basc类中定义的另一种整型,有3个可能的值。常量ios base::beg 指相对于文件开始处的偏移量。常量iosbase::cur指相对于当前位置的偏移量;常量ios_base::end 指相对于文件尾的偏移量。下面是一些调用示例,这里假设n是一个ifstream对象:
fin.seekg(30,ios base::beg);//30 bytes beyond the beginning
fin.seekg(-1,ios base::cur);//back up one byte
fin.seekg(0,ios base::end);//go to the end of the file
下面来看第二个原型。streampos 类型的值定位到文件中的一个位置。它可以是类,但如果是这样的话,这个类将包含一个接受 strcamo参数的构造函数和一个接受整数参数的构造函数,以便将两种类型转换为streampos值。streampos值表示文件中的绝对位置(从文件开始处算起)。可以将streampos 位置看作是相对于文件开始处的位置(以字节为单位,第一个字节的编号为0)。因此下面的语句将文件指针指向第112个字节,这是文件中的第113个字节:
fin.seekg(112);如果要检査文件指针的当前位置,则对于输入流,可以使用tellg()方法,对于输出流,可以使用tellp()方法。它们都返回一个表示当前位置的streampos值(以字节为单位,从文件开始处算起)。创建 fstream 对象时,输入指针和输出指针将一前一后地移动,因此tellg()和tellp()返回的值相同。然而,如果使用 istrcam 对象来管理输入流,而使用ostream对来管理同一个文件的输出流,则输入指针和输出指针将彼此独立地
移动,因此 tellg()和 tellp( )将返回不同的值。然后,可以使用seekg()移到文件的开头。下面是打开文件、移到文件开头并显示文件内容的代码片段:
fstream finout;/read and write streams
finout.open(file,ios::inios::out |ios::binary);//NOTE: Some Unix systems require omittingios::binary
int et = 0;
if (finout.is open/))
{
finout.seekg(0);//go to beginning
cout <<"Here are the current contents of the "<< file << " file:\n";
while (finout,read((char *)&pl,sizeof pl))
{
Cout << ct++<<":"<< setw