2
#include<stdio.h>
#include<stdlib.h>


typedef struct{
        char cStdName[50];
        int  nStdNum;
        char cStdClass[4];
        float dStdAvg;
}student;

student* students;

int cmp(const void* a, const void* b);

void main() {
    int num = 0,i=0;
    FILE *f;

    printf("Number of students:");
    scanf("%d", &num);
    students = (student*)malloc(num*sizeof(student));

    for(i=0;i<num;++i){

        student* ptr = students+i*sizeof(student);
        printf("Name:");
        scanf("%s", ptr->cStdName);
        printf("Num");
        scanf("%d", &ptr->nStdNum);
        printf("Class:");
        scanf("%s", ptr->cStdClass);
        printf("Grade:");
        scanf("%f", &ptr->dStdAvg);
    }

    f = fopen("bin.bin","wb");
    fwrite(&num,sizeof(int),1,f);
    fwrite(students,sizeof(student),num,f);
    fclose(f);
    system("pause");
}

これは、学生の数とすべての構造「配列」をバイナリファイルに出力することになっており、1人の学生で機能します。しかし、>=2 人を追加すると、ファイルは次のようになります: http://i.imgur.com/LgL8fUa.png

生徒を 1 人だけ追加した場合でも、この Windows パスのナンセンスがいくつか残っています: http://i.imgur.com/s7fm9Uv.png ファイルを読み取るプログラムは、NULL (つまり、最初の char 配列の場合)。

問題は for() ループとポインターのジャグリングのどこかにあると思いますが、どこにあるのかわかりません。

4

4 に答える 4

5
student* ptr = students + i * sizeof(student);

C では、ポインター演算には既に が含まれていますsizeof(student)。配列の終わりを過ぎて読み取ります。

student* ptr = students + i;

ただし、 へのアクセスptrは へのアクセスと同じであることがわかりますstudents[i]

于 2013-03-27T15:45:45.723 に答える
1

コードにはいくつかの問題があります。

  • まず第一に、Kirilenko が言ったようstudents[i]に、コードで使用する必要があります。あなたの場合、students + i * sizeof(student)範囲外になります。
  • fwrite構造体の配列で使用することは決して良い考えではありません。これは、コンパイラが構造体のメンバー間にスペースを追加する可能性があるためです (パディング)。これは、構造体の配列を に渡すとfwrite、パディング バイト (ガベージを含む) が出力されることを意味します。
  • 構造体の char 配列メンバーにも同じことが当てはまります。未使用のすべてのバイトにはガベージが含まれており、使用すると出力されますfwritestrlen各 char 配列に含まれる読み取り文字数を決定するために使用することをお勧めします。

生徒の配列をファイルに書き込む方法は次のとおりです。

void write(students* array, int len, FILE* out)
{
    int i;
    fwrite(len, 1, sizeof(len), out);

    for (i = 0; i < len; i++){
        fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
        fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
        fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
        fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
    }
}
于 2013-03-27T16:15:58.303 に答える
0

ポインターの割り当ては、一度 for ループの外側に置くことができ、for ループの最後でポインターをインクリメントできます。これを試して。

Ok。これは何が起こっているのかを説明する試みです.ptrは構造体型の学生リストの最初の要素を指しています.forループの最後でptrをインクリメントすると、リスト内の次の学生を指します.


---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;

    for(i=0;i<num;++i){

        printf("Name:");
        ----
        ----
        ptr++;
    }
于 2013-03-27T15:51:57.370 に答える
0

Kirilenko の答えは当面の問題を解決するはずですが、このプログラムのほぼすべての行に些細なものから深刻なものまでさまざまなエラーがあります。より大きな目標が何であるかによっては、すべてを修正する必要はないかもしれませんが、氷山の大きさを示すために、とにかくすべてを書き留めておきます。

void main() {

int main(void). はint絶対条件です。C (C++ ではない) では()、関数の引数リストを記述することは、引数が指定されていないことを意味し、引数がないことを意味しません。これは関数定義であるため、技術的にはここで回避できますが、スタイルが悪いです。

関数定義の開き中かっこは、他のすべての開き中かっこがくっついている場合でも、常に単独で 1 行に表示されます。

    int num = 0,i=0;

一貫性のない間隔。初期化は不要です。

    printf("Number of students:");

一部のスタイル ガイドでは、の書式設定機能fputs("Number of students", stdout);を使用していない場合に優先されます。printfただし、一部のコンパイラは変換を行うことができ、大したことではありません。

    scanf("%d", &num);

scanffscanf、またはを使用しないsscanfでください。理由は次のとおりです。

  1. 数値のオーバーフローは、未定義の動作を引き起こします。誰かがあまりにも多くの数字を入力したという理由だけで、C ランタイムがプログラムをクラッシュさせることが許されています。
  2. 一部のフォーマット指定子 (特に%s、このプログラムの後半で使用するもの!) は、安全でないのとまったく同じようgetsに安全ではありません。つまり、提供されたバッファーの末尾を超えて元気よく書き込みを行い、プログラムをクラッシュさせます (この特定のプログラムはセキュリティに問題があるようには見えません)。 -私には敏感ですが、自分のプログラムが少なくともある程度危険であるかのように常にコーディングする必要があります)。
  3. これらは、不正な入力を正しく処理することを非常に困難にします。

