将二进制文件读入结构(c++)

Reading Binary File into a Structure (C++)

本文关键字:c++ 结构 二进制文件      更新时间:2023-10-16

所以我有一点不能正确地读取二进制文件到我的结构中的问题。结构是这样的:

struct Student
{
    char name[25];
    int quiz1;
    int quiz2;
    int quiz3;
};

37字节(25字节来自char数组,每个整数4字节)。我的。dat文件是185字节。5个学生,3个整数年级。因此,每个学生占用37字节(37*5=185)。

纯文本格式如下:

Bart Simpson          75   65   70
Ralph Wiggum          35   60   44
Lisa Simpson          100  98   91
Martin Prince         99   98   99
Milhouse Van Houten   80   87   79

我可以通过使用以下代码单独读取每条记录:

Student stud;
fstream file;
file.open("quizzes.dat", ios::in | ios::out | ios::binary);
if (file.fail())
{
    cout << "ERROR: Cannot open the file..." << endl;
    exit(0);
}
file.read(stud.name, sizeof(stud.name));
file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1));
file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2));
file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3));
while(!file.eof())
{
    cout << left 
         << setw(25) << stud.name
         << setw(5)  << stud.quiz1
         << setw(5)  << stud.quiz2
         << setw(5)  << stud.quiz3
         << endl;
    // Reading the next record
    file.read(stud.name, sizeof(stud.name));
    file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1));
    file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2));
    file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3));
}

我得到了一个漂亮的输出,但是我希望一次能够读取一个完整的结构,而不是每次只读取每个结构的单个成员。这段代码是我认为需要完成的任务,但是…它不工作(我将在它后面显示输出):

*不包括文件的打开和结构声明等类似的部分

file.read(reinterpret_cast<char *>(&stud), sizeof(stud));
while(!file.eof())
{
    cout << left 
         << setw(25) << stud.name
         << setw(5)  << stud.quiz1
         << setw(5)  << stud.quiz2
         << setw(5)  << stud.quiz3
         << endl;
    file.read(reinterpret_cast<char *>(&stud), sizeof(stud));
}
输出:

Bart Simpson             16640179201818317312
ph Wiggum                288358417665884161394631027
impson                   129184563217692391371917853806
ince                     175193530917020655191851872800

它唯一不会弄乱的部分是名字,之后它就走下坡路了。我什么都试过了,还是不知道哪里不对。我甚至把我所有的书都找遍了,但什么也没找到。里面的东西看起来和我的一样,也很好用,但出于某种奇怪的原因,我的就不行了。我在字节25处执行file.get(ch) (ch是一个char),它返回K,这是75的ASCII值。这是第一次考试的分数,所以,所有的东西都在它应该在的地方。它不能正确读取我的结构

任何帮助都将非常感激,我只是被这个卡住了。

编辑:在收到你们这么多意想不到的和很棒的输入后,我决定接受你们的建议,坚持一次读一个成员。我用函数让东西更简洁。再次感谢您提供如此快速和有启发性的输入。非常感谢。

如果您对在大多数不推荐的解决方案中感兴趣,请滚动到底部,到user1654209的第三个答案。这个解决方案完美无缺,但看看所有的评论,看看为什么它不受欢迎。

您的结构体几乎肯定已经被填充以保持其内容的对齐。这意味着它不会是37字节,而这种不匹配会导致读取不同步。看看每个字符串丢失3个字符的方式,它似乎被填充到40个字节。

由于填充可能在字符串和整数之间,因此即使第一个记录也不能正确读取。

在这种情况下,我建议不要尝试将数据作为二进制blob读取,而是坚持读取单个字段。它要坚固得多,尤其是如果你想改变你的结构。

没有看到写入数据的代码,我猜您是按照第一个示例中读取数据的方式写入数据的,每个元素一个接一个。那么文件中的每个记录将确实是37字节。

但是,由于编译器为了优化的原因而填充结构以将成员放在良好的边界上,所以您的结构是40字节。因此,当您在一次调用中读取完整的结构时,实际上每次读取40字节,这意味着您的读取将与文件中实际记录的相位不一致。

您要么必须重新实现写入以一次写入完整的结构,要么使用第一种读取方法,每次读取一个成员字段。

一个简单的解决方法是将结构打包为1字节

使用GCC

struct __attribute__((packed)) Student
{
    char name[25];
    int quiz1;
    int quiz2;
    int quiz3;
};

使用MSVC

#pragma pack(push, 1) //set padding to 1 byte, saves previous value
struct  Student
{
    char name[25];
    int quiz1;
    int quiz2;
    int quiz3;
};
#pragma pack(pop) //restore previous pack value

编辑:正如用户汉斯所说:gcc从2.7.2.3版本(1997年发布)开始支持pragma pack,所以如果您的目标是msvc和gcc

