[C言語のはじめ方] Part21: ファイル操作(fopen, fclose, fscanf, fprintf)

C言語

Step 5: ファイル入出力と標準ライブラリ – ファイル操作(fopen, fclose, fscanf, fprintf)

こんにちは!C言語学習の旅、順調に進んでいますか? 😊

これまでのステップで、基本的な文法からポインタ、構造体まで、C言語の重要な要素を学んできましたね。今回は、プログラムが外部のデータとやり取りするための重要な機能、「ファイル操作」について学びます。

プログラムを実行している間、計算結果やユーザーの入力はメモリ上にありますが、プログラムが終わると消えてしまいます。大切なデータを永続的に保存したり、大量のデータをプログラムで処理したりするには、「ファイル」への保存や読み込みが不可欠です。

このセクションでは、ファイル操作の基本中の基本である以下の4つの関数について、使い方をマスターしていきましょう!💪

  • fopen: ファイルを開く
  • fclose: ファイルを閉じる
  • fprintf: ファイルに書き込む
  • fscanf: ファイルから読み込む

1. ファイルを開く:fopen()

ファイルに対する読み書きを行うには、まずそのファイルを「開く(オープンする)」必要があります。ファイルを開くための関数が fopen() です。

fopen() は、ファイルを開くための重要な情報を管理する「ファイルポインタ」と呼ばれるものを返します。このファイルポインタは、FILE という特別な型(構造体)へのポインタです。私たちはこのポインタを通じて、ファイルに対する操作を行います。

#include <stdio.h> // fopen, fclose などを使うために必要

int main() {
    FILE *fp; // ファイルポインタを格納するための変数を宣言

    // "mydata.txt" というファイルを "書き込みモード("w")" で開く
    fp = fopen("mydata.txt", "w");

    if (fp == NULL) { // ファイルが開けなかった場合のエラー処理
        printf("ファイルを開けませんでした。\n");
        return 1; // エラー終了
    }

    // ... ここでファイルに対する処理を行う ...

    // ファイルを閉じる処理(後述)
    fclose(fp);

    return 0;
}

fopen() 関数の基本的な書式は以下の通りです。

FILE *fopen(const char *filename, const char *mode);
  • 第1引数 filename: 開きたいファイルの名前(パスを含むことも可能)を文字列で指定します。例: "mydata.txt", "C:\\Users\\MyUser\\Documents\\data.csv"
  • 第2引数 mode: ファイルをどのように開くかを指定するモード文字列です。よく使うモードには以下のようなものがあります。
    • "r" (read): 読み込み専用で開く。ファイルが存在しない場合はエラー (NULL を返す)。
    • "w" (write): 書き込み専用で開く。ファイルが存在する場合は中身を空にして上書き。存在しない場合は新規作成。
    • "a" (append): 追記書き込み専用で開く。ファイルが存在する場合は末尾に追加。存在しない場合は新規作成。
    • "r+": 読み書き両用で開く。ファイルが存在しない場合はエラー。
    • "w+": 読み書き両用で開く。ファイルが存在する場合は中身を空にして上書き。存在しない場合は新規作成。
    • "a+": 読み書き両用で追記モードで開く。ファイルが存在しない場合は新規作成。

    ※ これらに "b" を追加したモード (例: "rb", "wb") はバイナリモードと呼ばれ、画像ファイルなどを扱う際に使いますが、まずはテキストファイルを扱う上記のモードを覚えましょう。

⚠️ エラー処理の重要性
fopen() は、ファイルが存在しない(読み込みモードの場合)、書き込み権限がないなどの理由でファイルを開けないことがあります。その場合、fopen()NULL という特別な値を返します。プログラムでは、fopen() の戻り値が NULL かどうかを必ずチェックし、NULL であればエラーメッセージを表示して終了するなどの適切な処理を行う必要があります。これを怠ると、NULL ポインタへのアクセスが発生し、プログラムがクラッシュする原因となります。

2. ファイルに書き込む:fprintf()

ファイルを開いたら、次はデータを書き込んでみましょう。ファイルに書式を指定してデータを書き込むには fprintf() 関数を使います。

fprintf() は、これまで画面に出力するために使ってきた printf() と非常によく似ています。違いは、最初の引数に書き込み先のファイルを示すファイルポインタを指定する点だけです。

