详解C++编程中对⼆进制⽂件的读写操作
⼆进制⽂件不是以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所指的⼀段空间内。
[例] 将⼀批数据以⼆进制形式存放在磁盘⽂件中。
#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;
}
⽤成员函数write向stud.dat输出数据,从前⾯给出的write函数的原型可以看出:第1个形参是指向char型常变量的指针变量buffer,之所以⽤const声明,是因为不允许通过指针改变其指向数据的值。形参要求相应的实参是字符指针或字符串的⾸地址。现在要将结构体数组的⼀个元素(包含4个成员)⼀次输出到磁盘⽂件stud.dat。&tud[i] 是结构体数组第i个元素的⾸地址,但这是指向结构体的指针,与形参类型不匹配。因此要⽤(char *)把它强制转换为字符指针。第2个参数是指定⼀次输出的字节数。sizeof (stud[i])的值是结构体数组的⼀个元素的字节数。调⽤⼀次write函数,就将从&tud[i]开始的结构体数组的⼀个元素输出到磁盘⽂件中,执⾏3次循环输出结构体数组的3个元素。
其实可以⼀次输出结构体数组的个元素,将for循环的两⾏改为以下⼀⾏:
outfile.write((char*)&stud[0],sizeof(stud));
执⾏⼀次write函数即输出了结构体数组的全部数据。
abort函数的作⽤是退出程序,与exit作⽤相同。
可以看到,⽤这种⽅法⼀次可以输出⼀批数据,效率较⾼。在输出的数据之间不必加⼊空格,在⼀次输出之后也不必加回车换⾏符。在以后从该⽂件读⼊数据时不是靠空格作为数据的间隔,⽽是⽤字节数来控制。
[例] 将刚才以⼆进制形式存放在磁盘⽂件中的数据读⼊内存并在显⽰器上显⽰。
#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.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
请思考,能否⼀次读⼊⽂件中的全部数据,如:
答案是可以的,将指定数⽬的字节读⼊内存,依次存放在以地址&tud[0]开始的存储空间中。要注意读⼊的数据的格式要与存放它的空间的格式匹配。由于磁盘⽂件中的数据是从内存中结构体数组元素得来的,因此它仍然保留结构体元素的数据格式。现在再读⼊内存,存放在同样的结构体数组中,这必然是匹配的。如果把它放到⼀个整型数组中,就不匹配了,会出错。
与⽂件指针有关的流成员函数
在磁盘⽂件中有⼀个⽂件指针,⽤来指明当前应进⾏读写的位置。在输⼊时每读⼊⼀个宇节,指针就向后移动⼀个字节。在输出时每向⽂件输出⼀个字节,指针就向后移动⼀个字节,随着输出⽂件中字节不断增加,指针不断后移。对于⼆进制⽂件,允许对指针进⾏控制,使它按⽤户的意图移动到所需的位置,以便在该位置上进⾏读写。⽂件流提供⼀些有关⽂件指针的成员函数。为了查阅⽅便,将它们归纳为下表:
⼏点说明:
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); //输出⽂件中的指针从⽂件尾后移字节
随机访问⼆进制数据⽂件
⼀般情况下读写是顺序进⾏的,即逐个字节进⾏读写。但是对于⼆进制数据⽂件来说,可以利⽤上⾯的成员函数移动指针,随机地访问⽂件中任⼀位置上的数据,还可以修改⽂件中的内容。
[例] 有个学⽣的数据,要求:
把它们存到磁盘⽂件中;
将磁盘⽂件中的第,3,5个学⽣数据读⼊程序,并显⽰出来;
将第个学⽣的数据修改后存回磁盘⽂件中的原有位置。
从磁盘⽂件读⼊修改后的个学⽣的数据并显⽰出来。
要实现以上要求,需要解决个问题:
由于同⼀磁盘⽂件在程序中需要频繁地进⾏输⼊和输出,因此可将⽂件的⼯作⽅式指定为输⼊输出⽂件,即
ios::in|ios::out|ios::binary。
正确计算好每次访问时指针的定位,即正确使⽤seekg或seekp函数。
正确进⾏⽂件中数据的重写(更新)。
可写出以下程序:
#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学⽣数据开头
//先后读⼊个学⽣的数据,存放在stud1[0],stud[1]和stud[2]中
//输出stud1[0],stud[1]和stud[2]各成员的值
cout<<stud1[i/2].num<<" "<<stud1[i/2].name<<" "<<stud1[i/2].score<<endl;
}
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++)
{
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 (已修改的第个学⽣数据)
sizeof结构体大小
1006 Tan 76.5
1010 ling 96
本程序也可以将磁盘⽂件stud.dat先后定义为输出⽂件和输⼊⽂件,在结束第⼀次的输出之后关闭该⽂件,然后再按输⼊⽅式打开它,输⼊完后再关闭它,然后再按输出⽅式打开,再关闭,再按输⼊⽅式打开它,输⼊完后再关闭。显然这是很烦琐和不⽅便的。在程序中把它指定为输⼊输出型的⼆进制⽂件。这样,不仅可以向⽂件添加新的数据或读⼊数据,还可以修改(更新)数据。利⽤这些功能,可以实现⽐较复杂的输⼊输出任务。
请注意,不能⽤ifstream或ofstream类定义输⼊输出的⼆进制⽂件流对象,⽽应当⽤fstream类。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论