繁体   English   中英

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

[英]Reading Binary File into a Structure (C++)

所以我遇到了一个无法正确读取二进制文件到我的结构中的问题。 结构是这样的:

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

它是37个字节(来自char数组的25个字节,每个整数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));
}

OUTPUT:

Bart Simpson             16640179201818317312
ph Wiggum                288358417665884161394631027
impson                   129184563217692391371917853806
ince                     175193530917020655191851872800

唯一没有搞乱的部分就是名字,之后就是下山了......我已经尝试了一切,我不知道出了什么问题。 我甚至搜索过我的书,我找不到任何东西。 那里的东西看起来像我拥有的​​东西而且它们起作用,但由于一些奇怪的原因,我的东西没有。 我在第25个字节做了file.get(ch)(ch是一个字符),它返回了K,这是第7个测试分数的ASCII,所以,一切都应该是它。 它只是没有正确阅读我的结构。

任何帮助将不胜感激,我只是坚持这一个。

编辑:在收到你们这么大的意外和令人敬畏的意见之后,我决定接受你的建议并坚持一次阅读一个成员。 我通过使用功能使事情更清洁,更小。 再次感谢您提供如此快速和启发性的输入。 非常感谢。

如果您对大多数人不推荐的解决方法感兴趣 ,请向用户1654209滚动到底部,到第3个答案。 该解决方案完美无瑕,但阅读所有评论,看看为什么它不受青睐。

您的结构几乎肯定已经填充以保持其内容的对齐。 这意味着它不会是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

编辑:正如用户ahans所述:自版本2.7.2.3(1997年发布)以来,gcc支持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 ++。

有多种方法可以解决这个线程的问题。 这是一个基于使用struct和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 buf。 并注意到需要零投射。 代码中没有一个强制转换。

我也不必担心填充。

对于那些不喜欢递归的人,抱歉。 使用递归进行处理对我来说更容易,更不容易出错。 对别人来说可能不容易? 递归可以转换为循环。 并且它们需要转换为非常大的文件的循环。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM