ファイル入出力関数 詳細

ファイル入出力関数の詳細と具体例を示す.

目次

ファイルのオープン・クローズ

fopen() 関数のプロトタイプ宣言は,

FILE *fopen(const char *filename, const char *mode); 

となっている.

ファイル名の指定

fopen 関数の第 1 引数「ファイル名」は,ディスク上でのファイルの名称を表す文字列である.
ファイル名を表す文字列定数や変数を渡せばよい.
例:"textfile.txt"など.

パスを省略した場合は,exeファイルと同じフォルダ内(またはOSが指定する作業ディレクトリ)にファイルが書き出され/読み込まれる.
作業ディレクトリと別の場所にファイルを保存したければ,フルパスで指定する.
例えば,"c:\\users\\textfile.txt"
パスの区切り文字は\\とすること! さもないとエスケープシーケンスとみなされて,ファイルが開けない.

モードの指定

fopen 関数の第 2 引数「モード」は,同じく文字列であるが,以下に示すファイルに対する操作を指定する.
バイナリファイルの場合には,モードの文字列にbを追加し,"rb","wb" のように指定する.

モード 意味 備考
"r" 読み込み専用でファイルを開く.read 既存ファイルが無ければエラー.
"w" 書き込み専用で新規ファイルを開く.write ファイルが既に存在する場合は消去・上書き.
"a" 追加書き込み専用でファイルを開く.append 元のファイルの末尾に追記.ファイルが無い場合は作成される.
"r+" 読み書き用でファイルを開く. 既存ファイルが無ければエラー.
"w+" 読み書き用で新規ファイルを開く. ファイルが既に存在する場合は消去・上書き.
"a+" 読み込み+追加書き込み用でファイルを開く. 元のファイルの末尾に追記.ファイルが無い場合は作成される.

戻り値

fopen() は,ファイルのオープンに成功すると,そのファイルへのファイルポインタ(FILE構造体へのポインタ)を返す.
ファイルポインタ変数には,「どのファイルの,どの辺りを読み書きしているか」が書かれている.

なんらかの原因によりオープンに失敗すると,NULL ポインタを返す.
このため,以下のようなfopen()の戻り値のチェック処理(NULLチェック)を必ず入れること.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "datafile.txt";    // ファイル名

    fp = fopen(fname, "w");   // fnameは,ファイル名
    if( fp == NULL ) {
        printf("エラー.%s が開けません.\n", fname);    // 開けなかったファイル名を表示
        exit(1);
    }

    // ... 読み書き処理

    printf("%s を保存しました (or 読み込みました).\n", fname);  // 処理後にファイル名とメッセージを出す場合
    fclose(fp);  // ファイルを閉じる
}

1 byte 入出力

ファイル読み書きの基本単位は 1 バイト ( byte ) であり,1 バイトづつ読み書きする関数が用意されている.

int fgetc(FILE* fp);
int fputc(int 書き出す値, FILE* fp);

fgetc, fputc = file {get, put} char の省略である.

fgetc()

1バイト読み込み fgetc() は,ファイルの現在の位置から(unsigned charとして)1バイト読み込み,int型の値として返す.

この関数の戻り値は int =32bit だが,ファイル読み込んだ数値はそのうちの下位 1 バイトに格納されている.
したがって,戻り値を charunsigned char型の別の変数に代入しても,問題はない.

エラーの時(またはファイルの最後で読むものがなくなった時)は,EOF (End Of File, -1と定義されている) を返す. 文字・文字列で解説した通り,ASCIIコードはすべて符号なし整数であるため,EOF 記号を判定すれば「どの文字でもない」ことがわかる.

fputc()

1 バイト書き出しfputc() は,「書き出す値」の下位 1 バイトを, ファイルの 現在の位置unsigned char 型の値として書き込む.
戻り値は,書き込みが成功すると書き込んだ値を返し,失敗すると EOF を返す.

fgetc, fputc 関数は,ファイルに書かれている内容を数値として取り出す/書き出すだけであるので, テキストファイル・バイナリーファイルを問わずいかなるファイルでも読み書きできる.

fputc の例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "fputc_sample.txt";    /* ファイル名 */

    fp = fopen(fname, "w");
    if( fp ==  NULL ) {
        printf("エラー.%s が開けません.\n", fname);
        exit(1);
    }

    char c;
    for(c='a'; c<='z'; c++) {     /* 文字コードを使ったループ */
        fputc(c, fp);
    }

    fputc('\n', fp);              /* ファイル内で改行 */

    for(c='A'; c<='Z'; c++) {
        fputc(c, fp);
    }

    printf("%s を保存しました.\n", fname);
    fclose(fp);    /* ファイルを閉じる */
}

fgetc の例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;

    const char fname[] = "fputc_sample.txt";    /* 読み込みたいファイル名 */

    fp = fopen(fname, "r");   /* fnameは,既に存在するファイル名 */

    if( fp ==  NULL ) {
        printf("エラー.%s が開けません.\n", fname);
        exit(1);
    }


    int c;
    do {
        c = fgetc(fp);
        printf("0x%02x %d \n", c, c);    /* 読み込んだファイルの内容を 16進数 と 10進数 で表示 */
    } while ( c != EOF );

    printf("%s を読み込みました.\n", fname);
    fclose(fp);    /* ファイルを閉じる */
}

1 行入出力

ファイル内の「行」単位で文字列の入出力を行うには,fputs関数や,fgets関数を使用する.

int fputs(char* 書き出す文字列, FILE* fp);
char* fgets(char* 文字列バッファ, int 文字列バッファ長さ, FILE* fp);

fgets, fputs = file {get, put} string の省略である.
「行」という考え方がそもそもテキストファイルであるので,この関数はテキストファイルを扱うことが前提である!

fputs()

fputs() は,引数の文字列すべてをファイルに書き出す.
戻り値は,成功すると負でない値を返し,エラーの時は EOF を返す.

fgets()

fgets() は,ファイルから1行を文字列バッファに読み込み,成功すると読み込んだ文字列(の先頭アドレスを指すポインタ)を返す.
エラー(ファイルの終端に達した等)の時は, NULL を返す.

fputs の例

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "fputs_sample.txt";    /* ファイル名 */

    fp = fopen(fname, "w");
    if( fp ==  NULL ) {
        printf("エラー.%s が開けません.\n", fname);
        exit(1);
    }

    fputs("Hello world!\n", fp);        /* 直接文字列を渡す */

    char str[] = "This is sample text.";
    fputs(str, fp);                        /* 配列をを渡す */

    printf("%s を保存しました.\n", fname);
    fclose(fp);    /* ファイルを閉じる */
}

書式指定入出力

ファイル入出力で一番柔軟性が高いのは,以下の関数である.

int fprintf(FILE* fp, char* 書式指定文字列, ...);
int fscanf(FILE* fp, char* 書式指定文字列, ...);

fprintf()fscanf() は,画面・キーボード入出力で使う printf()scanf() とほぼ同じ動作をするが, 最初の引数がファイルポインタである点が異なる.
もちろん,入出力がキーボードや画面に対して行われるのではなく,ファイルに対して行われるため,例えば fprintf() 関数を呼び出しても画面には何も表示されない..

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "sample.txt";

    fp = fopen(fname, "r");
    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    char buf[100];                             // 読み込み用バッファ
    while( fscanf(fp, "%s", buf) != EOF) {     // EOF = end of file を表す記号
        printf("%s\n", buf);
    }

    fclose(fp);
    return 0;
}

バイナリ入出力

テキストデータの読み書きには,fscanf(), fprintf() 関数が便利だが,それだけでは十分ではない.
例えば,bmp, jpeg画像ファイル,mp3, wav音声ファイル,mp4, mov動画ファイルなどは, 数値データの配列であって文字列ではないため,テキスト入出力関数では扱えない.

このようなデータを扱う場合,以下のバイナリ入出力関数を用いる必要がある.

size_t  fread(void* buffer, size_t サイズ, size_t 個数, FILE* fp);
size_t fwrite(void* buffer, size_t サイズ, size_t 個数, FILE* fp);

