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

时间:2013-03-21 08:32:20

标签: c++ file file-io struct binary

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

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));
}

输出:

Bart Simpson             16640179201818317312
ph Wiggum                288358417665884161394631027
impson                   129184563217692391371917853806
ince                     175193530917020655191851872800

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

任何帮助都会非常感激,我只是坚持这个。

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

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

5 个答案:

答案 0 :(得分:9)

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

由于填充可能在字符串和整数之间,所以即使第一条记录也没有正确读取。

在这种情况下,我建议不要尝试将数据作为二进制blob读取,并坚持阅读单个字段。它更加强大,特别是如果你想改变你的结构。

答案 1 :(得分:4)

在没有看到写入数据的代码的情况下,我猜你在第一个例子中按照你的方式编写数据,每个元素都是一个接一个。然后文件中的每个记录确实是37个字节。

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

您必须重新执行写入以一次编写完整的结构,或者使用第一种方法来一次读取一个成员字段。

答案 2 :(得分:4)

一个简单的解决方法是将结构打包为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作为唯一的打包符号似乎是安全的

答案 3 :(得分:3)

正如您已经发现的那样,填充是这里的问题。此外,正如其他人所建议的那样,解决这个问题的正确方法是单独阅读每个成员,就像你在你的例子中所做的那样。我不认为这比一次性能阅读整个事情花费更多。但是,如果你仍想继续阅读它,你可以告诉编译器以不同的方式执行填充:

#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 ++。

答案 4 :(得分:1)

解决此线程问题的方法不止一种。这是一个基于使用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是最好的解决方案。似乎这是一个解决方案。也许你喜欢它?