プログラムの変数の値はプログラムが終ると消えてなくなります。 計算した値をプログラムが終っても残しておくために、そのデータを ファイルに移して保管します。またそのようなデータをファイルから プログラムに読み込んで計算を進めることもあります。
このようにファイルを読み書きする方法は膨大なデータを取り扱う プログラムでは必須の技術です。この章ではこのファイル操作に ついて解説します。
ファイルとはディスクに書き込まれたデータの固まりです。プログラムの 変数のようにメモリの中に存在するデータとは根本的に異なります。 ディスクの中に存在するデータを読み書きする操作はハード的にもメモリ のそれとは全く異なります。
ですが、これらの末端の操作はほとんど全て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()関数を利用する場合には、
このようにバイナリモードでファイルを開いておくことが必須です。
|
|
★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()関数と同じです。
使用例:
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 );
この例はファイル data.txt に整数がたくさん並んでいると仮定して
それを先頭から1個づつ最後まで読み込んで、それを1個づつ
画面に表示します。
int fputs( char* str, FILE* fptr );
str は出力する文字列のアドレスです。
char* fgets( char* str, int n, FILE* fptr );str は文字列を格納する配列のアドレス。 n は読み込む最大文字数です。
使用例:
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 );
この例はテキスト形式のファイル src.txt を1行づつ読みこんで
その内容を新しいファイル dst.txt に1行づつ書き込むます。
つまりコピーです。
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 ブロックを そのまま読み込みます。配列変数の読み込みに効果的です。この関数を 使う場合はファイルはバイナリモードで開けておくべきです。
使用例:
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 );
この例は src.dat に double型データが大量に格納されていると
仮定して、
それを256個づつ読み込んで新しいファイル dst.dat に書き込みます。
実践的な例を示します。前章の最後の例示したデータ変換プログラムを リダイレクションではなくちゃんとしたファイル操作で行います。
ある実行ファイル 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の数に変換して返します。