二进制文件不是以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);
例.14 将一批数据以二进制形式存放在磁盘文件中。
#include <fstream>
using namespace std;
struct student
{
char name[20];
int num;
int age;
char sex;
};
int main( )
{
student stud[3]={"Li",1001,18,'f',"Fun",1002,19,'m',"Wang",1004,17,'f'};
ofstream outfile("stud.dat",ios::binary);
if(!outfile)
{
cerr<<"open error!"<<endl;
abort( );//退出程序
}
for(int i=0;i<3;i++)
outfile.write((char*)&stud[i],sizeof(stud[i]));
outfile.close( );
return 0;
}
其实可以一次输出结构体数组的个元素,将for循环的两行改为以下一行:
outfile.write((char*)&stud[0],sizeof(stud));
执行一次write函数即输出了结构体数组的全部数据。
可以看到,用这种方法一次可以输出一批数据,效率较高。在输出的数据之间不必加入空格,在一次输出之后也不必加回车换行符。在以后从该文件读入数据时不是靠空格作为数据的间隔,而是用字节数来控制。
例.15 将刚才以二进制形式存放在磁盘文件中的数据读入内存并在显示器上显示。
#include <fstream>
using namespace std;
struct student
{
string name;
int num;
int age;
char sex;
};
int main( )
{
student stud[3];
int i;
ifstream infile("stud.dat",ios::binary);
if(!infile)
{
cerr<<"open error!"<<endl;
abort( );
}
for(i=0;i<3;i++)
infile.read((char*)&stud[i],sizeof(stud[i]));
infile.close( );
for(i=0;i<3;i++)
{
cout<<"NO."<<i+1<<endl;
cout<<"name:"<<stud[i].name<<endl;
cout<<"num:"<<stud[i].num<<endl;;
cout<<"age:"<<stud[i].age<<endl;
cout<<"sex:"<<stud[i].sex<<endl<<endl;
}
return 0;
}
运行时在显示器上显示:
NO.1
name: Li
num: 1001
age: 18
sex: f
NO.2
name: Fun
num: 1001
age: 19
sex: m
NO.3
name: Wang
num: 1004
age: 17
sex: f
请思考: 能否一次读入文件中的全部数据,如
infile.read((char*)&stud[0],sizeof(stud));
二、与文件指针有关的流成员函数
在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。对于二进制文件,允许对指针进行控制,使它按用户的意图移动到所需的位置,以便在该位置上进行读写。文件流提供一些有关文件指针的成员函数。
几点说明:
1.这些函数名的第一个字母或最后一个字母不是g就是p。
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); //输出文件中的指针从文件尾后移字节
三、随机访问二进制数据文件
一般情况下读写是顺序进行的,即逐个字节进行读写。但是对于二进制数据文件来说,可以利用上面的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。
例.16 有个学生的数据,要求:
(1) 把它们存到磁盘文件中;
(2) 将磁盘文件中的第,3,5个学生数据读入程序,并显示出来;
(3) 将第个学生的数据修改后存回磁盘文件中的原有位置。
(4) 从磁盘文件读入修改后的个学生的数据并显示出来。
要实现以上要求,需要解决个问题:
(1) 由于同一磁盘文件在程序中需要频繁地进行输入和输出,因此可将文件的工作方式指定为输入输出文件,即ios::in|ios::out|ios::binary。
(2) 正确计算好每次访问时指针的定位,即正确使用seekg或seekp函数。
(3) 正确进行文件中数据的重写(更新)。
可写出以下程序:
#include <fstream>
using namespace std;
struct student
{
int num;
char name[20];
float score;
};
int main( )
{
student stud[5]={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(!iofile)
{
cerr<<"open error!"<<endl;
abort( );
}
for(int i=0;i<5;i++)//向磁盘文件输出个学生的数据
iofile.write((char *)&stud[i],sizeof(stud[i]));
student stud1[5]; //用来存放从磁盘文件读入的数据
for(int i=0;i<5;i=i+2)
{
iofile.seekg(i*sizeof(stud[i]),ios::beg); //定位于第,2,4学生数据开头
iofile.read((char *)&stud1[i/2],sizeof(stud1[0]));//先后读入个学生的数据,存放在stud1[0],stud[1]和stud[2]中
cout<<stud1[i/2].num<<" "<<stud1[i/2].name<<" "<<stud1[i/2].score<<endl;//输出stud1[0],stud[1]和stud[2]各成员的值
}
cout<<endl;
stud[2].num=1012; //修改第个学生(序号为)的数据
strcpy(stud[2].name,"Wu");
stud[2].score=60;
iofile.seekp(2*sizeof(stud[0]),ios::beg); //定位于第个学生数据的开头
iofile.write((char *)&stud[2],sizeof(stud[2])); //更新第个学生数据
iofile.seekg(0,ios::beg); //重新定位于文件开头
for(int i=0;i<5;i++)
{
iofile.read((char *)&stud[i],sizeof(stud[i])); //读入个学生的数据
cout<<stud[i].num<<" "<<stud[i].name<<" "<<stud[i].score<<endl;
}
iofile.close( );
return 0;
}
运行情况如下:
1001 Li 85(第个学生数据)
1004 Wang 54 (第个学生数据)
1010 ling 96 (第个学生数据)
1001 Li 85 (输出修改后个学生数据)
1002 Fun 97.5
1012 Wu 60 (已修改的第个学生数据)
1006 Tan 76.5
1010 ling 96
本程序将磁盘文件stud.dat指定为输入输出型的二进制文件。这样,不仅可以向文件添加新的数据或读入数据,还可以修改(更新)数据。利用这些功能,可以实现比较复杂的输入输出任务。
请注意,不能用ifstream或ofstream类定义输入输出的二进制文件流对象,而应当用fstream类。