ファイル入出力
ファイルは金庫のようなものです——使う前に開け(fopen)、出し入れのルールに従い(読み書きモード)、使い終わったら必ず閉め(fclose)なければ、データが失われる恐れがあります。
ファイルの基本概念
C言語はすべての外部デバイスを「ストリーム」として扱います。ファイルストリームには2種類あります:
- テキストストリーム:可視文字で構成され、改行文字が変換される場合があり、人間が読むのに適しています
- バイナリストリーム:生のバイト列であり、そのまま読み書きされ、プログラム処理に適しています
オペレーティングシステムはFILE構造体で開いているファイルを管理し、私たちはFILE *ポインタを通じて操作します。
ファイルポインタ
FILE *fp;
このポインタは、その後のすべてのファイル操作の「鍵」です。これなしでは何もできません。
ファイルのオープンとクローズ
fopen
FILE *fopen(const char *filename, const char *mode);
| モード | 意味 | ファイルが存在しない場合 | ファイルが存在する場合 |
|---|---|---|---|
"r" |
読み取り専用 | エラー | 先頭から読み取り |
"w" |
書き込み専用 | 作成 | 内容を切り詰め |
"a" |
追記 | 作成 | 末尾に追記 |
"r+" |
読み書き | エラー | 先頭から操作 |
"w+" |
読み書き | 作成 | 内容を切り詰め |
"a+" |
読み取りと追記 | 作成 | 末尾に追記 |
"rb" |
バイナリ読み取り専用 | エラー | 先頭から読み取り |
"wb" |
バイナリ書き込み専用 | 作成 | 内容を切り詰め |
"ab" |
バイナリ追記 | 作成 | 末尾に追記 |
"w"モードは既存のファイルを切り詰めます!初心者が最もよく犯す間違いであり、追記のつもりで"w"を使うと、すべてのデータが失われます。
fclose
int fclose(FILE *fp);
ファイルを閉じるとバッファがフラッシュされ、リソースが解放されます。成功すると0、失敗するとEOFを返します。
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
printf("ファイルを開けませんでした\n");
return 1;
}
fprintf(fp, "Hello, File!\n");
fclose(fp);
return 0;
}
fopenには対応するfcloseが必要です。ファイルのクローズ忘れはメモリリークやデータ損失を引き起こします。
テキストファイルの入出力
文字入出力:fgetc / fputc
int fgetc(FILE *fp);
int fputc(int ch, FILE *fp);
fgetcは1文字読み取り、ファイル終端でEOFを返します。fputcは1文字書き込みます。
文字列入出力:fgets / fputs
char *fgets(char *str, int n, FILE *fp);
int fputs(const char *str, FILE *fp);
fgetsは最大n-1文字を読み取り、改行またはファイル終端で停止し、自動的に'\0'を付加します。
#include <stdio.h>
int main(void) {
FILE *fp = fopen("poem.txt", "w");
if (fp == NULL) {
return 1;
}
fputs("夕暮れの河辺\n", fp);
fputs("河は海へと流れる\n", fp);
fclose(fp);
fp = fopen("poem.txt", "r");
if (fp == NULL) {
return 1;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);
return 0;
}
夕暮れの河辺
河は海へと流れる
バイナリファイルの入出力
fread / fwrite
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);
パラメータ:ptrはデータバッファ、sizeは各要素のバイトサイズ、countは要素数です。戻り値は実際に読み書きされた要素数です。
#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Student;
int main(void) {
Student stu = {1, "Alice", 92.5f};
FILE *fp = fopen("student.dat", "wb");
if (fp == NULL) {
return 1;
}
fwrite(&stu, sizeof(Student), 1, fp);
fclose(fp);
Student read_stu;
fp = fopen("student.dat", "rb");
if (fp == NULL) {
return 1;
}
fread(&read_stu, sizeof(Student), 1, fp);
fclose(fp);
printf("ID: %d, Name: %s, Score: %.1f\n",
read_stu.id, read_stu.name, read_stu.score);
return 0;
}
ID: 1, Name: Alice, Score: 92.5
フォーマット入出力
fprintf / fscanf
int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
使い方はprintf/scanfと同じで、ファイルポインタのパラメータが追加されているだけです。
#include <stdio.h>
int main(void) {
FILE *fp = fopen("scores.txt", "w");
if (fp == NULL) {
return 1;
}
fprintf(fp, "%s %d %.1f\n", "Bob", 2, 88.0);
fprintf(fp, "%s %d %.1f\n", "Carol", 3, 95.5);
fclose(fp);
fp = fopen("scores.txt", "r");
if (fp == NULL) {
return 1;
}
char name[20];
int id;
float score;
while (fscanf(fp, "%s %d %f", name, &id, &score) == 3) {
printf("Name: %s, ID: %d, Score: %.1f\n", name, id, score);
}
fclose(fp);
return 0;
}
Name: Bob, ID: 2, Score: 88.0
Name: Carol, ID: 3, Score: 95.5
ファイル位置ポインタ
開いているファイルにはすべて「位置ポインタ」があり、現在の読み書き位置を示しています。読書のしおりのようなもので、どこまで読んだかをしおりが示しています。
fseek
int fseek(FILE *fp, long offset, int origin);
| origin | 意味 |
|---|---|
SEEK_SET |
ファイルの先頭 |
SEEK_CUR |
現在の位置 |
SEEK_END |
ファイルの末尾 |
ftell / rewind
long ftell(FILE *fp);
void rewind(FILE *fp);
ftellは現在の位置(ファイル先頭からのバイトオフセット)を返します。rewindはfseek(fp, 0, SEEK_SET)と等価です。
例
fseekとftellを使ってファイルサイズを計算します:
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "rb");
if (fp == NULL) {
printf("ファイルを開けませんでした\n");
return 1;
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
printf("ファイルサイズ: %ld バイト\n", size);
fclose(fp);
return 0;
}
例
バイナリファイル内の特定レコードへのランダムアクセス:
#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Student;
int main(void) {
Student students[3] = {
{1, "Alice", 92.5f},
{2, "Bob", 88.0f},
{3, "Carol", 95.5f}
};
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
return 1;
}
fwrite(students, sizeof(Student), 3, fp);
fclose(fp);
fp = fopen("students.dat", "rb");
if (fp == NULL) {
return 1;
}
int target = 2;
fseek(fp, (target - 1) * sizeof(Student), SEEK_SET);
Student s;
fread(&s, sizeof(Student), 1, fp);
fclose(fp);
printf("Record %d: ID=%d, Name=%s, Score=%.1f\n",
target, s.id, s.name, s.score);
return 0;
}
Record 2: ID=2, Name=Bob, Score=88.0
その他のファイル関数
feof
int feof(FILE *fp);
ファイル終端に達したかどうかを確認します。EOFで非ゼロを返します。注意:feofは読み取り操作が失敗した後でのみ真になり、事前チェックとしては使えません。
fflush
int fflush(FILE *fp);
バッファを強制的にフラッシュし、バッファリングされたデータをファイルに書き込みます。入力ストリームに対してfflushを呼び出すと未定義動作になります。
remove / rename
int remove(const char *filename);
int rename(const char *old, const char *new);
ファイルの削除と名前変更です。成功すると0を返します。
ファイル操作の基本パターン
ほとんどのファイル操作は次のパターンに従います:
fopenでファイルを開き、戻り値を確認する- ループでデータを読み書きする
fcloseでファイルを閉じる
#include <stdio.h>
int main(void) {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
fclose(fp);
return 0;
}
fopenの戻り値確認は絶対的なルールです!ファイルが存在しない、権限がない、ディスクがいっぱい等の可能性があり——確認せずにヌルポインタを使うと即座にクラッシュします。
❓ よくある質問
\nと\r\nの変換を行いますが、バイナリモードは変換を行いません。Linuxでは両者に違いはありません。📖 まとめ
fopenでファイルを開き、必ず戻り値がNULLでないか確認する- テキストモードでは
fgets/fputs、fprintf/fscanfを、バイナリモードではfread/fwriteを使う "w"モードはファイルを切り詰め、"a"モードは末尾に追記する——混同しないように注意fseek/ftell/rewindでファイル位置ポインタを制御し、ランダムアクセスを実現する- すべての
fopenには対応するfcloseが必要——習慣にする
📝 練習問題
- テキストファイルの行数、単語数、文字数を数えるプログラムを作成してください
- バイナリモードでソースファイルを読み取り、宛先ファイルに書き出す簡単なファイルコピープログラムを作成してください
- 構造体配列をバイナリファイルに保存し、再度読み出して表示するプログラムを作成してください



