一週間で身につくC言語の基本

おさえておきたいプログラミングの基本

【応用編:6日目】 ファイルの読み書き

複数のデータをひとまとまりにしたものが構造体です。

6-1.ファイルの種類

(1) テキストファイルとバイナリファイル

ここでは、C言語によるファイルの読み書きについて学ぶこととしましょう。その前に、まずはファイルの種類について簡単に説明しましょう。以下の表を見てください(表6-1)。この表から見てもわかる通り、ファイルには大きくわけて、テキストファイルバイナリファイルが存在します。C言語でこれらを扱う方法は、基本的な部分に変わりはありませんが、ここではよりわかりやすく、汎用性の高いテキストファイルから説明することにします。(表6-1)

表6-1.ファイルの種類
ファイルの種類内容
テキストファイル文字として読めるデータ。.txtファイル、.cファイル、.htmlファイルほか
バイナリファイル文字として読めないデータ。(画像や音声など).pngファイル、.wavファイルほか

6-2.テキストファイルの書き込み

(1) テキストファイルにデータを書き込む

では、実際に来れに代入したり、出力したりするサンプルを見てみましょう。

listex6-1:main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char** argv){
    FILE *file;										// ファイルポインタ(出力用)
	file = fopen("c:\\test\\sample.txt", "w"); 		// ファイルを書き込み用にオープン(開く)
	if (file == NULL) {			 					// オープンに失敗した場合
		printf("ファイルが開けません。\n");			// エラーメッセージを出して
		exit(1);									// 異常終了
	}
	//	ファイルにデータを書き込む
	fprintf(file,"HelloWorld\r\n");		// ファイルに書く
	fprintf(file,"ABCDEF\r\n");
	fclose(file);         				// ファイルをクローズ(閉じる)
    return 0;
}

プログラムを実行しても、画面上に特に大きな変化は現れません。しかし、testフォルダを開くと、「sample.txt」というファイルが存在し、中を見ると

sample.txt
HelloWorld ABCDEF

と記述されているはずです。

(2) ファイルのオープン

では、順を追って説明していきましょう。まず、2行目でファイルのポインタを宣言しています。これは、ファイルを操作するときには必ず必要になるポインタです。

ファイルポインタの宣言
FILE *file;

次に、3行目でファイルをオープンしています。通常、ファイル処理を行うときは、対象となるファイルを「オープン」する処理が必要となります。

ファイルのオープン
file = fopen("c:\\test\\sample.txt", "w");

ファイルのオープンは、fopen関数を用いますが、書式は、以下の通りです。(表6-2)

表6-2.ファイルオープンの関数
関数書式意味使用例
fopen()fopen(ファイル名,モード);ファイルを、指定したモードで開く。
戻り値が、ファイルのポインタ。
FILE* fp = open("sample.txt","w");

このプログラムの場合、c:¥testフォルダ内にある、sample.txtというファイルを開くことになります。モードでで出ている"w"は、書き込みを意味します。なお、fopenのモードには、以下のものがあります。(表6-3)

表6-3.fopen()関数のモード
モード機能ファイルが存在しないとき
rテキストデータの読み込みエラーになる
wテキストデータの書き込みファイルを新規作成する
aテキストデータへの追加書き込みファイルを新規作成する
r+テキストデータを更新モードで開く(書き込み・読み込み共に可)エラーになる
w+テキストデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
a+テキストデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
rbバイナリデータの読み込みエラーになる
wbバイナリデータの書き込みファイルを新規作成する
abバイナリデータへの追加書き込みファイルを新規作成する
rb+ / r+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)エラーになる
wb+ / w+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する
ab+ / a+bバイナリデータを更新モードで開く(書き込み・読み込み共に可)ファイルを新規作成する

この表から判る通り、ここでは、テキストファイルを書き込みモードで開いていることが判ります。このとき、ファイルのオープンが失敗すると、戻り値にNULLが返されます。そのときは、異常終了の処理に入ります。

プログラムの終了にはexit() 関数を用います。引数 status は 0 以上 255 以下の値を設定し、正常終了の場合には0を設定します。今回は異常終了として、引数に1を指定しています。

exit関数を利用したプログラムの異常終了
exit(1)

また、ファイルのオープンに成功すると、fileにファイルポインタが渡されます。今後、開かれたファイルに対するアクセス(書き込み・読み込みなど)をするには、このポインタが必要になります。

(3) ファイルへの書き込み

このプログラムは、ファイルを書き込みのプログラムなので、次はファイルの書き込み処理となります。まず、書き込み処理の関数を見てみましょう。(表6-4)

表6-4.ファイルへの書き込み処理関数
関数書式意味使用例
fprintf()fprintf(ファイルポインタ,書き込み文字列,変数・・・);文字列をファイルに書き込むfprintf(fp,"no=%d",i);

見てわかるとおり、fprintf()は、画面に文字を出力するprintf()関数と似ています。ですので、最初にファイルのポインタを引数として渡す以外は基本的な文字列の操作はprinf関数とかわりません。

ただ、気を付けなくてはならないのが開業の場合です。これはOSによって異なります。

なお、OSがウィンドウズの場合、改行コードは「\n」ではなく、「\r\n」と2文字にります。改行を「\n」とすると、ファイルが改行されない可能性があるので気をつけましょう。linux系のOSでは、「\n」に書き変えなくてはなりません。

(4) ファイルのクローズ

ファイル書き込みの一連の処理が終わったら、ファイルを閉じますファイルをとじるのはclose(クローズ)関数です。書式は以下の通りです。(表6-5)

表6-5.ファイルをクローズする関数
関数書式意味使用例
fclose()fclose(ファイルポインタ);指定したファイルポインタのファイルを閉じるfclose(fp);

これで、ファイルをオープンしてから、書き込み、クローズまでの一連の処理がわかりました。次は、書き込んだファイルを読み込んでみましょう。

6-3.テキストファイルの読み込み

(1) 書き込んだファイルを読み込む

次は、listex6-1で書き込んだファイルを読み込み、画面に表示するプログラムを作って見ましょう。以下のサンプルを実行してみてください。

listex6-2:main.c
#include <stdio.h>
#include <stdlib.h>

#define SIZE    256

int main(int argc, char** argv) {
	FILE *file;									// ファイルポインタ(読み込み用)
	char line[SIZE];							//	読み込む行
	line[0] = '\0';								//	初期化(空文字列)
	file = fopen("c:\\test\\sample.txt", "r"); 	// ファイルを読み込み用にオープン(開く)
	if (file == NULL) {							// オープンに失敗した場合
		printf("ファイルが開けません。\n");		// エラーメッセージを出して
		exit(1);                         		// 異常終了
	}
	//	ファイルのデータ読み込む
	while ( fgets(line, SIZE, file) != NULL ) {
		printf("%s", line);
	}
	fclose(file);          // ファイルをクローズ(閉じる)
    return 0;
}
実行結果
HelloWorld ABCDEF

実行結果から判る通り、このプログラムによって、さきほど書き込まれたsample.txtが読み込まれ、その内容が表示されているのがわかります。ファイル読み込み時も、ファイルをオープンし、クローズするという一連の流れは変わりません。違いは、オープン時にモードが"r"になっていることぐらいです。

(2) 1行ごとのファイルの読み込み

また、ファイルからデータを読み出す関数は複数ありますが、ここでは特に、テキストデータということで、テキストデータを一行読み出すfgets()関数を用いました。以下に、関数の説明を書きます。(表6-6)

表6-6.fgets()関数
関数書式意味使用例
fgets()fgets(文字列,文字列サイズ,ファイルポインタ);指定したサイズの文字列を
ファイルから読み込む。
fgets(s,10,fp);

ここでいう文字列は、最初の引数として指定した文字列のサイズのことです。読み込みが成功すれば、これと同じ値が返ってきます。読み込みがうまくいかなかったり、最後まで読みきった場合は、NULLが帰ってきますので、このプログラムでは、NULLが帰ってくるまで読み込みを行い、表示するという処理をwhileループで行っています。

このプログラムで、文字列を入れる配列lineは、256という長さをとっています。気をつけなくてはならないのは、実際の文字列で一行の最大の文字列がこの値を超えてしまう場合もあるということです。なので、この配列のサイズは長めに取っておくことをおすすめします。このプログラムでも、実際はせいぜい10文字前後の文字列を読み込むのに、不必要なまでに大きなサイズの文字列を取っているのはそのためです。

(3) 1文字ごとのファイルの読み込み

しかし、それでもファイルの一行のサイズが、用意された文字列のファイルのサイズを超えてしまうことも想定されます。そこで、次に、1行ごとではなく、1文字ずつファイルを読み込み、表示する方法を紹介しましょう。

listex6-3:main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    FILE *file;                                   // ファイルポインタ(読み込み用)
	int c;                                        //  読み込む文字のコード
	file = fopen("c:\\test\\sample.txt", "r");    // ファイルを読み込み用にオープン(開く)
	if (file == NULL) {                    // オープンに失敗
		printf("ファイルが開けません。\n");  // エラーメッセージ
		exit(1);    // 異常終了
	}
	//    ファイルのデータ読み込む
	while ((c=fgetc(file)) != EOF){
		printf("%c",(char)c);
	}
	fclose(file);          // ファイルをクローズ(閉じる)
    return 0;
}

