プログラムの変数の値はプログラムが終ると消えてなくなります。 計算した値をプログラムが終っても残しておくために、そのデータを ファイルに移して保管します。またそのようなデータをファイルから プログラムに読み込んで計算を進めることもあります。
このようにファイルを読み書きする方法は膨大なデータを取り扱う プログラムでは必須の技術です。この章ではこのファイル操作に ついて解説します。
ファイルとはディスクに書き込まれたデータの固まりです。プログラムの 変数のようにメモリの中に存在するデータとは根本的に異なります。 ディスクの中に存在するデータを読み書きする操作はハード的にもメモリ のそれとは全く異なります。
ですが、これらの末端の操作はほとんど全てOSがまかなうので、私達は
OSに対してデータの読み書きを依頼するだけで済みます。
C/C++言語ではそのようなデータのファイルへの書き込みをOSに依頼する
関数がいくつか用意されています。
このようにOSにほとんどを任せてファイル操作を行う方法を 高水準ファイル入出力と呼びます。一方、OSの仕事を一部自分で 調整してファイル操作を行う低水準ファイル入出力と呼ばれる 方法もあります。
本書ではもっぱら高水準ファイル入出力について解説します。
高水準ファイル入出力ではファイルとのデータのやりとりはFILEという 特殊な構造体を窓口として行われます。プログラマが気にしておく ことはこの構造体のあるアドレスを格納した変数だけです。
ファイル操作を行うには、最初にこのFILE構造体に指定のファイルの 管理を依頼します。この操作の事を単にファイルをオープンするとも 言います。この操作にはfopen関数を用います。
FILE* fopen( char* filename, char* mode );
fopenには2つの引数があり、filenameでファイルのpathを表す 文字列を指定し、modeでファイルをどのように扱うかを指示する文字列を 指定します。
modeに使う文字列には以下の種類があります。
"r" | 読み込み用にオープン。 |
"r+" | 読み書き両用にオープン。 |
これらはファイルが無ければ失敗になります。 | |
"w" | 書き込み用にオープン。 |
"w+" | 読み書き両用にオープン。 |
これらはファイルが無ければ新設し、有れば空にします。 | |
"a" | 追加書き込みようにオープン。 |
"a+" | 読み書き両用にオープン。 |
ファイルが無ければ新設し、有ればそのままにします。 |
FILE* fptr; fptr = fopen("article.txt","w"); if( fptr == NULL ){ perror("failed at fopen"); exit(1); }
これで以後 fptr をファイル書き込みの窓口としてファイルに書き込みが できます。同様に "r" とすれば読み込みができます。 読み書きの関数の説明は後に回して、先にファイル操作を終了する方法 を解説します。
FILE構造体にファイル管理を終了させてFILE構造体のメモリを解放 するにはfclose関数を使います。この操作を単にファイルをクローズ するとも言います。
fclose( FILE* fptr );
fptrはクローズするファイルのFILE構造体のアドレスです。
★MicroSoftのOS(MS-DOS,Windows3.1,NT,95,98)でのプログラマへの注意。 これらのOSでは、C言語の改行コード '\n' をファイルに書き込もうと するとOSが自動的にそれを '\r' '\n' の2文字に変換して書き込みます。 なぜなら、それがMicroSoft独自の改行コードだからです。 逆に読み込みでファイルに'\r' '\n' の改行コードがあれば、OSが 自動的に '\n' の1文字に変換してプログラムに渡します。 この変換をしてほしくない場合にはファイルをバイナリモードとして オープンします。そのためにはfopen()関数のmode引数の文字列に"b"を 加えます。例えば とします。後述の fread(),fwrite()関数を利用する場合には、 このようにバイナリモードでファイルを開いておくことが必須です。fptr = fopen("binary.dat","wb"); |
★UNIX系のOSでのプログラマへの注意。 UNIXではMicroSoft OSでのような改行コードの変換は不要なので 何も気にすることはありません。従ってテキストモード、バイナリモード の概念すらありません。 |
データ書き込み読み込み関数はいろいろな種類があります。
一部のみ紹介します。
なお、変数fptrはすべて書き込むまたは読み込むファイルへの
file pointerをあらわしています。
int fprintf( FILE* fptr, const char* format[, arg1,arg2,... ] );printf()関数のFILE版です。 fptr以外はすべてprintf関数と同じです。
使用例:
fprintf( fptr, "hello, world!\n"); fprintf( fptr, "%d %d\n", n, m ); fprintf( fptr, "%lf %s\n", d, str );
int fscanf( FILE* fptr, const char* format[, arg1,arg2,... ] );scanf()関数のFILE版です。 fptr以外はすべてscanf()関数と同じです。
使用例:
この例はファイル data.txt に整数がたくさん並んでいると仮定して それを先頭から1個づつ最後まで読み込んで、それを1個づつ 画面に表示します。FILE* fptr = fopen("data.txt","rt"); if( fptr == NULL ){ perror("failed at fopen"); exit(1); } while( fscanf( fptr, "%d", &n ) != EOF ){ printf("%d\n", n ); } fclose( fptr );
str は出力する文字列のアドレスです。int fputs( char* str, FILE* fptr );
char* fgets( char* str, int n, FILE* fptr );str は文字列を格納する配列のアドレス。 n は読み込む最大文字数です。
使用例:
この例はテキスト形式のファイル src.txt を1行づつ読みこんで その内容を新しいファイル dst.txt に1行づつ書き込むます。 つまりコピーです。FILE* fptr_r = fopen("src.txt","rt"); FILE* fptr_w = fopen("dst.txt","wt"); if( fptr_r == NULL || fptr_w == NULL ){ perror("failed at fopen"); exit(1); } char buf[256]; while( fgets( buf, 256, fptr_r ) != NULL ){ fputs( buf, fptr_w ); } fclose( fptr_r ); fclose( fptr_w );
int fwrite( void* buf, int unit_size, int n, FILE* fptr );fwriteはunit_sizeで指定されたbyte数を1単位として n ブロックを そのまま書き込みます。配列変数の書き込みに効果的です。この関数を 使う場合はファイルはバイナリモードで開けておくべきです。
int fread( void* buf, int unit_size, int n, FILE* fptr );freadはunit_sizeで指定されたbyte数を1単位として n ブロックを そのまま読み込みます。配列変数の読み込みに効果的です。この関数を 使う場合はファイルはバイナリモードで開けておくべきです。
使用例:
この例は src.dat に double型データが大量に格納されていると 仮定して、 それを256個づつ読み込んで新しいファイル dst.dat に書き込みます。FILE* fptr_r = fopen("src.dat","rb"); FILE* fptr_w = fopen("dst.dat","wb"); if( fptr_r == NULL || fptr_w == NULL ){ perror("failed at fopen"); exit(1); } int n; double data[256]; while( ( n = fread( data, sizeof(double), 256, fptr_r ) > 0 ){ fwrite( data, sizeof(double), n, fptr_w ); } fclose( fptr_r ); fclose( fptr_w );
実践的な例を示します。前章の最後の例示したデータ変換プログラムを リダイレクションではなくちゃんとしたファイル操作で行います。
ある実行ファイル filter を次のように実行して、original.dat の内容を読み込んで処理して結果をresult.datに書き出す プログラムを作ります。
filter original.dat result.dat
original.datの内容の書式は先の例より複雑にして次のようである とします。
" T 000 -100.62 -234.18 +086.04 96/12/16 13:23:51 th10-2-1 " " T 000 -057.36 +131.95 -009.92 96/12/16 13:42:36 th10-2-1 " " T 000 +012.06 -027.74 -009.88 96/12/16 13:46:20 th10-2-1 " " T 000 -103.17 -233.66 +084.74 96/12/16 13:52:59 th10-2-1 "ここで必要なのは各行の中あたりにある3つの小数とします。 これを行の中からどのようにして取り出すかが難しいところです。
この操作を行うには、fscanf関数は不向きです。小数以外の
データが並んでしまっているからです。
このような場合には一行をまるごと文字列配列変数に読み込んで
その特定の位置から始まる文字列の数字をdoubleの数に変換すると
いう方法を取ります。
文字列の数字をdoubleの数に変換するには atof()関数を使います。
atof()を使う際にはstdlib.hを必ずincludeしなくてなりません。
プログラムは次のようになります。
#include <stdio.h> #include <stdlib.h> int main( int argc, char* argv[] ) { FILE* fptr_r; FILE* fptr_w; char buf[128]; double d1,d2,d3; if( argc != 3 ){ printf("Give two file names.\n"); exit(1); } fptr_r = fopen( argv[1], "r" ); fptr_w = fopen( argv[2], "w" ); if( fptr_r == NULL || fptr_w == NULL ){ perror("failed at fopen"); exit(1); } while( fgets( buf, 128, fptr_r ) != NULL ){ d1 = atof(&buf[13]); d2 = atof(&buf[22]); d3 = atof(&buf[31]); fprintf( fptr_w, "%+07.2lf %+07.2lf %+07.2lf\n", d1, d2, d3 ); } fclose( fptr_r ); fclose( fptr_w ); return(0); }
atof()関数に各数字の文字列の先頭の位置のアドレスを渡しています。 こうするとatof()関数は小数として認識できるところまで自動的に 判断してそれをdoubleの数に変換して返します。