● ファイルの操作

プログラムの変数の値はプログラムが終ると消えてなくなります。 計算した値をプログラムが終っても残しておくために、そのデータを ファイルに移して保管します。またそのようなデータをファイルから プログラムに読み込んで計算を進めることもあります。

このようにファイルを読み書きする方法は膨大なデータを取り扱う プログラムでは必須の技術です。この章ではこのファイル操作に ついて解説します。


■ ファイル操作の基本概念

ファイルとはディスクに書き込まれたデータの固まりです。プログラムの 変数のようにメモリの中に存在するデータとは根本的に異なります。 ディスクの中に存在するデータを読み書きする操作はハード的にもメモリ のそれとは全く異なります。

ですが、これらの末端の操作はほとんど全て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構造体を作ってそのアドレスを返します。失敗するとNULLを返します。 ファイルを開く操作はよく失敗しますので、fopen関数が返した値は 必ずNULLでないかチェックしましょう。NULLの場合にはperror()関数を 使うと失敗の原因が画面(正確にはstderr)に表示されます。

使用例:
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"を 加えます。例えば

fptr = fopen("binary.dat","wb");
とします。後述の fread(),fwrite()関数を利用する場合には、 このようにバイナリモードでファイルを開いておくことが必須です。

★UNIX系のOSでのプログラマへの注意。

UNIXではMicroSoft OSでのような改行コードの変換は不要なので 何も気にすることはありません。従ってテキストモード、バイナリモード の概念すらありません。


■ ファイル読み書き関数の紹介

データ書き込み読み込み関数はいろいろな種類があります。 一部のみ紹介します。
なお、変数fptrはすべて書き込むまたは読み込むファイルへの file pointerをあらわしています。

  • fprintf(書式付き出力)
    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 );
        
  • fscanf(書式付き入力)
    int fscanf( FILE* fptr, const char* format[, arg1,arg2,... ] );
    scanf()関数のFILE版です。 fptr以外はすべてscanf()関数と同じです。
    返り値は正常に読み込んだ項目数です。
    ファイル終端またはエラーでEOFを返します。

    使用例:

        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個づつ 画面に表示します。
  • fputs(1行文字列出力)
        int fputs( char* str, FILE* fptr );
        
    str は出力する文字列のアドレスです。
    返り値は正常で0、エラーでその他の値です。
    fputsは文字列のnull terminater '\0' を出力しません。
    注意:putsは'\n'を加えて出力しますが、fputsはしません。

  • fgets(1行文字列入力)
    char* fgets( char* str, int n, FILE* fptr );
    str は文字列を格納する配列のアドレス。 n は読み込む最大文字数です。
    返り値は正常でstrの値、ファイルエンドまたはエラーでNULLです。
    fgetsは行末文字'\n'を読み込むか、n-1個の文字を読み込みまで 文字列をstrに格納し、最後に null terminater '\0' を加えます。 注意:getsは'\n'を'\0'に変換しますが、fgetsはしません。

    使用例:

        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行づつ書き込むます。 つまりコピーです。
  • fwrite(ブロック出力)
    int fwrite( void* buf, int unit_size, int n, FILE* fptr );
    fwriteはunit_sizeで指定されたbyte数を1単位として n ブロックを そのまま書き込みます。配列変数の書き込みに効果的です。この関数を 使う場合はファイルはバイナリモードで開けておくべきです。
      返り値は実際に書き込んだブロック数です。エラーで 0です。

  • fread(ブロック入力)
    int fread( void* buf, int unit_size, int n, FILE* fptr );
    freadはunit_sizeで指定されたbyte数を1単位として n ブロックを そのまま読み込みます。配列変数の読み込みに効果的です。この関数を 使う場合はファイルはバイナリモードで開けておくべきです。
      返り値は実際に読み込んだブロック数です。ファイルエンド   またはエラーで 0 になります。

    使用例:

        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の数に変換して返します。


  • 次へ
  • 目次
    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    This page was modified on Aug 3rd, 1998.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp