第1引数にはファイルの名前を文字列として渡す (プログラムを実行しているディレクトリとファイルのディレクトリが異なる場合にはパスも含めて渡す。)。 第2引数にはファイルを読み込むのか書き込むのか等の mode を 文字列として渡す。 mode の与え方は以下の通りである。FILE* fopen( char* filename, char* mode );
mode | 用途 | 開始位置 | 動作 |
"r" | 読み込み用 | 先頭 | ファイルが無ければ失敗 |
"r+" | 読み書き両用 | 先頭 | 有ればそのままにする |
"w" | 書き込み用 | 先頭 | ファイルが無ければ新設 |
"w+" | 読み書き両用 | 先頭 | 有れば空にする |
"a" | 追加書き込み用 | 終端 | ファイルが無ければ新設 |
"a+" | 読み書き両用 | 終端 | 有ればそのままにする |
fopen() 関数は FILE 型構造体のアドレスを戻り値にするので 対応するポインタ変数でこれを受け取る。このポインタ変数をstreamと呼ぶ。 この値が NULL の場合は、何らかの理由でファイルを開くことに 失敗している。以後のファイル操作はすべてこの値を元に行われる。 以下にファイルを書き込み用に開く操作の例を示す。
ファイル操作の最後の操作である「ファイルを閉じる」と呼ばれる操作を 先に紹介する。これはOSにファイルの操作の後始末を依頼する儀式である。 その操作は fclose() 関数が行う。FILE* stream = fopen("datafile.dat","w"); if( stream == NULL ){ perror("Can't open the file"); exit(1); }
fclose( stream );
fprintf() 関数は標準入出力の printf() 関数のファイル版 である。第1引数に書き込み先のstreamを渡し、以降は printf() 関数 と同じで第2引数に書式制御文字列と続く。
fscanf() 関数は標準入出力の scanf() 関数のファイル版 である。第1引数に書き込み先のstreamを渡し、 以降は scanf() 関数と同じで第2引数に書式制御文字列と続く。int n; double d; fprintf( stream, "%d %lf\n", n, d );
この例はファイルの1行に整数と実数が並んであるとして、 1行ごとにファイルの最後まで読み込むループである。while( fscanf( stream, "%d %lf", &n, &d ) != EOF ){ // do some disposals }
fputs() 関数は渡された文字列をテキスト形式で 書き込む。単純に文字列を扱うのに向いている。
出力する文字列に改行コードがなければ fputs() 関数は改行しない。 標準入出力の puts() 関数とは異なる。char buf[256]="Computer in Physics\n"; fputs( buf, stream );
fgets() 関数はテキスト形式のファイルの1行または指定文字数 までを文字列として読み込む。単純に文字列を扱うのに向いている。 fgets() 関数は1行の最後の改行も読み込み配列変数に一緒に格納する。
fgets() 関数は fscanf() 関数の書式制御では表せない複雑な データでも atoi(), atof() 関数と組んで 数値を読む込むことができる。例えば1行が以下の形式の データファイルで、2列目の整数と3列目の実数だけを読み込みたいとする。char buf[256]; while( fgets( buf, 256, stream ) != NULL ){ // do some disposals }
2列目の整数は行の先頭から7文字目の所から始まり、 3列目の実数は12文字目の所から始まる (C言語では文字列は先頭の文字を0文字目として数え始める。) ので次の様にする。T 000 -100.62 -234.18 +086.04 96/12/16 13:23:51 th10-2-1
fwrite() 関数は数値も文字列もそのままの形で指定バイト数だけ ファイルに書き込む。配列変数のデータを書き込むのに大変便利である。char buf[256]; int n; double d; while( fgets( buf, 256, stream ) != NULL ){ n = atoi( buf+7 ); d = atof( buf+12 ); }
この例では double 型のデータを一度に256個書き込む。 同様に構造体型の配列変数も扱える。double data[256]; fwrite( data, sizeof(double), 256, stream );
fread() 関数は数値も文字列もそのままの形で指定バイト数だけ ファイルから読み込む。配列変数のデータを読み込むのに大変便利である。
この例では double 型のデータを一度に256個読み込む。 同様に構造体型の配列変数も扱える。double data[256]; fread( data, sizeof(double), 256, stream );
Byte orderとは2byte以上の基本型 ( short , int , long , float , double など) をメモリに格納する際の、変数の内部のbyteの順番とメモリのアドレスの 順番の対応のことである。変数の小さい方(下位)のbyteから先にメモリに 格納する方式を little endian と呼び、Intel系のCPUやSparcのCPUなどで 採用されている。逆に大きい方(上位)のbyteから先に格納する方式を big endian と呼び、MacのCPUやDEC AlphaのCPUなどで採用されている。
使用しているマシンのbyte orderは次の様にして簡単に調べられる。
int 型と double 型の変数のbyte orderをひっくり返す 関数 bswap(int) と bswap(double) は次の様になる。int n=1; if( *(char*)&n == 1 ) printf("Little endian\n"); else printf("Big endian\n");
inline void swap( char* p1, char* p2 ){ char t = *p1; *p1 = *p2; *p2 = t; } inline void bswap( int& n ){ swap( 0+(char*)&n, 3+(char*)&n ); swap( 1+(char*)&n, 2+(char*)&n ); }
他の型用の関数も引数の型を変えて swap() を適切に変えるだけ (C++では引数の型が異なる同名の関数を定義できる。) でできあがる (この目的にはC++のtemplateの機構を利用すると便利なのだが、 実行効率が著しく下がるのでこの目的には不向きである。) 。inline void bswap( double& n ){ swap( 0+(char*)&n, 7+(char*)&n ); swap( 1+(char*)&n, 6+(char*)&n ); swap( 2+(char*)&n, 5+(char*)&n ); swap( 3+(char*)&n, 4+(char*)&n ); }
次に構造体のpropertyの構造体内の位置(offset address)の 調整の仕方である。調整の前に自作した構造体の各propertyの offset addressを次のマクロを用いて調べる。
これを異種マシンで動かしてpropertyのoffset addressに変化がないなら 問題は無い。Intel系CPUではこの例の結果は 0 4 8 となり、 char のpropertyと int のpropertyの間に2byteの隙間があることが わかる。別のCPUでは 0 8 16 となるかもしれない。この場合には Intel系CPUでの構造体の定義を#define StructOffset(Object,Property) (&(((Object*)0)->Property)) struct Test { char c; int n; double d; }; printf("%d %d %d\n", StructOffset(Test,c), StructOffset(Test,n), StructOffset(Test,d) );
としてoffset addressと全体のサイズを他のCPUに合わせる調整をしてデータ を作り直すか、もしくは次の様にして double 型を int 型の 2要素の配列で扱う。さらに、endianにも注意する。struct Test { char c; char dummy1[7]; int n; char dummy2[4]; double d; };
結論として、異種マシンで共有するデータに構造体を用いるべきでない。int n[2]; double d; n[0] = *(int*)&d; n[1] = *((int*)&d+1); // write side d = *(double*)n; // read side
プログラムの実行時に、読み込むファイルと書き込むファイルを 次の様にして指定する。
読み込みだけ、あるいは、書き込みだけの場合は、それぞれ 次の様にすれば良い。program < in.dat > out.dat
これをredirectionと呼ぶ。ここでひとつ大事な注意がある。 redirectionで既存のファイルに出力する場合には、始めに そのファイルの内容が空にされてから書き込みが始まる。 従って次の様なことをしてはいけない。program < in.dat program > out.dat
こうすると最初に in.dat の内容が空にされるので、 < in.dat としても何も読み込めない。program < in.dat > in.dat
また既存ファイルへ追加書き込むをする場合には次の様にする。
なお、MS-Windowsにおいてバイナリデータをredirection で扱うには、標準入出力を次の様にしてバイナリモードにしなくては ならないことに注意しよう。program < in.dat >> out.dat
_setmode( _fileno(stdin), _O_BINARY ); _setmode( _fileno(stdout), _O_BINARY );