実行結果は、listex6-2と同じなので省略します。

このプログラムでは、fgetc()関数を用いて一字ずつ文字を取得しそれを表示しています。

変数cには、文字コードが入りますので、printf()関数では、文字列ではなく、文字の表示を表す'%c'が用いられています。fgetc()を実行すると、先頭から順次ファイルを読み込んでいきます。

戻り値が文字コードとなって いますが、ファイルの最後まで到達すると、EOFが返ってきます。EOFは「End of File」の略で、ファイルの終端を示す特別なマーカーであり、データの終わりを検出するために必要です。

したがって、このプログラムでは、EOFが出現するまでファイルを読み続け、表示することにより、読み込んだファイルが全て表示される仕組みになっています。

fgetc()とfgets()の違いのイメージは、以下のようになります。(図6-1)

図6-1.fgetc()とfgets()の違い
fgetc()とfgets()の違い

6-4.バイナリファイルの読み書き

(1) バイナリファイルの読み書きをするプログラム

続いて、バイナリファイルの書き込みと読み込みを行うプログラムを作ってみましょう。

listex6-4:main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    FILE* file;
	int i;
	//	書き込むデータ
	char wdata[] = { 0x10 , 0x1a , 0x1e , 0x1f };
	char rdata[4];
	//バイナリデータの書き込み
	file = fopen("C:\\test\\test.bin","wb");
	if(file == NULL){
		printf("ファイルオープンに失敗しました。\n");
		exit(1);
	}
	fwrite(wdata,sizeof(char),sizeof(wdata),file);
	fclose(file);          // ファイルをクローズ(閉じる)
	//バイナリデータの書き込み
	file = fopen("C:\\test\\test.bin","rb");
	if(file == NULL){
		printf("ファイルオープンに失敗しました。\n");
		exit(1);
	}
	fread( rdata, sizeof(char), sizeof(rdata), file );
	fclose(file);          // ファイルをクローズ(閉じる)
	//	結果を表示
	for(i = 0; i < sizeof(rdata) ; i++){
		printf("%x ",rdata[i]);
	}
	printf("\n");
    return 0;
}
実行結果
10 1a 1e 1f

このサンプルでは、前半でバイナリファイルの書き込み、後半でバイナリファイルの読み込みを行っています。

(1) バイナリファイルの書きこみ

10行目から17行目は、ファイルへデータを書き込んでいます。fopen()でファイルを開くのは同じですが、モードを"wb"に指定していることから、バイナリモードでの書き込みとなります。

ここでは、fwrite()でデータを書き込みます。ファイル名は、cドライブ直下のtestフォルダー内にあるtest.binに書き込まれます。

データは、char型の配列wdataのデータを書き込んでいます。最後は、fclose()でファイルを閉じて、書き込みが終了します。

ここで出てきたfwrite()関数の仕様は以下の通りです。(表6-7)

表6-7.fwrite()関数
関数書式意味使用例
fwrite()fwrite(データ,データのバイト長,
データの数,ファイルポインタ);
バイナリデータ
の書き込み。
fwrite(data,sizeof(int),sizeof(data),fp);

16行目のfwrite()関数では、wdataのデータを書き込んでいます。wdataは、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、データの数に、配列wdataの大きさを指すsizeof(wdata)を指定します。

(2) バイナリファイルの読み込み

次に、18行目から25行目でデータを読み込みます。fopen()でファイルを開きますが、今度はバイナリファイルの読み込みなので、モードに"rb"を指定します。バイナリモードでの読み込みは、fread()関数を用います。ここでは、char型の配列rdataに値を読み込んでいます。最後はclose()でファイルを閉じて終了です。

最後に、rdataの値を16進数で表示すると、書き込んだ通りのデータが読み込まれていることがわかります。

ここで出てきたfread()関数の仕様は、以下の通りです。(表6-8)

表6-8.fread()関数
関数書式意味使用例
fread()fread(データ,データのバイト長,
データの数,ファイルポインタ);
バイナリデータ
の読み込み。
fread(data,sizeof(int),sizeof(data),fp);

24行目のfread()関数では、rdataのデータを書き込んでいます。rdataは、fwriteの場合と同様に、char型のサイズ4の配列なので、データのバイト数に、char型のサイズのバイト数を表すsizeof(char)、データの数に、配列rdataの大きさを指すsizeof(rdata)を指定します。

6-5.大きさのわからないファイルの読み込み

(1) ファイル読出しの問題点

listex6-4で、バイナリファイルの基本的な読み書きの説明をしました。しかし、ここには大きな問題点があります。バイナリファイルに限った事ではありませんが、ファイルはき込むときは比較的簡単ですが、読み込むときには大きな問題があります。それは、読み込むべきファイルの大きさが分からないということです。

