8

そのため、バイナリ ファイルを自分の構造に正しく読み込めないという問題が少しあります。構造は次のとおりです。

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

37 バイトです (char 配列から 25 バイト、整数ごとに 4 バイト)。私の .dat ファイルは 185 バイトです。3 つの整数の成績を持つ 5 人の学生です。したがって、各学生は 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));
}

そして、見栄えの良い出力が得られますが、一度に各構造の個々のメンバーだけでなく、一度に 1 つの構造全体を読み取れるようにしたいと考えています。このコードは、タスクを達成するために必要であると私が信じているものですが、... 機能しません (その後に出力を表示します):

※ファイルの開き方や構造宣言等、類似部分は除きます。

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) を実行すると、75 の ASCII である K が返されました。これは最初のテスト スコアです。私の構造を正しく読み取っていないだけです。

どんな助けでも大歓迎です、私はこれで立ち往生しています。

編集: 皆さんから予想外で素晴らしいインプットを大量に受け取った後、私は皆さんのアドバイスを受けて、一度に 1 人のメンバーを読むことに固執することにしました. 関数を使用して、物事をよりきれいに、より小さくしました。 このような迅速かつ啓発的な情報を提供していただき、重ねてお礼申し上げます。とても感謝しています。

ほとんどの人が推奨していない回避策に興味がある場合は、一番下までスクロールして、user1654209 による 3 番目の回答を見つけてください。この回避策は問題なく機能しますが、すべてのコメントを読んで、それが支持されない理由を確認してください。

4

5 に答える 5

10

構造体は、そのコンテンツの配置を維持するためにほぼ確実にパディングされています。これは、37 バイトにならないことを意味し、その不一致により読み取りが同期しなくなります。各文字列が 3 文字失われている様子を見ると、40 バイトにパディングされているようです。

パディングは文字列と整数の間にある可能性が高いため、最初のレコードでさえ正しく読み取れません。

この場合、データをバイナリ BLOB として読み取ろうとせず、個々のフィールドの読み取りに固執することをお勧めします。特に構造を変更したい場合でも、はるかに堅牢です。

于 2013-03-21T08:37:33.970 に答える
4

データを書き込むコードを見なくても、最初の例で読み取った方法で各要素を 1 つずつデータを記述していると思います。その場合、ファイル内の各レコードは実際には 37 バイトになります。

ただし、コンパイラは最適化のためにメンバーを適切な境界に配置するために構造体をパディングするため、構造体は 40 バイトになります。したがって、1 回の呼び出しで完全な構造を読み取る場合、実際には一度に 40 バイトを読み取ることになります。つまり、読み取りはファイル内の実際のレコードと位相がずれることになります。

完全な構造を一度に書き込むには、書き込みを再実装するか、一度に 1 つのメンバー フィールドを読み取る最初の読み取り方法を使用する必要があります。

于 2013-03-21T08:46:16.960 に答える
3

すでにわかっているように、パディングがここでの問題です。また、他の人が示唆しているように、これを解決する適切な方法は、例で行ったように各メンバーを個別に読み取ることです。パフォーマンスの観点から、これがすべてを一度に読み取るよりもはるかに多くの費用がかかるとは思いません。ただし、それでも先に進んで一度だけ読みたい場合は、コンパイラーにパディングを別の方法で行うように指示できます。

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

#pragma pack(push, 1)現在の pack 値を内部スタックに保存し、その後 pack 値 1 を使用するようにコンパイラに指示します。これは、1 バイトのアラインメントを得ることを意味します。つまり、この場合、パディングはまったくありません。#pragma pack(pop)スタックから最後の値を取得し、その後これを使用するようコンパイラーに指示すると、コンパイラーが の定義の前に使用した動作が復元されますstruct

通常、移植性がなく、コンパイラに依存する機能を#pragma示しますが、これは少なくとも GCC と Microsoft VC++ で動作します。

于 2013-03-21T09:23:01.827 に答える
1

このスレッドの問題を解決する方法は複数あります。以下は、構造体と 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 を持つことができます。また、ゼロキャストが必要であることに注意してください。コードには 1 つのキャストはありません。

パディングについても心配する必要はありませんでした。

再帰が苦手な方はごめんなさい。再帰を使用して作業する方が簡単で、エラーが発生しにくくなります。他の人にとっては簡単ではないでしょうか?再帰はループに変換できます。また、非常に大きなファイルのループに変換する必要があります。

再帰が好きな人にとっては、これは再帰を使用するもう 1 つの例です。

ユニオンを使用することが最善の解決策であるとは主張しません。それは解決策のようです。多分あなたはそれが好きですか?

于 2016-09-29T02:41:11.310 に答える