7.ファイル入出力
データ解析や数値演算においては、元のデータをファイルから読み込んだり、解析結果をファイルに書き出したりする必要が出てきます。ファイル入出力は、メモリとファイル間でのデータのやり取りです。
7.1 テキストファイルとバイナリファイル
ファイルには「テキストファイル」と「バイナリファイル」の2種類があり、それぞれデータの保持のしかたが異なります。
テキストファイルは、データを文字列として管理しているファイルです。たとえば"10000"という数値は、'1', '0', '0', '0', '0'という5つの文字(に対応する文字コード、0x31, 0x30, 0x30, 0x30,
0x30、計5バイト)として保存されています。
それに対して、バイナリファイルは数値をそのまま管理します。"10000"という数値は、short型整数であれば0x2710という2バイトのデータとして保存されます。
テキストファイルは、テキストエディタでファイルの内容が確認できる反面、ファイルサイズが大きくなってしまいます。バイナリファイルは、ファイルのサイズが小さくてすむ反面、テキストエディタではファイルの内容を確認できません。
後述のように、C言語ではこれらのファイルを扱う関数が一部異なります。
7.2 ファイルのオープンとクローズ
下の例は、テキストファイルtext.txtとバイナリファイルdata.datをプログラムで使うための、準備と後始末の手順を示したものです。(実際に読み込んだり書き込んだりはしていません。実行ファイルと同じディレクトリにtext.txtというファイルがないと、このまま実行してもエラーになります。)
<例7−1>
#include <stdio.h>
int main(void) {
FILE
*tp, *bp; /* FILE型構造体へのポインタ */
char
t_file[]="text.txt"; /*
読み出し用ファイル名 */
char
b_file[]="data.dat"; /*
書き込み用ファイル名 */
/*
テキストファイルt_fileを読み出しモードでオープン */
if
((tp = fopen(t_file, "r")) == NULL) {
printf("Text
file open error.\n");
return
1;
}
/***
read something from the text file ***/
fclose(tp); /* ファイルのクローズ */
/*
バイナリファイルb_fileを書き込みモードでオープン */
if
((bp = fopen(b_file, "wb")) == NULL) {
printf("Binary
file open error.\n");
fclose(tp);
}
/***
write something to the binary file ***/
fclose(bp); /* ファイルのクローズ */
return
0;
}
[解説] ストリーム、標準ストリーム
[解説] fopenのモード
ファイルから読み込む場合も書き込む場合も、また、テキストファイルの場合もバイナリファイルの場合も、プログラムからファイルを扱う時には、とにかく先ずその「ファイルをオープン」しなくてはなりません。
ここでいう「ファイルのオープン」は、ワープロなどのアプリケーションであるファイルのウィンドウを開くのとは違います。fopen関数を使って、特定のファイルとデータのやり取りをするための「ストリーム」を確立する作業です。
FILE *tp;
...
if ((tp = fopen(t_file,
"r")) == NULL) {
/*
エラー処理 */
}
fopen関数は、ファイル名とモード(「書き込み用か読み込み用か」、「テキストかバイナリか」)を指定して呼び出し、オープンに成功すると確立したストリームを返します。モードについては上の[解説]で確認してください。
fopenの返却値はFILE型構造体へのポインタで受け取ります。FILE型構造体は、ファイル入出力に必要不可欠な情報が格納されますが、通常その中身を意識する必要はありません。何らかの理由でファイルのオープンに失敗した場合(指定した名前のファイルがない、など)、fopenはNULLを返しますので、上の例のようにしてエラー処理をします。
ファイルを使う用が済んだら、fclose関数で「ファイルをクローズ」し、ストリームを切り離します。
fclose(tp);
7.3 データの書き込みと読み出し
7.3.1 テキストファイルの場合
以下の例のプログラムを実行する前に、テキストエディタで適当なテキストファイルを作っておくとよいでしょう。こちらのサンプルファイルsample.txtをダウンロードして使っても構いません。
<例7−2>
#include <stdio.h>
int main(void) {
char
infile[40], outfile[40]; /* ファイル名格納用の文字列 */
FILE
*fin, *fout /*
FILE型構造体へのポインタ */;
int
c;
/*
入力ファイルと出力ファイルのファイル名を取得 */
printf("Source
file: ");
scanf("%39s",
infile);
printf("Copy
file: ");
scanf("%39s",
outfile);
/*
入力ファイル(テキスト)を読み出しモードでオープン */
if
((fin=fopen(infile, "r")) == NULL) {
fprintf(stderr,
"Source file open error.\n");
return
1;
}
/*
出力ファイル(テキスト)を書き込みモードでオープン */
if
((fout=fopen(outfile, "w")) == NULL) {
fprintf(stderr,
"Copy file open error.\n");
fclose(fin);
return
1;
}
/*
入力ファイルから出力ファイルへ1文字ずつコピー */
while
((c=fgetc(fin))!=EOF) { /* fgetc関数の返却値はint型 */
fputc(c,
fout); /*
fputc関数の第1引数はint型 */
}
/*
ファイルのクローズ */
fclose(fin);
fclose(fout);
return
0;
}
[解説] テキストファイルへの入出力関数
この例では、それぞれ指定した名前のファイルを読み込みモードと書き込みモードでオープンした後、入力ファイルから出力ファイルへ一文字ずつコピーしています。
while ((c=fgetc(fin))!=EOF) {
fputc(c,
fout);
}
fgetc関数はファイルから一文字読み込む関数、fputc関数はファイルに一文字書き込む関数です。fgetcはファイルの最後尾に到達するとEOF(end of file)を返すので、それを指標にしてループを終了させています。
テキストファイルへのその他の入出力関数については、上の例の[解説]を参照してください。
【課題7−1】(プログラムを提出)
<例7−2>を、fgets関数とfputs関数を使って「1行ずつコピーする」プログラムに変更する。
[注意] fgets関数はファイル終了時にEOFではなくNULLを返す。
《プログラム例》
7.3.2 バイナリファイルの場合
下のプログラムを実行する前に、サンプルファイルexample.datを実行ファイルと同じディレクトリにダウンロードしておく必要があります。example.datはバイナリファイルなので、テキストエディタやブラウザで中身を見ることはできません。
<例7−3>
#include <stdio.h>
int main(void) {
FILE
*fin, *fout; /*
FILE構造体へのポインタ */
char
infile[]="example.dat"; /*
入力ファイル名 */
char
outfile[]="example.txt"; /* 出力ファイル名 */
double
x; /*
実数データ格納用変数 */
/*
入力ファイル(バイナリ)を読み出しモードでオープン */
if
((fin=fopen(infile, "rb"))==NULL) {
fprintf(stderr,
"Data file open error.\n");
return
1;
}
/*
出力ファイル(テキスト)を書き込みモードでオープン */
if
((fout=fopen(outfile, "w"))==NULL) {
fprintf(stderr,
"Result file open error.\n");
fclose(fin);
return
1;
}
/*
入力ファイルから出力ファイルへ、データを1つずつコピー */
while
(fread(&x, sizeof(double), 1, fin) > 0) {
fprintf(fout,
"%5.4f\n", x);
}
/*
ファイルのクローズ */
fclose(fin);
fclose(fout);
return
0;
}
[解説] バイナリファイルの入出力関数
[解説] シーケンシャルアクセスとランダムアクセス
実際には、バイナリファイルexample.datの中には、0〜1のあいだのランダムな100個の数値がdouble型で入っています。その中身をexample.txtというテキストファイルに書き出しています。プログラムの実行後、作られたexample.txtの中身はテキストエディタで確認可能です。
上の例では、バイナリファイルから一つずつ値を読み出して、テキストファイルに書き出しています。また、一つ書くたびに改行しています。
while (fread(&x,
sizeof(double), 1, fin) > 0) {
fprintf(fout,
"%5.4f\n", x);
}
fread関数は、指定された個数のデータを読み込んで、実際に読み込めた個数を返します。ファイルの終わりは、fread関数がデータを読み込めずに0を返してくることで判断できます。
fread関数やfwrite関数など、バイナリファイルに対する入出力関数については、上の[解説]を参照してください。これらの関数の始めの引数は、読み込む(書き込む)データを格納する変数のアドレスを渡すことに注意して下さい。下の課題のように、大きなデータをまとめて読み込む時には、あらかじめ用意した配列(や動的に確保したメモリ領域)の先頭アドレスを渡すことになります。
【課題7−2】(プログラムを提出)
example.datのデータを10x10の二次元配列にまとめて読み込み、タブ区切りのテキストファイルとして書き出すプログラムを作る。
[解説] タブ区切りのテキストファイル
《プログラム例》