背景
因为项目需要,所以需要学习一下对二进制文件读写的操作,特此记录成长也跟大家总结分享一下较为全面的用法!
百度经验
二进制文件不是以ASCII形式存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。
对二进制文件的操作也需要先打开文件,用完后要关闭文件。在打开时要用ios::binary指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。这是和ASCII文件不同的地方。
读写二进制文件的原理
对二进制文件的读写主要用istream和ostream类的成员函数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所指的一段空间内。
在二进制文件里写数据
struct NodeInfo
{
char NodeValue;
int SonoffSize;
int NodeIdOffSize;
};
ofstream outfile("NodeInfo.dat",ios::binary);
if(!outfile) {
cerr<<"open error!"<<endl;
exit(0);
}
NodeInfo aNode;
aNode.NodeIdOffSize = 10;
aNode.NodeValue = 'A';
aNode.SonoffSize = 2;
outfile.write((char*)&aNode,sizeof(NodeInfo));
outfile.close( );
用成员函数write向NodeInfo.dat输出数据,从前面给出的write函数的原型可以看出: 第1个形参是指向char型常变量的指针变量buffer,之所以用const声明,是因为不允许通过指针改变其指向数据的值。形参要求相应的实参是字符指针或字符串的首地址。现在要将结构体对象的一个元素(包含3个成员)一次输出到磁盘文件NodeInfo.dat。&aNode 是结构体对象首地址,但这是指向结构体的指针,与形参类型不匹配。因此 要用(char *)把它强制转换为字符指针。第2个参数是指定一次输出的字节数。sizeof (NodeInfo )的值是结构体对象字节数。调用一次write函数,就将从NodeInfo 结构体的一个对象输出到磁盘文件中。
在二进制文件里读数据。
struct NodeInfo
{
char NodeValue;
int SonoffSize;
int NodeIdOffSize;
};
ifstream infile("NodeInfo.dat",ios::binary);
if(!infile) {
cerr<<"open error!"<<endl;
exit(0);
}
NodeInfo aNode;
infile.read((char*)&aNode,sizeof(NodeInfo));
infile.close( );
printf("aNode.NodeValue = %c,aNode.SonoffSize = %d, aNode.NodeIdOffSize = %d\n", aNode.NodeValue, aNode.SonoffSize, aNode.NodeIdOffSize);
读取的数据与输入数据完全一致。
与文件指针有关的流成员函数
在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。每读 一个宇节,指针就向后移动一个字节。在输出时每向文件写一个字节,指针就向后移动 一个字节,随着写入文件中字节不断增加,指针不断后移。对于二进制文件,允许对指针进行控制,使它按用户的意图移动到所需的位置,以便在该位置上进行读写。文件流提供 一些有关文件指针的成员函数。
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); //输出文件中的指针从文件尾后移字节
随机访问二进制数据文件
一般情况下读写是顺序进行的,即逐个字节进行读写。但是对于二进制数据文件来说,可以利用上面的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。
[例13.16] 有个多个NodeInfo的数据,要求:
把它们存到磁盘文件中;
将磁盘文件中的第1,3个Node数据读入程序,并显示出来;
将第2个Node的数据修改后存回磁盘文件中的原有位置。
从磁盘文件读出修改后的个学生的数据并显示出来。
要实现以上要求,需要解决个问题:
由于同一磁盘文件在程序中需要频繁地进行读和写,因此可将文件的工作方式指定为可读可写文件
1〉ios::in|ios::out|ios::binary。
2〉正确计算好每次访问时指针的定位,即正确使用seekg或seekp函数。
3〉正确进行文件中数据的重写(更新)。
fstream iofile("NodeInfo.dat",ios::in|ios::out|ios::binary);
if(!iofile) {
cerr<<"open error!"<<endl;
exit(0);
}
NodeInfo aNode[3];
for (int i = 0; i < 3; i++) {
aNode[i].NodeIdOffSize = 10 * (i+1);
aNode[i].NodeValue = 'A' + i;
aNode[i].SonoffSize = 2 *(i+1);
}
for (int i = 0; i < 3; i++) {
iofile.write((char *)&aNode[i],sizeof(aNode[i]));
}
NodeInfo aNode1[3];
for (int i = 0; i < 3; i+=2) {
iofile.seekg(i*sizeof(aNode[i]),ios::beg);
iofile.read((char *)&aNode1[i/2],sizeof(aNode1[0]));
}
for (int i = 0; i < 2; i++) {
printf("%c %d %d\n", aNode1[i].NodeValue, aNode1[i].SonoffSize, aNode1[i].NodeIdOffSize);
}
aNode[1].NodeIdOffSize = 100;
aNode[1].NodeValue = 'F';
aNode[1].SonoffSize = 208;
iofile.seekp(2*sizeof(aNode[0]),ios::beg);
iofile.write((char *)&aNode[1],sizeof(aNode[1]));
iofile.seekg(0,ios::beg);
for (int i = 0; i < 3; i++) {
printf("%c %d %d\n", aNode[i].NodeValue, aNode[i].SonoffSize, aNode[i].NodeIdOffSize);
}
iofile.close( );
以上,欢迎留言交流~