int fprintf(FILE *stream, const char *format, ...);
  • 第1引数 stream: fopen() で取得したファイルポインタを指定します。
  • 第2引数 format: printf() と同じ書式指定文字列です。%d (整数), %f (浮動小数点数), %s (文字列), %c (文字) などが使えます。
  • 第3引数以降 ...: 書式指定文字列に対応する値を指定します。

以下は、ファイルにいくつかのデータを書き込む例です。

#include <stdio.h>

int main() {
    FILE *fp;
    char name[] = "Taro";
    int age = 20;
    double height = 175.5;

    fp = fopen("profile.txt", "w"); // 書き込みモードで開く
    if (fp == NULL) {
        printf("ファイルを開けませんでした。\n");
        return 1;
    }

    // ファイルに書き込む
    fprintf(fp, "名前: %s\n", name);
    fprintf(fp, "年齢: %d 歳\n", age);
    fprintf(fp, "身長: %.1f cm\n", height); // 小数点以下1桁まで表示

    printf("profile.txt にデータを書き込みました。\n");

    fclose(fp); // ファイルを閉じる

    return 0;
}

このプログラムを実行すると、同じディレクトリに profile.txt というファイルが作成され、以下のような内容が書き込まれます。

名前: Taro
年齢: 20 歳
身長: 175.5 cm

3. ファイルから読み込む:fscanf()

ファイルに書き込んだデータをプログラムで読み込むには fscanf() 関数を使います。

fscanf() は、キーボードからの入力を受け取る scanf() とよく似ています。違いは、最初の引数に読み込み元のファイルを示すファイルポインタを指定する点です。

int fscanf(FILE *stream, const char *format, ...);
  • 第1引数 stream: fopen() で取得したファイルポインタを指定します。
  • 第2引数 format: scanf() と同じ書式指定文字列です。読み込みたいデータの型に合わせて指定します。
  • 第3引数以降 ...: 読み込んだデータを格納する変数のアドレス (&変数名) を指定します。

fscanf() は、書式指定に従ってデータを読み込み、成功した場合は読み込んだ項目の数を返します。ファイルの終端に達した場合や、読み込みエラーが発生した場合は EOF (End Of File) という特別な値(通常は -1)を返します。

先ほど作成した profile.txt からデータを読み込む例を見てみましょう。

#include <stdio.h>

int main() {
    FILE *fp;
    char name[50]; // 文字列を格納するのに十分なサイズの配列を用意
    int age;
    double height;
    int itemsRead;

    fp = fopen("profile.txt", "r"); // 読み込みモードで開く
    if (fp == NULL) {
        printf("ファイルを開けませんでした。\n");
        return 1;
    }

    // ファイルからデータを読み込む
    // 注意: fscanf は空白文字(スペース、タブ、改行)を区切りとして読み込むため、
    //       元のファイルの形式に合わせて書式指定を行う必要がある。
    //       この例では簡単化のため、各データが別々の行にあることを前提とせず、
    //       単純に文字列、整数、浮動小数点数を読み込む試み。
    //       より確実な方法は後続の fgets などで学ぶ。

    // "名前: Taro" から "Taro" を読み込む試み (期待通りに動かない可能性あり)
    itemsRead = fscanf(fp, "名前: %s", name);
    if (itemsRead == 1) {
         printf("読み込んだ名前: %s\n", name);
    } else {
        printf("名前の読み込みに失敗またはファイル終端\n");
        fclose(fp);
        return 1;
    }

    // "年齢: 20 歳" から 20 を読み込む試み
    itemsRead = fscanf(fp, " 年齢: %d 歳", &age); // 先頭にスペースを入れて改行を読み飛ばす試み
     if (itemsRead == 1) {
         printf("読み込んだ年齢: %d\n", age);
    } else {
        printf("年齢の読み込みに失敗またはファイル終端\n");
        fclose(fp);
        return 1;
    }

     // "身長: 175.5 cm" から 175.5 を読み込む試み
    itemsRead = fscanf(fp, " 身長: %lf cm", &height); // double型の場合は %lf を使う
     if (itemsRead == 1) {
         printf("読み込んだ身長: %.1f\n", height);
    } else {
        printf("身長の読み込みに失敗またはファイル終端\n");
        fclose(fp);
        return 1;
    }

    fclose(fp); // ファイルを閉じる

    return 0;
}
💡 fscanf の注意点と EOF