はそれぞれ,buffer を先頭アドレスとするデータを,指定の長さ分,ファイルに読み書きする関数である.
読み書きするデータの総バイト数は,1個当たり「サイズ」バイトのデータを「個数」個,すなわち,(サイズ)×(個数)バイトとなる.

戻り値は,「実際に」ファイルに書き込んだデータの個数(バイト数ではない!)が返る. 戻り値が第3引数の「個数」と等しければ書き込み成功,それ以下であれば何らかの理由で書き込みが失敗したことを表す. (頻度は高くないが,ディスクが一杯,壊れているなど)

注1:void* は,voidポインタであり,「どんな型でも指すことができる汎用ポインタ」である.
従って,第1引数には,char だけでなく,int, floatdouble などの単純型だけでなく,配列や構造体など,あらゆるデータのアドレスを渡すことができる.
注2:size_t 型は,コンパイラがもつ最大の値を格納できる整数と定義されていて, 多くの場合,unsigned long と同等である.
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp;
    const char fname[] = "data.bin";    // ファイル名

    fp = fopen(fname, "wb");   // binary モード
    if( fp ==  NULL ) {
        printf("エラー.%s が開けません.\n", fname);   // 開けなかったファイル名を表示
        exit(1);
    }

    const int N = 5;                                    // 配列の要素数
    float a[5] = {0.0,  1.2,  -5.3,  99.99, 10000};     // float 配列

    fwrite(a, sizeof(float), N, fp);

    printf("%s を書き出しました.\n", fname);

    fclose(fp);    // ファイルを閉じる
}

は,float 型配列 5 個分 = 20 byte のデータをファイルに書き出す.

fwrite()は,戻り値として書き込むことができたデータ数を返すので,エラー処理を丁寧に書くならば

if( fwrite(a, sizeof(float), N, fp) != N) {
    printf("write error!\n");
}

とすると良い.

ファイルの状態判定

fgetc()fputc() では,「エラーの時」と,「ファイル終端の時」で戻り値が同じであり,判定できない.
また,その他の関数を使用した読み書きでも,ファイルの終端まで読み込んだかどうかの判定が必要である.

これらの判定には, feof 関数や ferror関数を使う.

int feof(FILE* fp);
int ferror(FILE* fp);

feof() は,現在の位置がファイルの終わりであれば 0以外の値 を,そうでなければ 0 を返す.
ファイルの中身にどれだけデータが書かれているか不明の場合に,while文と組み合わせて大いに役立つ関数である.

ferror() は,ファイル入出力にエラーが起きていれば 0以外の値を,エラーが起きていなけれな 0 を返す.


ファイルの終端まで読み込みたい場合

while ( ! feof(fp) ) {

    /* fgetc() など...*/

}

標準入出力とファイルの関係

fprintf関数など,多くのファイル入出力関数の引数には,fopen()関数が返した有効なファイルポインタを渡すが,ここに stdoutstderr と記述すると,ファイルではなく画面に出力される.
この機能は,ファイル出力プログラムの動作チェックに重宝する.

なお,stdoutは,コマンドプロンプトでのリダイレクト(exeファイル名に続けて > 記号で画面出力をファイルに振り替える機能)により, 出力先がファイルに変更されるが, stderrは,必ず画面に出力される.

stdoutstderr は,fopen(), close()しなくとも使用できる.

fprintf(stdout, " ... ", ...);    /* ファイルではなく標準出力に出力.printfと等価.*/
fprintf(stderr, " ... ", ...);    /* ファイルではなく標準エラー出力に出力.*/

ファイル入出力の実例

ここでは,実際のファイル入出力のサンプルプログラムを例示する.
パターンは決まっているので,サンプルを各自で実行してみて,定石を覚えてしまうと良い.

ファイル「書き出し」の例