おそらく、ほとんどのプログラムは、サイズのわからないデータを読み込まなければならないはずです。そういった時にやるべきことは、ファイルの大きさを取得し、その分のメモリを確保してデータを読み込むことです。それには、fseek()関数、およびftell()関数が必要となります。まずは、以下のサンプルを見てください。

listex6-5:main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
	FILE* file;
	int i,size;
	//	書き込むデータ
	char* rdata;
	//バイナリデータの書き込み
	file = fopen( "C:\\test\\test.bin", "rb" );
	if( file == NULL ){
		printf( "ファイルオープンに失敗しました。\n" );
		exit( 1 );
	}
	//	ファイルの最後までシーク
	fseek(file, 0, SEEK_END);
	//	ファイルの大きさを取得
	size = ftell(file);
	//	メモリのサイズだけ、配列を動的に生成
	rdata = (char*)malloc(sizeof(char)*size);
	//	再るの最後までポインタを戻す
	fseek(file,0,SEEK_SET);
	fread(rdata, sizeof(char), size, file);
	fclose(file);          // ファイルをクローズ(閉じる)
	//	結果を表示
	for(i = 0; i < size ; i++){
		printf("%x ",rdata[i]);
	}
	printf("\n");
	//	メモリ解放
	free(rdata);
    return 0;
}
実行結果

このサンプルは、listex6-4で書き込まれた「test.bin」を読み出して表示するプログラムです。実行結果は同じなので省略します。

(2) fseek,ftell関数

ここで用いられているfseek関数およびftell関数は、ファイルの位置を指定する関数です。仕様は以下の通りです。(表6-9)

表6-9.fseek(),ftell()関数の仕様
関数書式意味使用例
fseek()fseek(ファイルポインタ,
移動バイト数,開始位置);
ファイルを開始位置から、移動バイト数移動する fseek(fp, 0L, SEEK_END);
ftell()ftell(ファイルポインタ);現在のファイル位置の値をバイト数で返す。ftell(fp);

一般にこの二つの関数はセットで使用されるケースがほとんどです。なお、fseek関数で指定される開始位置は、以下の通りです。(表6-10)(図6-2)

表6-10.fseek関数で用いられる開始位置
開始位置の定数意味
SEEK_SETファイルの先頭
SEEK_CURファイルの現在位置
SEEK_ENDファイルの終端
図6-2.fseek関数の開始位置
fseek関数の開始位置

(3) ファイル読み込みの流れ

では、これをもとにプログラムを解説していきましょう。ファイルオープンまでの流れはlistex6-4と同じなので省略します。まず、16行目のfseek()関数で、ファイルの読み出し位置を、ファイルの終端から0バイチ目、つまり、ファイルの終端にまで持っていきます。

次に、18行目でftell()関数を用いて、位置を取得しています。ftell()では、ファイルの位置を取得してます。ファイルの読み出しの現在位置は終端にあるので、この時得られる値はファイルの大きさのバイト数に相当するはずです。なので、この値を変数sizeに代入し、保存します。

次に、このsizeをもとに、データを格納するメモリを確保(20行目)し、再びfseek()関数を用いて、ファイルの読み出し位置を先頭に戻し(22行目)、そのバイト数だけのデータを読み出します。(23行目)

最後にファイルを閉じ(24行目)、読みだしたデータの結果を表示してから、生成したメモリを開放し(31行目)、プログラムを終了します。

画像データのように、大きさが一定しないデータを読み出すときはこの方法が便利です。尚、fseek()およびftell()はテキストデータの読み出しの時にも使えます


練習問題 : 問題6.

一週間で学べるコースの一覧
Udemy
...
2024/10/01

Udemyでも学びましょう!

一週間でわかるC言語・C++言語がオンライン講座になりました!動画音声によってさらにわかりやすくなりました!! 1講座で2つの言語を学ぶことができる上に、練習問題の回答もダウンロードできます。

Read →
Impress一週間シリーズ
1週間でC言語の基礎が学べる本
2024/10/01

書籍化された一週間シリーズ

本講座が「1週間でC言語の基礎が学べる本」として書籍化されました!サイトの内容プラスアルファでより学習しやすくなっています!Impressより発売中です!!

Read →
Impress一週間シリーズ
...
2024/10/01

書籍化された一週間シリーズ

一週間シリーズは書籍化されています。こちらもどうぞ!

Read →
プログラマーなら欲しいグッズ
プログラミンググッズ

プログラミンググッズ

快適なプログラミング環境を構築したい人々にぜひとも揃えてほしいグッズです。

Read →
制作・管理
シフトシステム株式会社

シフトシステム株式会社

このサイトはシフトシステム株式会社によって制作・管理がなされています。

Read →