读写C++二进制文件(精华版)

 2024-03-02 04:05:50  阅读 0

我们先来谈谈为什么要使用二进制文件以及它们相对于文本文件有哪些优点。

以文本格式存储信息不仅浪费空间,而且检索也不方便。 例如,一个学籍管理程序需要记录所有学生的学号、姓名、年龄信息,并能够通过姓名搜索学生信息。 程序中可以使用一个类来代表学生:

class CStudent
{
    char szName[20];  //假设学生姓名不超过19个字符,以 '\0' 结尾
    char szId[l0];  //假设学号为9位,以 '\0' 结尾
    int age;  //年龄
};

如果您使用文本文件来存储学生信息,该文件可能如下所示:

17 号

汤姆·汉克斯 18

这种存储方式不仅浪费空间,而且查找效率低。 由于每个学生的信息占用的字节数不同,即使文件中的学生信息是按姓名排序的,仍然没有好的办法用程序按姓名进行查找。 您只能在文件中从头到尾搜索。 搜索。

如果将所有学生信息读入内存并排序后再进行查找,速度当然会很快,但如果学生数量巨大,将所有学生信息读入内存可能不太现实。

学生信息可以以二进制形式存储,即对象直接写入文件。 在这个文件中,每个学生的信息占用()个字节。 当一个对象被写入文件时,它通常被称为“记录”。 在这个例子中,每个学生对应一条记录。 学生记录文件可以按名称排序,因此使用半查找的效率会很高。

对于二进制文件的读写,不能使用前面提到的类似cin、cout的方法从流中读写数据。 这时,就可以调用类和类的read成员函数从文件中读取数据,调用类和类的write成员函数将数据写入文件中。 使用::write成员函数来写文件,write成员函数实际上是从该类继承的,原型如下:

& write(char*, int count);

该成员函数将内存中指向的count个字节写入文件。 返回值是对该函数所作用的对象的引用。 例如,obj.write(...) 的返回值是对 obj 的引用。

write成员函数将多个字节写入文件,但是在调用write函数时,它并没有指定这些字节要写入文件中的哪个位置。 那么,write函数在执行过程中将这些字节写入哪里呢? 答案是从文件写指针指向的位置开始写入。

文件写指针是一个由 or 对象内部维护的变量。 当文件第一次打开时,文件写指针指向文件的开头(如果以ios::app模式打开,则指向文件的结尾)。 使用write函数写入n个字节,写指针指向的位置向后移动n个字。 节日。

以下程序从键盘输入几个学生的姓名和年龄(输入时按 Ctrl+Z 单独一行,然后按 Enter 结束输入。假设学生姓名中没有空格)并将其输出为二进制文件文件 存储为学生记录文件 .dat。

例如,使用二进制文件保存学生记录:

#include 
#include 
using namespace std;
class CStudent
{
public:
    char szName[20];
    int age;
};
int main()
{
    CStudent s;
    ofstream outFile("students.dat", ios::out | ios::binary);
    while (cin >> s.szName >> s.age)
        outFile.write((char*)&s, sizeof(s));
    outFile.close();
    return 0;
}

进入:

汤姆 60↙

杰克 80↙

简 40↙

^Z↙

生成的.dat为72字节,用“记事本”程序打开会出现乱码:

汤姆热热热热热热热热热热热热热热热热热吗? 简,热,热,热,热,热?

第13行指定文件的打开方式为ios::out|ios::,即以二进制写入方式打开。 在平台中,需要以二进制方式打开,否则可能会出现错误。 原因将在“文件的文本打开方式与二进制打开方式的区别”一节中介绍。

第 15 行将 s 对象写入文件。 s的地址是要写入文件的内存缓冲区的地址。 但 &s 不是 char * 类型,因此需要强制转换。

第16行,文件使用后必须关闭,否则程序结束后文件内容可能不完整。 使用::read成员函数来读取文件,read成员函数实际上继承自该类,原型如下:

