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);
scanf
、fscanf
、またはを使用しないsscanf
でください。理由は次のとおりです。
- 数値のオーバーフローは、未定義の動作を引き起こします。誰かがあまりにも多くの数字を入力したという理由だけで、C ランタイムがプログラムをクラッシュさせることが許されています。
- 一部のフォーマット指定子 (特に
%s
、このプログラムの後半で使用するもの!) は、安全でないのとまったく同じようgets
に安全ではありません。つまり、提供されたバッファーの末尾を超えて元気よく書き込みを行い、プログラムをクラッシュさせます (この特定のプログラムはセキュリティに問題があるようには見えません)。 -私には敏感ですが、自分のプログラムが少なくともある程度危険であるかのように常にコーディングする必要があります)。
- これらは、不正な入力を正しく処理することを非常に困難にします。
ユーザーから単一の負でない数値を読み取る正しい方法は次のとおりです。
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++
の代わりに使用します。これにより、読みやすくなります。++i
i
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
は とまったく同じです。もちろん、エラー メッセージは少し異なります。getul
strtod
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
。キーを押して終了させるプログラムは、バッチ処理に適していません。