fscanf() は、書式指定文字列に完全に一致する形でデータがファイルに書かれていないと、期待通りに読み込めないことがあります。特に空白や改行の扱いは注意が必要です。

また、ファイルからデータを繰り返し読み込む場合、いつファイルの終わりに達したかを判定する必要があります。fscanf() の戻り値が EOF かどうかをチェックすることで、ファイルの終端を検出できます。ループを使ってファイル全体を読み込む際によく使われるパターンです。

int value;
while (fscanf(fp, "%d", &value) != EOF) {
    // value を使った処理
    printf("読み込んだ値: %d\n", value);
}
// ループが終了したら、ファイルの終端に達したか、
// もしくは読み込みエラーが発生したかのどちらか。
// feof() や ferror() 関数で詳細を確認できる(今回は省略)。

ただし、fscanf は予期しない入力形式に弱いため、より安全なファイル読み込み方法として、次のセクションで学ぶ fgets などが推奨される場合も多いです。


4. ファイルを閉じる:fclose()

ファイルを開いて操作が終わったら、必ずファイルを「閉じる(クローズする)」必要があります。ファイルを閉じるための関数が fclose() です。

int fclose(FILE *stream);
  • 引数 stream: 閉じるファイルのファイルポインタを指定します。

fclose() を呼び出すと、以下のような重要な処理が行われます。

  • バッファのフラッシュ: ファイルへの書き込みは、効率化のために一時的にメモリ(バッファ)に溜め込まれることがあります。fclose() は、このバッファに残っているデータを確実にファイルに書き出します(フラッシュ)。
  • リソースの解放: OSに対して、このファイルの使用が完了したことを伝え、関連するリソース(ファイルポインタなど)を解放します。
🚨 fclose() を忘れないで!

fclose() を呼び忘れると、以下のような問題が発生する可能性があります。

  • 書き込んだはずのデータがファイルに完全には保存されない(バッファに残ったままになる)。
  • プログラムが同時に開けるファイル数には上限があるため、不要なファイルを開いたままにしておくと、新しいファイルを開けなくなる可能性がある。
  • OSのリソースを無駄に消費し続ける。

fopen() を使ったら、必ず対応する fclose() を呼び出す習慣をつけましょう!🤝

#include <stdio.h>

int main() {
    FILE *fp;

    fp = fopen("temp.txt", "w");
    if (fp == NULL) {
        printf("ファイルを開けませんでした。\n");
        return 1;
    }

    fprintf(fp, "これは一時的なデータです。\n");

    // ファイル操作が終わったら必ず閉じる
    if (fclose(fp) == 0) {
        printf("ファイルを正常に閉じました。\n");
    } else {
        printf("ファイルのクローズに失敗しました。\n");
        // エラー処理 (必要であれば)
    }

    return 0;
}

fclose() は、成功すれば 0 を、失敗すれば EOF を返します。通常、fclose() の失敗は稀ですが、ディスクがいっぱいになった場合などに発生する可能性があります。


まとめ

今回は、C言語におけるファイル操作の基本として、以下の4つの関数を学びました。

  • FILE *fopen(const char *filename, const char *mode); : ファイルを開き、ファイルポインタを取得する。モード指定とエラーチェックが重要。
  • int fprintf(FILE *stream, const char *format, ...); : ファイルに書式付きでデータを書き込む。printf と似ているが、第一引数にファイルポインタを指定する。
  • int fscanf(FILE *stream, const char *format, ...); : ファイルから書式付きでデータを読み込む。scanf と似ているが、第一引数にファイルポインタを指定する。戻り値で読み込み成功数や EOF を確認する。
  • int fclose(FILE *stream); : 開いたファイルを閉じる。バッファのフラッシュとリソース解放のために必須。

ファイル操作の基本的な流れは、「開く → 読み書きする → 閉じる」です。この流れと、各関数の役割、特にエラー処理の重要性をしっかりと理解しておきましょう。

これで、プログラムがファイルとデータをやり取りする第一歩を踏み出しました! 🎉 次回は、一行ずつファイルを読み書きする fgets()fputs() など、さらに便利なファイル操作関数について学んでいきます。

コメント

タイトルとURLをコピーしました