以下のプログラムを作成し,実行してみよ.myfile.txtは,どのようなファイルとなるか. 実行後にテキストエディタで開いてみよ.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp = NULL;
    const char fname[] = "myfile.txt";   /* ファイル名.*/

    /***********************************************/
    /* 書き込みモードでテキストファイルを開く     */
    /***********************************************/

    fp = fopen(fname, "w");
    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    /***********************************************/
    /*  fputcの例.                                */
    /***********************************************/

    fputc('a', fp);
    fputc('b', fp);
    fputc('c', fp);
    fputc('\n', fp);    /*  改行 */
    fputc('d', fp);
    fputc('\t', fp);    /* tab */
    fputc('e', fp);
    fputc('f', fp);
    fputc('\n', fp);    /*  改行 */


    /***********************************************/
    /*  fputsの例                                  */
    /***********************************************/

    char str[] = "Hello Meiji!\n";
    fputs(str, fp);    /*  1行書き出し  */
    fputs(str, fp);    /*  1行書き出し  */


    /***********************************************/
    /*  fprintfの例                                */
    /***********************************************/

    for(int i=0; i<10; i++) {
        fprintf(fp, "%d , ", i);  /*  コンマ区切りの例.csvファイルとして出力する方法. */
    }
    fprintf(fp, "\n");

    fclose(fp);     /* これを忘れてはいけない. */
}

ファイル「読み込み」の例1

さきほど作成したmyfile.txtを読み込む例.先の書き出しのサンプルを実行した後に実行すること.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp = NULL;
    const char fname[] = "myfile.txt";   /* ファイル名 */

    /* 読み込みモードでテキストファイルを開く */

    fp = fopen(fname, "r");

    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }


    /*  fgetcの例.先頭から最後まで1バイトごとに,読み込みと画面に表示 */
    int c;

    while( ! feof(fp) ) {
        c = fgetc(fp);
        printf("%c (0x%02x) %d \n", c, c, c);  /*  読み込んだ1バイトを,文字,16進数,10進数で表示 */
    }

    fclose(fp);     /* これを忘れてはいけない. */
}

これを実行すると何が表示されるか,よく考えてみよ.
最後に表示される −1 は,ファイル終端記号(EOF)である.

ファイル「読み込み」の例2

同様に,myfile.txtをfgetsで読み込む例.
ファイル書き出しのサンプルを実行した後に,実行すること.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp = NULL;
    const char fname[] = "myfile.txt";   /* ファイル名 */


    /*  読み込みモードでテキストファイルを開く */
    fp = fopen(fname, "r");

    if( fp == NULL) {
        printf("File Open Error\n");
        exit(1);
    }

    /*  ここまではfgetcの場合と同じ          */
    /*****************************************/
    

    /*************************************************/
    /*  fgetsの例.                                  */
    /*  先頭から最後まで1行づつ読み込んで画面に表示  */
    /*************************************************/

    const int N = 100;    /*  行バッファのサイズ.1行の文字数より大きい値にしておく.constでないと次の行がエラーとなる.*/
    char buffer[N];       /*  行バッファ. ここに 1 行分のデータを格納する  */

    while( ! feof(fp) ) {

        if( fgets(buffer, N, fp) != NULL) {     /*  1 行読込.改行コードまで読み込まれ,ヌル文字が自動的に付加される.*/

            printf("%s", buffer);  /*  読み込んだ1行を文字列で表示*/

        }
    }

    fclose(fp);
}

これを実行すると何が表示されるか,考えてみよう.
最後に表示される −1 は,ファイル終端記号(EOF)である.


バイナリファイルの読み書きの例

以下のプログラムを作成し,実行してみよ. myfile.binは,どのようなファイルとなるか.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp = NULL;
    const char fname[] = "myfile.bin";   /* ファイル名 */

    /*  書き込みモードでバイナリファイルを開く */
    if( (fp = fopen(fname, "wb")) == NULL) {
        printf("File Open Error¥n");
        exit(1);
    }

    const int N = 1;     /*  データ個数 */

    int i = 100;         /*  データ */

    if( fwrite(&i, sizeof(int), N, fp) != N) {
        printf("write error¥n");
        exit(1);
    }
    fclose(fp);

    i = 0;        /* clear */

    /*************************** ここでいったん区切り *****************************************/

    /* 読み込みモードでバイナリファイルを再度開く */
    if( (fp = fopen(fname, "rb")) == NULL) {
        printf("Open Error¥n");
        exit(1);
    }

    if( fread(&i, sizeof(int), N, fp) != N) {
        printf("read error¥n");
        exit(1);
    }
    fclose(fp);

    printf("i = %d¥n", i);
}