,那么使用pragma pack作为唯一的打包符号似乎是安全的,因为您已经发现,填充是这里的问题。另外,正如其他人所建议的,解决这个问题的正确方法是像您在示例中所做的那样单独读取每个成员。我不认为这会比一次性阅读整个程序花费更多的时间。但是,如果您仍然想要读取它,您可以告诉编译器以不同的方式进行填充:

#pragma pack(push, 1)
struct Student
{
    char name[25];
    int quiz1;
    int quiz2;
    int quiz3;
};
#pragma pack(pop)

对于#pragma pack(push, 1),您告诉编译器将当前包值保存在内部堆栈上,然后使用1的包值。这意味着您将获得1字节的对齐,这意味着在这种情况下根本没有填充。对于#pragma pack(pop),你告诉编译器从堆栈中获取最后一个值,然后使用它,从而恢复编译器在定义struct之前使用的行为。

虽然#pragma通常表示不可移植的、依赖于编译器的特性,但这个至少适用于GCC和Microsoft vc++。

解决这个线程问题的方法不止一种。下面是基于使用结构体和char buf的联合的解决方案:

#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
/*
This is the main idea of the technique: Put the struct
inside a union. And then put a char array that is the
number of chars needed for the array.
union causes sStudent and buf to be at the exact same
place in memory. They overlap each other!
*/
union uStudent
{
    struct sStudent
    {
        char name[25];
        int quiz1;
        int quiz2;
        int quiz3;
    } field;
    char buf[ sizeof(sStudent) ];    // sizeof calcs the number of chars needed
};
void create_data_file(fstream& file, uStudent* oStudent, int idx)
{
    if (idx < 0)
    {
        // index passed beginning of oStudent array. Return to start processing.
        return;
    }
    // have not yet reached idx = -1. Tail recurse
    create_data_file(file, oStudent, idx - 1);
    // write a record
    file.write(oStudent[idx].buf, sizeof(uStudent));
    // return to write another record or to finish
    return;
}

std::string read_in_data_file(std::fstream& file, std::stringstream& strm_buf)
{
    // allocate a buffer of the correct size
    uStudent temp_student;
    // read in to buffer
    file.read( temp_student.buf, sizeof(uStudent) );
    // at end of file?
    if (file.eof())
    {
        // finished
        return strm_buf.str();
    }
    // not at end of file. Stuff buf for display
    strm_buf << std::setw(25) << std::left << temp_student.field.name;
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz1;
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz2;
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz3;
    strm_buf << std::endl;
    // head recurse and see whether at end of file
    return read_in_data_file(file, strm_buf);
}

std::string quiz(void)
{
    /*
    declare and initialize array of uStudent to facilitate
    writing out the data file and then demonstrating
    reading it back in.
    */
    uStudent oStudent[] =
    {
        {"Bart Simpson",          75,   65,   70},
        {"Ralph Wiggum",          35,   60,   44},
        {"Lisa Simpson",         100,   98,   91},
        {"Martin Prince",         99,   98,   99},
        {"Milhouse Van Houten",   80,   87,   79}
    };


    fstream file;
    // ios::trunc causes the file to be created if it does not already exist.
    // ios::trunc also causes the file to be empty if it does already exist.
    file.open("quizzes.dat", ios::in | ios::out | ios::binary | ios::trunc);
    if ( ! file.is_open() )
    {
        ShowMessage( "File did not open" );
        exit(1);
    }

    // create the data file
    int num_elements = sizeof(oStudent) / sizeof(uStudent);
    create_data_file(file, oStudent, num_elements - 1);
    // Don't forget
    file.flush();
    /*
    We wrote actual integers. So, you cannot check the file so
    easily by just using a common text editor such as Windows Notepad.
    You would need an editor that shows hex values or something similar.
    And integrated development invironment (IDE) is likely to have such
    an editor.   Of course, not always so.
    */

    /*
    Now, read the file back in for display. Reading into a string buffer
    for display all at once. Can modify code to display the string buffer
    wherever you want.
    */
    // make sure at beginning of file
    file.seekg(0, ios::beg);
    std::stringstream strm_buf;
    strm_buf.str( read_in_data_file(file, strm_buf) );
    file.close();
    return strm_buf.str();
}

调用quiz()并接收一个格式化的字符串,用于显示std::cout、写入文件或其他。

主要思想是联合中的所有项都从内存中的同一地址开始。所以你可以有一个char或wchar_t,但它的大小与你想要写入或从文件中读取的结构体相同。注意,需要零强制转换。代码中没有强制类型转换

我也不必担心填充。

对于那些不喜欢递归的人,抱歉。对我来说,用递归来解决这个问题更容易,也更不容易出错。也许对其他人来说不容易?递归可以转换为循环。对于非常大的文件,它们需要转换为循环。

对于那些喜欢递归的人来说,这是使用递归的另一个实例。

我并不是说使用并集是最好的解决方案。似乎这是一个解决方案。也许你喜欢它?