& 读取(char*, int count);

该成员函数从文件中读取 count 个字节并将它们存储在指向的内存缓冲区中。 返回值是对该函数所作用的对象的引用。

如果想知道总共读取了多少个字节(读到文件末尾时可能读不到count个字节),可以在执行完read函数后立即调用文件流对象的成员函数,其返回值是最新执行read函数时成功读取的字节数。 它是类的成员函数,其原型如下:

int();

read 成员函数从文件读指针指向的位置开始读取多个字节。 文件读指针是对象内部维护的变量。 第一次打开文件时,文件读指针指向文件开头(如果以ios::app模式打开,则指向文件结尾)。 使用read函数读取n个字节,读指针指向的位置向后移动n个字。 节日。 因此,通过在打开文件后不断调用read函数,就可以读取整个文件的内容。

以下程序读取并显示之前创建的学生记录文件 .dat 的内容。

#include 
#include 
using namespace std;
class CStudent
{
    public:
        char szName[20];
        int age;
};
int main()
{
    CStudent s;       
    ifstream inFile("students.dat",ios::in|ios::binary); //二进制读方式打开
    if(!inFile) {
        cout << "error" <

程序的输出是:

汤姆60

杰克80

简40

第18行判断文件是否已读的方法与while(cin>>n)类似。 归根结底,是因为类重载了bool强制转换运算符。

第 19 行只是演示了该函数的用法。 删除该行对程序运行结果没有影响。

问题:两个.dat程序中,如果类的定义不是“char[20]”而是“”,可以吗? 为什么? 使用文件流类的put和get成员函数来读写文件。 您可以使用and 类(从该类继承)的get 成员函数从文件中一次读取一个字节,也可以使用and 类(从该类继承)的put 成员函数。 一次向文件写入一个字节。

例:编写一个程序,实现文件复制。 用法是在“命令提示符”窗口中输入:

源文件名 目标文件名

源文件将被复制到目标文件。 例如:

源文件.dat 目标.dat

即,将src.dat复制到dest.dat。 如果 dest.dat 原本存在,则原文件将被覆盖。

解决问题的基本思路是每次从源文件中读取一个字节,然后写入到目标文件中。 程序如下:

#include 
#include 
using namespace std;
int main(int argc, char* argv[])
{
    if (argc != 3) {
        cout << "File name missing!" << endl;
        return 0;
    }
    ifstream inFile(argv[l], ios::binary | ios::in);  //以二进制读模式打开文件
    if (!inFile) {
        cout << "Source file open error." << endl;
        return 0;
    }
    ofstream outFile(argv[2], ios::binary | ios::out);  //以二进制写模式打开文件
    if (!outFile) {
        cout << "New file open error." << endl;
        inFile.close();  //打开的文件一定要关闭
        return 0;
    }
    char c;
    while (inFile.get(c))  //每次读取一个字符
        outFile.put(c);  //每次写入一个字符
    outFile.close();
    inFile.close();
    return 0;
}

文件存储在磁盘上,磁盘访问速度比内存慢很多。 如果每次读一个字节或者写一个字节都要访问磁盘,那么文件读写速度就会慢得难以忍受。 因此,当操作系统收到读文件的请求时,即使只需要读一个字节,也会一次性读到一段数据(通常至少是512字节,因为磁盘的一个扇区是512B)操作 在系统自己管理的内存缓冲区中,当要读取下一个字节时,不需要访问磁盘,直接从缓冲区中读取即可。

当操作系统收到写文件的请求时,它首先将要写入的数据保存在内存缓冲区中。 当缓冲区满时,它将缓冲区的所有内容写入磁盘。 关闭文件可确保内存缓冲区中的数据写入磁盘。

即便如此,当你想连续读写文件时,像程序一样逐字节读写还不如一次读写一块内存区域那么快。 每次读写的字节数最好是512的整数倍。

标签: 文件 字节 函数

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码