ユーザーから単一の負でない数値を読み取る正しい方法は次のとおりです。

unsigned long getul(void)
{
    char buf[80], *endp;
    unsigned long val;

    for (;;) {
        fgets(buf, 80, stdin);
        val = strtoul(buf, &endp, 10);
        if (endp != buf && *endp == '\n')
            return val;

        if (buf[strlen(buf)] != '\n')
            while (getchar() != '\n')
                /* discard */;
        fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
                ULONG_MAX);
    }
}

ULONG_MAX が大きすぎて 80 文字に収まらない人はいないため、ここでは固定サイズのバッファーを使用できます。

    students = (student*)malloc(num*sizeof(student));

calloc構造をディスクに書き込むときに、がらくたでいっぱいにならないように、ここで使用する必要があります。(または、Alexandros が提案するカスタム シリアライザーを作成すると、問題も回避されます。)

    for(i=0;i<num;++i){

好ましいスタイルはfor (i = 0; i < num; i++) {. 間隔に加えて、3 つの式すべてで同じ位置に表示されるsoi++の代わりに使用します。これにより、読みやすくなります。++ii

        student* ptr = students+i*sizeof(student);

student* ptr = students + i;他の場所で説明したように。

        printf("Name:");
        scanf("%s", ptr->cStdName);

上記のコメントを参照してください。次のように、別のヘルパー関数が必要です。

void getstr(char *buf, size_t len)
{
    size_t n;

    for (;;) {
        fgets(buf, len, stdin);
        n = strlen(buf);
        if (n < len && buf[n] == '\n') {
            memset(buf+n, 0, len-n);
            return;
        }
        while (getchar() != '\n')
            /* discard */;
        fprintf(stderr, "*** Enter no more than %lu characters.",
                (unsigned long)(len-1));
    }
}

...

        scanf("%f", &ptr->dStdAvg);

ここで、別のヘルパー関数が必要です。を使用する以外getfは とまったく同じです。もちろん、エラー メッセージは少し異なります。getulstrtod

    f = fopen("bin.bin","wb");

それは非常に一般的なファイル名ではありませんか? おそらく、ユーザーがそれを指定する方法があるはずです。

    fwrite(&num,sizeof(int),1,f);

ファイル形式にはマジック ナンバーが必要です。

    fwrite(students,sizeof(student),num,f);

CPU エンディアン順でバイナリ データをディスクに書き込んでいます。このアプリケーションでは問題にならないかもしれませんが、今後クロスプラットフォームの互換性の問題が発生する可能性があることに注意してください。(個人的には、あなたがしているように見えるものについては、JSON などのテキストのシリアル化、または sqlite などの単純なデーモンのないデータベースを使用します。)このファイル形式の潜在的な問題については、Alexandros の回答を参照してください。

ディスク上のファイルに書き込むときに問題になることはめったにありません。これは、出力がどこかにパイプされるようなプログラムではありませんが、それでも、fwrite提供したすべてのデータを書き込むことを保証するものではありません. 技術的にはfwrite、次のようなループで呼び出す必要があります。

size_t r, n = sizeof(student) * num;
char *p = (char *)students;
while (n > 0) {
    r = fwrite(p, 1, n, f);
    if (r == 0) break;  /* write error */
    n -= r;
    p += r;
}

これが正しく機能するためには、乗算を自分で行い、2 番目の引数として 1 を渡す必要fwriteがあります。そうしないと、短い書き込みが「データの要素」の途中で終了する可能性があり、これが発生したことを知る方法がありません。

    fclose(f);

ファイルを閉じる前に、書き込みエラーを確認してください。

if (ferror(f) || fclose(f)) {
    perror("bin.bin");
    return 1; /* unsuccessful exit */
}

...

    system("pause");

ただreturn 0。キーを押して終了させるプログラムは、バッチ処理に適していません。

于 2013-03-27T16:28:18.323 に答える