このページではC言語でのファイルの入出力(読み込み・書き込み)について解説します。
Contents
ファイル入出力
まずはファイル入出力の流れについて解説していきたいと思います。
一般的に、ファイルの中身を読み込んだり、ファイルを編集したりするときは下記の流れで行うと思います。
- ファイルのオープン(ファイルを開く)
- ファイルからの読み込み or ファイルへの書き込み
- ファイルのクローズ(ファイルを閉じる)
C言語においても全く処理の流れは同じです。つまり、下記の3つのステップでファイルの入出力処理を行います。
- ファイルのオープン(ファイルを開く)
- ファイルからの読み込み or ファイルへの書き込み
- ファイルのクローズ(ファイルを閉じる)
ストリームと FILE
構造体
C言語でのファイル入出力において重要な要素が2つあります。それはストリームと FILE 構造体です。
ストリーム
ストリームとはデータの流れを表す言葉です。
Apple Music や spotify などの音楽配信サービスをストリーミングサービスと言ったりしますよね?ここで使われてるストリーム(ストリーミング)と同じ意味です。
ユーザーが音楽を再生したとき、このストリーミングサービスでは下記の処理が繰り返し行われることになります。
- 再生位置の音楽データを受信してバッファ(メモリ)に格納する
- 受信した音楽データを音として再生する
- 次の再生位置のデータを受信したバッファ(メモリ)に上書きする
- 受信した音楽データを音として再生する
- ・・・・・
ここでバッファのデータに注目すると、ちょうど川の流れのように、必要なデータがどんどん流れていくことになります。聴き終わったデータはどんどんバッファから流れ出ていく感じですね。
こういったデータが連続してどんどん流れていくバッファのことをストリームといいます。音楽配信サービスもストリームを利用したサービスなのでストリーミングサービスと呼ばれたりするんです。
で、C言語のファイル入出力においても同様にストリームを利用して処理が行われます。
FILE
構造体
前述の通り、ファイルの入出力においては、まずファイルのオープンを行います。これは実際には、ファイルとプログラムの間にストリームを作成する処理になります。
ストリームにはファイルのデータの一部が格納され、そのストリームに対して処理を行うことで、ファイルからのデータの読み込みや、ファイルへのデータの書き込みを行うことができます。
そして、そのストリームの情報を管理するのが FILE
構造体です。ストリームのサイズやストリームバッファへのポインタなどの情報も、この FILE
構造体に格納されています。
ただし、C言語では一般的に FILE
構造体のデータを自分で作成したり変更するようなことは行わないです。標準関数を利用して FILE
構造体を扱うことになります。
例えば fopen
という標準関数を使用することで自動的に FILE
構造体が作成されますし、fread
や fwrite
などの標準関数を使用することで、その FILE
構造体に紐付けされたストリームからデータを読み込んだり書き込んだりすることができます(これらの関数の説明については後述します)。
また、先ほどストリームはデータの流れる川のようなものだと言いましたが、このデータの流れも標準関数内で実現してくれます。例えばストリーム内のデータを全て読み込んだような場合には、標準関数内で次に読み込むべきデータをストリームに移動させてきてくれます。
逆に自分で FILE
構造体を変更してストリームを制御しようと思うとめちゃめちゃ大変です。FILE
構造体のメンバのデータにアクセスすることも可能ですが、自分で変更することは避けた方が良いです。
ですので、C言語でファイル入出力を行う場合は、すでに用意されている標準関数(fopen
、fread
、fgets
などなど)を素直に使うのが良いと思います。FILE
構造体のアドレスさえ渡せば、関数内で良い感じにストリームの制御をしてくれます。
また、ここまでストリームについて解説してきましたが、実は標準関数を使用することでこのストリームを意識することなく、あたかもファイルに直接アクセスしているかのようにファイルの入出力を行うこともできます。
ただし、ストリームなのでデータがどんどん流れていく点だけは注意しましょう。例えば fread
関数などでデータを読み込んだ場合、その読み込んだデータはストリームからどんどん吐き出されていきます。この辺りは、ここから解説していく各関数の説明の中でも説明していこうと思います。
スポンサーリンク
ファイルのオープン
ファイルのオープン(ファイルを開く)に用いる代表的な関数はfopen
関数です。前述の通り、ファイルをオープンすることでストリームが作成され、そのストリームからデータをファイルを読み込んだり書き込んだりすることができるようになります。
fopen
関数
fopen
関数はファイルをオープンしてストリームを作成する関数です。
#include <stdio.h>
FILE* fopen(const char*, const char*);
fopen
関数の引数
fopen
関数の引数には下記を指定します。
- 第一引数:開きたいファイル名(の文字列)
- 第二引数:ファイルを開くモード(の文字列)
ファイルを開くモードの基本となるのは下記の3つです。
"r"
:読み込みモード- ファイルが存在しない場合はエラー
"w"
:書き込みモード- ファイルが存在しない場合はファイルを作成する
- ファイルが存在する場合はファイルの先頭から上書きする
"a"
:追記モード- ファイルが存在しない場合はファイルを作成する
- ファイルが存在する場合はファイルの最後から追記する
さらに各モードの後ろに +
を付加することで下記のように読み込みも書き込みも可能なモードになります。
"r+"
:読み込み書き込みモード- ファイルが存在しない場合はエラー
"w+"
:読み込み書き込みモード- ファイルが存在しない場合はファイルを作成する
- ファイルが存在する場合はファイルの先頭から上書きする
"a+"
:読み込み追記モード- ファイルが存在しない場合はファイルを作成する
- ファイルが存在する場合はファイルの最後から追記する
"r+"
と "w+"
は同じようなモードになりますが、ファイルの有無により動作が異なります。"r+"
は開くファイルが無い時はエラーになりますが、"w+"
はエラーになりません。
また、ここまで解説してきたモードにさらに b
を付加することでファイルをバイナリモードで開くことが可能です。
テキストモードとバイナリモードの大きな違いは、開いたファイルへの操作時に OS によって異なる改行コードを考慮して処理してくれるかどうかの違いです。
例えば "Hello, World\n"
という文字列をファイルに書き出すとき、テキストモードでは下記のように動作が異なります。
- Mac:
'\n'
をそのまま ‘\n’ で出力 - Windows:
'\n'
を'\r\n'
に置き替えて出力
つまり、テキストモードでは改行コードを OS に応じて変換して処理してくれるというわけです。
バイナリモードの場合はこのような変換は行わず、バイナリモードでは指定されたデータをそのまま出力します。
- Mac:
'\n'
をそのまま'\n'
で出力 - Windows:
'\n'
をそのまま'\n'
で出力
基本的に、
- テキストファイル(文字列のみから作られるファイル)にはテキストモード
- それ以外にはバイナリモード
を使用する考え方で良いと思います。
fopen
関数の戻り値
fopen
関数の戻り値は下記の通りです。
- 成功時:ストリームの情報が格納された
FILE
構造体のアドレス - 失敗時:
NULL
第一引数で指定されたファイルのオープンに成功した場合は、そのファイル関連づけられたストリームの情報を格納した FILE
構造体のアドレスが返却されます。
fopen
関数の注意点
fopen
使用時に特に注意していただきたいのは下記の二点です。
- 第二引数で指定するのは文字列であること
- もともと存在するファイルを書き込みモードで開くと上書きされる
後者に関しては、せっかく苦労したファイルも書き込みモードで開いてしまうと中身が空になってしまいますので特に注意してください。
fopen
関数の使用例
例えば "input.txt"
を “読み込みモード&テキストモード” で開くときは下記のように fopen
関数を実行します。
FILE *fi;
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
printf("file open error:input.txt\n");
return -1;
}
"input.jpg
を “バイナリモード” で開くときは下記のように fopen
関数を実行します。
FILE *fi;
/* ファイルのオープン */
fi = fopen("input.jpg", "rb");
if(fi == NULL){
printf("file open error:input.jpg\n");
return -1;
}
ファイルのクローズ
fopen
関数で開いたファイルのクローズ(ファイルを閉じる)に用いる代表的な関数は fclose 関数です。
スポンサーリンク
fclose
関数
fclose
関数はストリームを解放し、ファイルを閉じる関数です。
#include <stdio.h>
int fclose(FILE*);
fclose
関数の引数
fclose
関数の引数には下記を指定します。
- 第一引数:閉じたいファイルに関連づけられた
FILE
型ポインタ
引数には fopen
の戻り値である FILE
型ポインタを指定するだけであり、モードの指定なども不要ですので使い方は簡単です。
fclose
関数の戻り値
fclose
関数の戻り値は下記のようになっています。
- 成功時:
0
- 失敗時:
EOF
ですが(本当は良くないかもしれませんが…)、あまりfclose
関数の戻り値って見ないんですよね。失敗するときは指定したファイルがオープンされていない場合くらいじゃないかなぁ…。その場合はクローズが不要ですから失敗しても問題ないですよね…。
fclose
関数の使用例
下記は fclose
関数の使用例になります。
FILE *fi;
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
printf("file open error:input.txt\n");
return -1;
}
/* ファイル入出力処理 */
fclose(fi);
ファイルの入力(ファイルからの読み込み)
ファイルの入力(ファイルからの読み込み)で使用する代表的な関数には、fgetc
、fgets
、fread
などがあります。
使用例で読み込むファイルは下記を input.txt
という名前で保存したテキストファイルとして動きを解説しています。
Good Morning... "Hello" Who are you? Good Bye!!
fgetc
関数
fgetc
関数は、ストリームから1文字読み込む関数です。1文字といっても半角英数字の1文字(unsigned char
型のデータ)と考えて良いです。テキストファイル向けの関数です。
#include <stdio.h>
int fgetc(FILE*);
fgetc
関数の引数
fgetc
関数の引数には下記を指定します。
- 第一引数:読み込みたいファイルの
FILE
構造体のアドレス
fgetc
関数の戻り値の FILE
構造体へのポインタを指定すれば良いです。
fgetc
関数の戻り値
fgetc
関数の戻り値は下記のようになっています。
- 成功時:読み込んだ1文字分の文字
- 失敗時:
EOF
ファイルの終端まで読み終わっている状態や読み込みに失敗した場合は EOF
が返却されます。ですので、テキストファイルの場合はファイルをまだ読み込めるかどうかは fgetc
関数の戻り値が EOF
であるかどうかで判断することが可能です。
画像などのバイナリデータの場合は EOF
の値自体がデータ内に存在する場合があります
ですので、戻り値が EOF
の場合でもファイルの終端でない場合があり、ファイルの終端を判断することが難しいです
こういった理由でちょっとバイナリデータの読み込みには使いにくいと思います
fgetc
関数の使用例
fgetc
関数の使用例は下記の通りです。
#include <stdio.h>
int main(void){
FILE *fi;
int ret;
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
printf("file open error:input.txt\n");
return -1;
}
while(1){
/* 1文字読み込み */
ret = fgetc(fi);
if(ret == EOF){
/* 戻り値がEOFの場合読み込み終了 */
break;
}
printf("%c", (char)ret);
}
/* ファイルのクローズ */
fclose(fi);
return 0;
}
実行すると下記のように表示されます。
Good Morning... "Hello" Who are you? Good Bye!!
input.txt
の中身がそのまま出力されていることが確認できると思います。
注目していただきたいのは、fi
変数は何も変更していないのに fgetc
関数で読み込むデータが次々と変化している点です。これこそがストリームの動きです。
読み込んだ文字はストリームから流れ出ていき、次の文字が次々とストリームに流れ入ってきているイメージが伝わるのではないかと思います。このようなストリームの制御は fgetc
関数の中で行われていますので、ファイルの読み込み位置の移動などは意識することなくプログラミングすることが可能です。
スポンサーリンク
fgets
関数
fgets
関数はストリームから(ファイルから)一行分データを読み込む関数です。もう少し正確にいうと、改行コード or ファイルの終端までのデータを読み込む関数になります。読み込んだ文字列の最後にはヌル文字('\0'
)が格納されます。
fgets
関数はテキストファイル向けの関数です。
#include <stdio.h>
char *fgets(char*, int, FILE*);
fgets
関数に関しては、下記ページで個別に解説していますので、詳しくは下記ページをご参照いただければと思います。
fread
関数
fread
関数はストリームから(ファイルから)指定したサイズ分のデータを読み込む関数です。
どちらかというとバイナリファイル向けの関数です。がテキストファイルでも使えます。ただし、fgets
のように自動的に読み込んだデータにヌル文字('\0'
)を付加したりはしてくれませんので、テキストファイルを扱う場合はその辺りに注意しながらプログラミングする必要があります。
#include <stdio.h>
size_t fread(void*, size_t, size_t, FILE*);
fread
関数の引数
fread
関数の引数には下記を指定します。
- 第一引数:読み込んだデータを格納するバッファ(メモリ領域)のアドレス
- 第二引数:読み込むデータ単位のサイズ(バイト)
- 第三引数:読み込むデータの個数
- 第四引数:読み込みたいファイルの FILE 構造体のアドレス
“第二引数で指定したサイズ x 第三引数で指定した個数” バイトのデータが読み込まれることになります。ややこしいのは第二引数と第三引数の使い分けですね。
基本的には
- 第二引数:第一引数のバッファ(配列等)の型のサイズ
- 第三引数:読み込むデータ数
を指定するので良いと思います。重要なのは、読み込むデータのサイズ(つまり「第二引数 x 第三引数」)が第一引数で指定したアドレスのバッファのサイズを超えないことです。
fread
関数の戻り値
fread
関数の戻り値は下記のようになっています。
- 読み込んだデータの個数
つまり、戻り値が「第三引数で指定したデータの個数」と同じであれば、まだ読み込めるデータが存在すると判断できますし、戻り値が「第三引数で指定したデータの個数」よりも小さいのであれば、ファイルの終端まで読み終わったと判断することが可能です。
fread
関数の使用例
fread
関数の使用例は下記の通りです。テキストファイルの読み込みを行なっています。ポイントはファイル読み込み終了の判断部分と、fgets
関数時と違って自分でヌル文字を付加している部分です。
#include <stdio.h>
#define READ_SIZE 4
int main(void){
FILE *fi;
char str[READ_SIZE+1];
size_t ret;
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
printf("file open error:input.txt\n");
return -1;
}
while(1){
/* READ_SIZEバイト分読み込み */
ret = fread(str, sizeof(char), READ_SIZE, fi);
str[ret] = '\0';
printf("%s\n", str);
if(ret < READ_SIZE){
/* 戻り値が第三引数よりも小さい
のでファイルの終端に行き着いた */
break;
}
}
/* ファイルのクローズ */
fclose(fi);
return 0;
}
出力結果は下記の通りです。
Good Mor ning ... "He llo" Wh o ar e yo u? G ood Bye! !
ファイルの出力(ファイルへの書き込み)
ファイルの出力(ファイルへの書き込み)で使用する代表的な関数には、fputc
、fwrite
、fprintf
などがあります。
スポンサーリンク
fputc
関数
fputc
関数はストリームへ(ファイルへ)1文字分書き込む関数です。fgetc
の書き込みバージョンみたいな感じですね。
テキストファイル・バイナリファイル両方に向いている関数です。が、テキストモードだと改行コードの変換が行われることがありますので、改行コードの変換をしたくない場合は fopen
関数でバイナリモードを指定するようにしましょう。
#include <stdio.h>
int fputc(int, FILE*);
fputc
関数の引数
fputc
関数の引数には下記を指定します。
- 第一引数:書き込みたい文字
- 第二引数:書き込みたいファイルの
FILE
構造体のアドレス
第一引数は int
型ですが、実際に書き込まれる文字は int
型を unsigned char
型に変換した文字となります。
fputc
関数の戻り値
fputc
関数の戻り値は下記のようになっています。
- 成功時:書き込んだ文字
- 失敗時:
EOF
fputc
関数の使用例
fputc
関数の使用例は下記の通りです。
#include <stdio.h>
#include <string.h> /* strlen用 */
int main(void){
char str[] = "Hello\n World!";
FILE *fo;
int ret;
int i;
/* ファイルのオープン */
fo = fopen("output.txt", "w");
if(fo == NULL){
printf("file open error:output.txt\n");
return -1;
}
for(i = 0; i < strlen(str); i++){
/* 1文字書き込み */
ret = fputc(0x200 + str[i], fo);
}
/* ファイルのクローズ */
fclose(fo);
return 0;
}
実行すると output.txt
が作成されます。ファイルの中身は下記のようになります。
Hello World!
読み込みで用いた fgetc
関数同様に、fputc
関数実行するたびにファイルの書き込み位置が変化している点がポイントですね。これも fputc
関数の中でデータが流れていくようにストリームの処理を行なってくれているからです。
fprintf
関数
fprintf
関数は、おなじみの printf
関数と同じような使い方でストリーム(ファイル)に書き込みを行う関数です。テキストファイル向けの関数です。
#include <stdio.h>
int fprintf(FILE*, char*, ・・・);
fprintf
関数の引数
fprintf
関数の引数には下記を指定します。
- 第一引数:書き込みたいファイルの
FILE
構造体のアドレス - 第二引数:書式文字列
- 第三引数:引数リスト
第一引数以外は printf
関数と全く同じ引数となります。
実は第一引数に stdout
を指定すると、標準出力に文字が出力され、printf
と同じ働きをします
fprintf
関数の戻り値
fprintf
関数の戻り値は下記の通りです。
- 成功:書き込んだ文字数
- 失敗:負の値
fprintf
関数の使用例
fprintf
関数の使用例は下記の通りです。
#include <stdio.h>
int main(void){
FILE *fo;
char *ret;
char name[5][10] = {
"taro", "hanako", "miki", "ziro", "emi"
};
int i;
/* ファイルのオープン */
fo = fopen("output.txt", "w");
if(fo == NULL){
printf("file open error:output.txt\n");
return -1;
}
for(i = 0; i < 5; i++){
fprintf(fo, "%d : %s\n", i+1, name[i]);
}
/* ファイルのクローズ */
fclose(fo);
return 0;
}
実行して作成される output.txt
の中身は下記のようになります。
1 : taro 2 : hanako 3 : miki 4 : ziro 5 : emi
fwrite
関数
fwrite
関数はストリームへ(ファイルへ)指定したサイズ分データを書き込む関数です。fread
関数の書き込みバージョンみたいな感じです。fwrite
関数はどちらかというとバイナリファイル向きの関数です。テキストファイルでも使用できますが、改行コードの変換に注意しましょう。
#include <stdio.h>
size_t fwrite(void*, size_t, size_t, FILE*);
fwrite
関数の引数
fwrite
関数の引数には下記を指定します。
- 第一引数:書き込むデータを格納するバッファ(メモリ領域)のアドレス
- 第二引数:書き込むデータ単位のサイズ(バイト)
- 第三引数:書き込むデータの個数
- 第四引数:書き込みたいファイルの
FILE
構造体のアドレス
基本的に fread
関数と同様にして引数を指定すれば良いです。読み込むのか、書き込むのかが違うくらいですね。
fwrite
関数の戻り値
fwrite
関数の戻り値は下記の通りです。
- 書き込んだデータの個数
fwrite
関数の使用例
fwrite
関数の使用例は下記の通りです。input.txt
を読み込み、そのままコピーして output.txt
に書き出す使用例としています。
#include <stdio.h>
#define READ_SIZE 10
int main(void){
FILE *fi;
FILE *fo;
char str[READ_SIZE];
size_t ret;
/* ファイルのオープン */
fi = fopen("input.txt", "r");
if(fi == NULL){
printf("file open error:input.txt\n");
return -1;
}
fo = fopen("output.txt", "w");
if(fo == NULL){
printf("file open error:output.txt\n");
fclose(fi);
return -1;
}
while(1){
/* READ_SIZEバイト分読み込み */
ret = fread(str, sizeof(char), READ_SIZE, fi);
fwrite(str, sizeof(char), ret, fo);
if(ret < READ_SIZE){
/* 戻り値が第三引数よりも小さい
のでファイルの終端に行き着いた */
break;
}
}
/* ファイルのクローズ */
fclose(fi);
fclose(fo);
return 0;
}
実行して作成される output.txt
の中身は下記のようになります。
Good Morning... "Hello" Who are you? Good Bye!!
スポンサーリンク
まとめ
このページではファイルの入出力の仕組みやファイル入出力に使用する標準関数の仕方について解説しました。ファイル入出力が出来るとC言語でできることの幅がかなり広がりますのでしっかりマスターしておきましょう!
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/