Step 5: ファイル入出力と標準ライブラリ 📂
行単位の入出力:fgets と fputs をマスターしよう!
こんにちは! C言語学習、順調に進んでいますか? 😊 Step 5 ではファイル操作について学んでいますね。前回は fscanf
や fprintf
を使って、書式を指定してファイルからデータを読み書きする方法を学びました。
今回は、ファイルから1行ずつ文字列を読み込んだり、ファイルに文字列(行)を書き込んだりするための重要な関数、fgets
と fputs
について詳しく見ていきましょう。これらはテキストファイルの処理、特に設定ファイルやログファイルの読み書きなどで非常によく使われます。📝
1. ファイルから1行読み込む: fgets 関数
ファイルからデータを読み込む際、特定の書式ではなく、単純に「1行分」のテキストをまとめて取得したい場面が多くあります。例えば、テキストファイルの内容をそのまま画面に表示したり、特定のキーワードを含む行を探したりする場合です。そんな時に活躍するのが fgets
関数です。
fgets 関数の書式
fgets
関数の基本的な形は次のようになります。
char *fgets(char *str, int n, FILE *stream);
引数を一つずつ見てみましょう。
char *str
: 読み込んだ文字列を格納するための文字配列(バッファ)へのポインタを指定します。ここに読み込まれた文字列が格納されます。int n
: 読み込むことができる最大文字数を指定します。これは、str
で指定した配列のサイズ(要素数)を指定するのが一般的です。重要なのは、実際に読み込まれるのは最大でもn-1
文字までということです。最後の1文字分は、文字列の終わりを示すヌル文字 (\0
) を格納するために確保されます。FILE *stream
: どのファイルから読み込むかを指定するファイルポインタです。fopen
関数で開いたファイルのポインタを指定します。
fgets 関数の動作 🤔
fgets
は、指定されたファイルストリーム (stream
) から、以下のいずれかの条件が満たされるまで文字を読み込み、指定されたバッファ (str
) に格納します。
- 改行文字 (
\n
) が読み込まれた場合: 改行文字も文字列の一部としてバッファに格納されます。 n-1
文字が読み込まれた場合: 指定された最大文字数に達した場合です。- ファイルの終端 (EOF) に達した場合: ファイルの最後まで読み込んだ場合です。
読み込みが完了すると、fgets
は自動的にバッファの最後にヌル文字 (\0
) を追加します。これにより、バッファに格納された内容がC言語の文字列として正しく扱えるようになります。
戻り値について
fgets
関数は、以下の値を返します。
- 成功した場合: 文字列が格納されたバッファ (
str
) へのポインタを返します。これは、第1引数で渡したものと同じ値です。 - 失敗した場合、またはファイルの終端に既に達していた場合:
NULL
ポインタを返します。
したがって、fgets
をループで使う場合は、戻り値が NULL
かどうかをチェックすることで、ファイルの終端まで読み込んだか、あるいはエラーが発生したかを判断できます。✅
💡 fgets の重要なポイント
- バッファオーバーフローに強い:
fgets
は読み込む最大文字数 (n
) を指定するため、用意したバッファのサイズを超えるデータが読み込まれてメモリを破壊する「バッファオーバーフロー」を防ぐことができます。これは非常に重要な特徴です。 - 改行文字も読み込む: 読み込んだ行に改行文字 (
\n
) が含まれている場合、それも文字列の一部としてバッファに格納されます。 - ヌル文字で終端: 読み込んだ文字列の末尾には必ずヌル文字 (
\0
) が追加されます。 gets
関数は使わないで!: 以前はgets
という標準入力から1行読み込む関数がありましたが、これは読み込む文字数を指定できないため、バッファオーバーフローの脆弱性を引き起こす非常に危険な関数です。絶対に使用しないでください。fgets
を使いましょう。
fgets の使用例
それでは、実際に fgets
を使ってファイルの内容を1行ずつ読み込み、画面に表示する簡単なプログラムを見てみましょう。
まず、以下のような内容のテキストファイル input.txt
を用意してください。
Hello, C Language!
Let's learn fgets function.
This is the third line.
次に、このファイルを読み込む C のコードです。
#include <stdio.h>
#define BUFFER_SIZE 256 // バッファサイズを定義
int main() {
FILE *fp;
char buffer[BUFFER_SIZE]; // 文字列を読み込むためのバッファ
// ファイルを読み込みモードで開く
fp = fopen("input.txt", "r");
if (fp == NULL) {
perror("Error opening file"); // エラーメッセージを表示
return 1; // エラー終了
}
printf("Reading from file:\n");
printf("--------------------\n");
// fgetsで1行ずつ読み込み、NULLが返されるまで(ファイルの終わりまで)繰り返す
while (fgets(buffer, BUFFER_SIZE, fp) != NULL) {
// 読み込んだ行を表示
printf("%s", buffer); // bufferには改行が含まれている可能性があるので、printfでは\nをつけない
}
printf("--------------------\n");
printf("Finished reading file.\n");
// ファイルを閉じる
if (fclose(fp) != 0) {
perror("Error closing file");
return 1; // エラー終了
}
return 0; // 正常終了
}
このコードを実行すると、input.txt
の内容が1行ずつ読み込まれ、画面に表示されます。BUFFER_SIZE
は、1行の最大文字数を想定して決めます。ここでは256文字としていますが、必要に応じて調整してください。fgets
のループ条件で != NULL
をチェックしている点にも注目してください。これにより、ファイルの終端まで安全に読み込めます。
2. ファイルに文字列(行)を書き込む: fputs 関数
次に、ファイルに文字列(通常は1行分のテキスト)を書き込むための fputs
関数について学びましょう。fprintf
のように書式を指定する必要がなく、単純に文字列をそのままファイルに書き込みたい場合に便利です。
fputs 関数の書式
fputs
関数の基本的な形は以下の通りです。
int fputs(const char *str, FILE *stream);
引数を見てみましょう。
const char *str
: ファイルに書き込みたい文字列へのポインタを指定します。この文字列はヌル文字 (\0
) で終わっている必要がありますが、ヌル文字自体はファイルには書き込まれません。FILE *stream
: どのファイルに書き込むかを指定するファイルポインタです。fopen
関数で書き込みモード(”w”, “a” など)で開いたファイルのポインタを指定します。
fputs 関数の動作 🤔
fputs
は、指定された文字列 (str
) を、ヌル文字 (\0
) に到達するまで、指定されたファイルストリーム (stream
) に書き込みます。重要な点として、fputs
は自動的に改行文字 (\n
) を追加しません。もし行末に改行を入れたい場合は、書き込む文字列 str
の中にあらかじめ含めておく必要があります。
戻り値について
fputs
関数は、以下の値を返します。
- 成功した場合: 負でない値(通常は 0)を返します。
- 失敗した場合:
EOF
(End Of File、通常は -1 として定義される定数) を返します。
書き込みエラーが発生したかどうかを確認するには、戻り値が EOF
かどうかをチェックします。✅
⚠️ fputs の注意点
- 改行は自動で追加されない: これが
fputs
の最も重要な特徴の一つです。改行が必要な場合は、文字列の末尾に\n
を付けておく必要があります。 - ヌル文字は書き込まない: 文字列の終端を示す
\0
はファイルには書き込まれません。 fprintf
との違い:fprintf
は書式指定文字列を使って様々なデータ型を整形して書き込めますが、fputs
は単純に文字列をそのまま書き込むための関数です。
fputs の使用例
では、fputs
を使ってファイルに複数の行を書き込む簡単なプログラムを見てみましょう。
#include <stdio.h>
int main() {
FILE *fp;
// ファイルを書き込みモードで開く(ファイルが存在しない場合は新規作成、存在する場合は上書き)
fp = fopen("output.txt", "w");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
printf("Writing to file...\n");
// 1行目(改行を含む)を書き込む
if (fputs("This is the first line.\n", fp) == EOF) {
perror("Error writing to file");
fclose(fp); // エラーが発生してもファイルを閉じる試みはする
return 1;
}
// 2行目(改行を含む)を書き込む
if (fputs("fputs example.\n", fp) == EOF) {
perror("Error writing to file");
fclose(fp);
return 1;
}
// 3行目(改行を含まない)を書き込む
if (fputs("This line has no newline character.", fp) == EOF) {
perror("Error writing to file");
fclose(fp);
return 1;
}
// 必要なら、改行だけを書き込むこともできる
if (fputs("\n", fp) == EOF) {
perror("Error writing to file");
fclose(fp);
return 1;
}
printf("Finished writing to file.\n");
// ファイルを閉じる
if (fclose(fp) != 0) {
perror("Error closing file");
return 1;
}
return 0;
}
このコードを実行すると、output.txt
というファイルが作成(または上書き)され、3行のテキストが書き込まれます。fputs
の呼び出しごとに、書き込みが成功したか(戻り値が EOF
でないか)をチェックすることが推奨されます。文字列の末尾に \n
を含めることで、ファイル内で改行されることに注意してください。
3. 実践! fgets と fputs でファイルコピー
fgets
と fputs
の使い方を理解したところで、これらを組み合わせて、あるファイルの内容を別のファイルに1行ずつコピーするプログラムを作成してみましょう。これは非常に実用的な例です。
#include <stdio.h>
#define BUFFER_SIZE 256
int main() {
FILE *source_fp, *dest_fp;
char buffer[BUFFER_SIZE];
// コピー元ファイルを開く(読み込みモード)
source_fp = fopen("input.txt", "r");
if (source_fp == NULL) {
perror("Error opening source file");
return 1;
}
// コピー先ファイルを開く(書き込みモード)
dest_fp = fopen("output_copy.txt", "w");
if (dest_fp == NULL) {
perror("Error opening destination file");
fclose(source_fp); // 開いたファイルを閉じる
return 1;
}
printf("Copying file contents...\n");
// source_fp から1行読み込み、bufferに格納
// fgetsがNULLを返すまで(ファイルの終わりまで)繰り返す
while (fgets(buffer, BUFFER_SIZE, source_fp) != NULL) {
// 読み込んだ行 (buffer) を dest_fp に書き込む
if (fputs(buffer, dest_fp) == EOF) {
perror("Error writing to destination file");
fclose(source_fp);
fclose(dest_fp);
return 1;
}
}
// ファイルの終端に達したか、fgetsでエラーが発生したかを確認
if (ferror(source_fp)) {
fprintf(stderr, "Error reading from source file.\n");
fclose(source_fp);
fclose(dest_fp);
return 1;
}
printf("File copied successfully!\n");
// 両方のファイルを閉じる
if (fclose(source_fp) != 0) {
perror("Error closing source file");
// dest_fp も閉じるべきだが、エラー処理は簡略化
}
if (fclose(dest_fp) != 0) {
perror("Error closing destination file");
}
return 0;
}
このプログラムは、まず input.txt
を読み込みモード ("r"
) で、output_copy.txt
を書き込みモード ("w"
) で開きます。そして、while
ループの中で fgets
を使って input.txt
から1行ずつ読み込み、その内容を fputs
を使って output_copy.txt
に書き込んでいます。fgets
が NULL
を返したら(ファイルの終端)、ループが終了します。最後に両方のファイルを閉じて完了です。
fgets
は改行文字 \n
も読み込んでくれるので、fputs
でそのまま書き込めば、元のファイルの改行構造が維持されるのがポイントです。
また、ループ終了後に `ferror` 関数を使って、fgetsがNULLを返した理由がファイル終端(EOF)なのか、それとも読み込みエラーなのかを確認しています。これはより堅牢なエラー処理のために役立ちます。
まとめ ✨
今回は、ファイルから行単位で文字列を読み込む fgets
と、ファイルに文字列(行)を書き込む fputs
について学びました。
- fgets:
- ファイルから1行(または指定最大文字数-1まで)を読み込む。
- 改行文字(
\n
)も読み込む。 - 読み込んだ文字列の末尾にヌル文字(
\0
)を付加する。 - バッファサイズを指定するため、バッファオーバーフローに対して安全。
- ファイルの終端またはエラーで
NULL
を返す。
- fputs:
- ファイルに文字列を書き込む。
- 改行文字(
\n
)を自動で追加しない。 - 文字列終端のヌル文字(
\0
)は書き込まない。 - 書き込みエラーで
EOF
を返す。
これらの関数は、テキストファイルを扱う上で非常に基本的かつ重要なものです。特に fgets
は、安全な入力処理の基本となる関数ですので、しっかり使い方をマスターしておきましょう。
次回は、文字列を操作するための便利な標準ライブラリ関数 (strlen
, strcpy
, strcat
など) について学びます。お楽しみに!🚀
コメント