详解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.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));

答案是可以的,将指定数目的字节读入内存,依次存放在以地址&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]中
   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;
  }
  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类。

(0)

相关推荐

  • C++中输出十六进制形式的字符串

    前言 在进行 i18n 相关的开发时,经常遇到字符编码转换的错误.这时如果能把相关字符串用十六进制的形式打印出来,例如,"abc" 输出成 "\\x61\\x62\\x63" 这对于 i18n 的除错来说是很有帮助的.Python 里面,只需要使用 repr() 函数就行了.可在 C++ 中如何做到这点呢? 下面是用 ostream 的格式化功能的一个简单的实现: std::string get_raw_string(std::string const& s

  • C++ 基础编程之十进制转换为任意进制及操作符重载

    C++ 基础编程之十进制转换为任意进制及操作符重载 最近学习C++ 的基础知识,完成十进制转换为任意进制及操作符重载,在网上找的不错的资料,这里记录下, 实例代码: #include<iostream> #include<vector> #include<limits> using namespace std; using std::iterator; ///<summary> ///十进制转换为任意进制,为了熟悉操作符,也加了操作符重载. ///包括自增(

  • C++ 十进制转换为二进制的实例代码

    题目内容:将十进制整数转换成二进制数. 输入描述:输入数据中含有不多于50个的整数n(-231<n<231). 输出描述:对于每个n,以11位的宽度右对齐输入n值,然后输出"-->",再然后输出二进制数.每个整数n的输出,独立占一行. 题目分析:将某个数从十进制转为二进制的具体方法是,该数对2取余,结果要么为1要么为0,此为该数对应二进制的末位:然后该数除以二,得到的商再次对2取余,结果为对应二进制的倒数第二位--以此类推,知道除以2的结果为0. 参考代码: 复制代码

  • C++实现数字转换为十六进制字符串的方法

    本文实例讲述了C++实现数字转换为十六进制字符串的方法.分享给大家供大家参考.具体如下: function toHex(number, min) { var hexes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; var hex = ''; var min = min || 2; var nibble; do { nibble = number & 0x0f; number

  • C/C++ 读取16进制文件的方法

    1.为什么有这种需求 因为有些情况需要避免出现乱码.不管什么编码都是二进制的,这样表示为16进制就可以啦. 2.如何读取16进制文件 最近编程用这一问题,网上查了一下,感觉还是自己写吧. 16进制数据一般是:text=0x340xb5...,就是0x开头,之后是两个数字十六进制数. 如果直接使用sscanf(text,"0x",&num),会把连续的数字读进去直到遇到'x'. 如使用sscanf读取text第一个读到的十六进制数是0x340,误判啦.最后,因为是4个一组,就先把

  • c++实现十进制转换成16进制示例

    c++实现十进制转换成16进制 复制代码 代码如下: //Decimal to hexadecimal number//programming by : Erfan Nasoori//Date of send : 2009/1/11 #include <iostream.h> void main(){ int x,y,i; int d,n=1; int * r; char h[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D

  • C++实现读入二进制数并转换为十进制输出

    题目描述 已知一个只包含0和1的二进制数,长度不大于10,将其转换为十进制并输出. 输入描述 输入一个二进制整数n,其长度不大于10 输出描述 输出转换后的十进制数,占一行 样例输入 110 样例输出 6 solution: 很多学过C初学C++的人可能想到的是用字符数组形式读入再一位一位计算成十进制输出. 没必要. C++类库提供了二进制数据类,并且可以用其方法转换成十进制. 代码如下: 复制代码 代码如下: #include <iostream> using namespace std;

  • C++实现十六进制字符串转换为十进制整数的方法

    本文实例讲述了C++实现十六进制字符串转换为十进制整数的方法.分享给大家供大家参考.具体实现方法如下: /* * 将十六进制数字组成的字符串(包含可选的前缀0x或0X)转换为与之等价的整型值 */ #include <stdio.h> #include <math.h> /* 将十六进制中的字符装换为对应的整数 */ int hexchtoi(char hexch ) { char phexch[] = "ABCDEF"; char qhexch[] = &qu

  • C++实现string存取二进制数据的方法

    本文实例讲述了C++实现string存取二进制数据的方法,分享给大家供大家参考.具体方法分析如下: 一般来说,STL的string很强大,用起来也感觉很舒服,这段时间在代码中涉及到了用string存取二进制数据的问题,这里记录一下,以供以后参考. 首先提一下STL中string的参考资料:http://www.cplusplus.com/reference/string/string/ ,不懂的朋友可以看下. 在数据传输中,二进制数据的buffer一般用系统预设的大数组进行存储,而不是STL的s

  • C++中几种将整数转换成二进制输出的方法总结

    看<编程之美>第二节的时候,它是定义的一个整型,然后取位.但是他的那个或运算符号好像写错了,写成了异或符号"^",应该是"|".我就突然对二进制的输出感兴趣了.想知道怎样输出二进制.我们知道C++输出十六进制是cout〈〈hex〈〈 a:而八进制是cout〈〈 ocx〈〈 a;二进制则没有默认的输出格式,需要自己写函数进行转换,于是上网搜索了一下.网上思路真是广泛啊. 下面列出一些方法.  #include 〈iostream〉 #include 〈li

随机推荐