C言語

ファイル入出力

データをプログラム終了後にも残し、読み書きの失敗を正しく扱いましょう。

01

ファイルを開いて閉じる

fopenは成功するとFILE *、失敗するとNULLを返します。使い終わったらfcloseし、その結果も確認します。

モード動作
r読み取り。存在しなければ失敗
w書き込み。既存内容を消す
a末尾へ追記
rbなどバイナリモード
02

テキストを書く

scores.txtを作るC
#include <stdio.h>
int main(void) {
    FILE *file = fopen("scores.txt", "w");
    if (file == NULL) { perror("scores.txt"); return 1; }
    if (fprintf(file, "Aki,82\nMika,91\n") < 0) {
        perror("書き込み"); fclose(file); return 1;
    }
    if (fclose(file) == EOF) { perror("閉じる"); return 1; }
    printf("保存しました\n");
    return 0;
}
実行結果OUTPUT
保存しました
scores.txtTEXT
Aki,82
Mika,91
03

一行ずつ読み込む

fgetsは配列サイズを指定できるため、一行を安全に読みやすい関数です。

内容を表示C
#include <stdio.h>
int main(void) {
    FILE *file = fopen("scores.txt", "r");
    if (file == NULL) { perror("scores.txt"); return 1; }
    char line[128];
    while (fgets(line, sizeof line, file) != NULL) fputs(line, stdout);
    if (ferror(file)) { perror("読み取り"); fclose(file); return 1; }
    fclose(file);
    return 0;
}
実行結果OUTPUT
Aki,82
Mika,91
04

読み取った値を変換する

簡単な形式ならsscanfで変換数を確認できます。ただし、この例は名前にカンマが含まれない独自形式であり、一般的なCSVを完全には扱いません。次のコードは、前の例でfgetsにより一行をlineへ読み込んだあとの、main関数内へ記述する部分です。

main関数内の解析部分C
char name[32];
int score;
if (sscanf(line, "%31[^,],%d", name, &score) == 2) {
    printf("%s: %d点\n", name, score);
} else {
    fprintf(stderr, "形式が正しくありません: %s", line);
}

Aki,82を解析すると「Aki: 82点」と表示されます。幅31は配列末尾のヌル文字分を残します。

05

バイナリデータを扱う

fwritefreadは指定したサイズと個数でデータを扱います。次のコードはstdio.hを読み込んだプログラムの、main関数内へ記述する部分です。

main関数内の保存部分C
int values[] = {10, 20, 30};
FILE *file = fopen("values.bin", "wb");
if (file == NULL) return 1;
size_t written = fwrite(values, sizeof values[0], 3, file);
if (written != 3) { fclose(file); return 1; }
fclose(file);
互換性 構造体や整数をそのまま保存した形式は、型サイズ、バイト順、パディングの違いで別環境から読めないことがあります。交換用データには形式を明確に定義します。
06

安全なファイル操作

確認すること
  • 利用者入力を検証せずパスへ使わない
  • wは既存内容を消すため、重要ファイルを直接上書きしない
  • 読み書き件数とferrorを確認する
  • 秘密情報を公開フォルダーへ保存しない
  • 文字コードと改行コードを利用側と揃える
PRACTICE

ミニ課題:学習記録

日付、学習内容、分数を一行ずつファイルへ追記し、全行を読み込んで合計時間を表示してください。不正な行は行番号とともに報告します。