ファイル操作


ストリーム

これまで私たちは"メモリ"に対してデータの読み書きを行ってきました
この作業には、特別複雑なことは必要とされませんでした

この章では、ついにハードドライブのファイル操作を学習していきたいと思います
実際に実用するプログラムでは、この操作ができなくてはならないですね

C言語では、どのような入出力デバイスであろうとも同じ手法で操作することができます
これは、ストリームと呼ばれる抽象的なインタフェイスを操作するためです
C言語プログラマは、論理的なインターフェイスである「ストリーム」を通してファイルの入出力を操作します


まず、ファイルをストリームに結びつける必要があります
そのためにはFILE構造体を使用します
FILE構造体はストリームの状態に関する情報を格納し、ストリーム入出力操作で使います
FILE構造体はstdio.hで定義されています

FILE *fp;

このようにファイル構造体のポインタ変数を宣言します
ファイルを開くのにはfopen()関数を使用します

FILE *fopen( const char *ファイル名, const char *モード );

この関数はファイルへのポインタを返します
だから、FILE構造体のポインタ変数を宣言していたのです


ファイル名は、開きたいファイル名を指定します
ファイル名だけの場合は、実行ファイルのあるディレクトリのファイルを検索します
他のディレクトリを検索する場合は、そのプラットフォーム固有の方法でディレクトリを指定します
WindowsのMS-DOSならば \DirName\test.txt というように指定します

モードは、ファイルを開く時に「読み込み/書き込み」などを指定します
ASCII文字として文字変換(改行コードなど)するテキストファイル
いっさいの変換が行われない、1対1のバイナリファイルの選択なども指定できます
(バイナリファイルは後ほど紹介します、この場ではテキストファイルを扱います)

モード説明
"r" 読み込みようとしてファイルを開きます
ファイルが見つからなかった場合はNULL(エラー)を返します
"w" 書き込みモードでファイルを開きます
ファイルがない場合は新しく作成し、存在している場合はその内容を破壊します
"a" 追加モードでファイルを開きます
ファイルがない場合は作成します
"r+" 既存ファイルを対象に、読み込み/書き込みの両方のモードで開きます
ファイルがない場合はエラーを返します
"w+" ファイルを作成し、読み込み/書き込みの両方のモードで開きます
ファイルが存在している場合はその内容を破壊します
"a+" 読み込み/書き込みモードの両方のモードで開きます
ファイルが存在する場合は追加、ない場合は作成します

fopen()関数は、ファイルのオープンに失敗するとNULLを返します
NULLはstdio.hで定義されているNULLポインタと呼ばれるマクロです
NULLポインタは評価結果が0になる整数定数、void*型にキャストされた式(void*型は後記)などが返すポインタで
標準関数などで頻繁に使用されます
NULLはいかなるポインタと比較しても等しくならないポインタとして保証されています
(Microsoftのコンパイラ固有の仕様の based ポインタ は除きます)

このNULLを利用して、fopen()関数で開いたストリームとNULLを比較します
NULLであれば、ファイルのオープンに失敗したということなので強制終了します
強制終了には、全てのバッファをフラッシュ(後処理)するexit()関数を使用します

普通、ファイルシステムはセクタごとにディスクへの書き出しを行っています
そのため、1セクタ分のデータが溜まるまではバッファにデータが蓄えられますが
exit()関数などを呼び出すと、バッファに残っているデータも自動的に書き込まれるようになっています
この作業をバッファをフラッシュするといいます
exit()関数はstdlib.hヘッダファイルに登録されています

void exit(int ステータス);

ステータスには終了状態を表します
正常終了ならば0、エラーの時にはそれ以外の値を指定するのが一般的です


ストリームにファイルを開いたならば、それを切り離す作業も覚えなくてはなりません
ストリームからファイルを切り離すにはfclose()関数を使用します

int fclose(FILE *ストリーム);

*ストリームには、閉じるファイルストリームを指定します
通常は fopen()関数で取得した有効なファイルポインタでなければなりません
fclose()関数も、バッファのフラッシュを行います

さて、ここで一度これまでの説明をまとめましょう
次のプログラムはストリームにファイルを読み込みます
#include <stdio.h>
#include <stdlib.h>

int main() {
        char fileName[128];
        FILE *fp;

        printf("ファイル名を入力してください>");
        scanf("%s" , fileName);

        fp = fopen(fileName , "r");
        if (fp == NULL) {
                printf("ファイルが開けませんでした");
                exit (1);
        }
        printf("ファイルのオープンに成功しました");

        fclose(fp);
        return 0;
}
ファイルポインタの取得に成功すれば「ファイルのオープンに成功しました」という文字が出力されます
失敗した場合は NULL が返されるのでifのステートメントが実行し強制終了されます

重要なのは、以下の作業です

FILE *fp;
fp = fopen(fileName , "r");
fclose(fp);

今後、この基本的な作業を中心にファイルポインタを操作していきます
まずは、ストリームにファイルを読み込む作業になれてください


ファイル内容を表示する

次に、読み込んだファイルの内容を表示することに挑戦しましょう
これができなくては、ファイルを開いた実感もできません

読み込んだファイルの内容を出力する方法はいくつかあります
そのうちのひとつがfgetc()関数です

int fgetc(FILE *ストリーム);

この関数は、指定されたストリームの次の文字をint型で返します
正確に言うと、unsigned char型で読み込んだ文字をint型にキャストして返します

ファイル操作の関数に共通しているお約束なのですが
エラーが発生するか、ファイルの最後に達した場合EOFを返します
EOFマクロは通常 -1 の値で、End Of File を表します
fgetc()関数が返すのはint型ですが、下位8バイトはファイルから読み込まれた内容なので問題はありません
#include <stdio.h>
#include <stdlib.h>

int main() {
	char fileName[128];
	int getf;
	FILE *fp;

	printf("ファイル名を入力してください>");
	scanf("%s" , fileName);

	fp = fopen(fileName , "r");
	if (fp == NULL) {
		printf("ファイルが開けませんでした");
		exit (1);
	}
	while(1) {
		getf = fgetc(fp);
		if (getf == EOF) break;
		printf("%c" , getf);
	}

	fclose(fp);
	return 0;
}
ただし、このようにEOFを検出する方法は確実ではありません
この方法では、エラーなのかファイルの終わりまで達したのかを確実に確認することができないのです

そこで、指定されたストリームの現在のファイルポインタの位置がファイルの最後かを調べるfeof()関数を使用します
この関数を使用すれば、確実にファイルの最後を求めることができます

int feof(FILE *ストリーム);

指定されたストリームのファイルポインタの位置がEOFならば0以外を返します
ファイルの最後でない場合は0を返します
#include <stdio.h>
#include <stdlib.h>

int main() {
	char fileName[124];
	int getf;
	FILE *fp;

	printf("ファイル名を入力してください>");
	scanf("%s" , fileName);

	fp = fopen(fileName , "r");
	if (fp == NULL) {
		printf("ファイルが開けませんでした");
		exit (1);
	}

	while(1) {
		getf = fgetc(fp);
		if (!feof(fp)) printf("%c" , getf);
		else break;
	}

	fclose(fp);
	return 0;
}
同時に、エラーをチェックするferror()関数も存在します

int ferror(FILE *ストリーム);

この関数は、ストリームでエラーが発生すると0以外の値を返します
しかし、通常はこの関数を呼び出さなくても、ファイル関数の戻り値でチェックすることができるため
普段は使用されません
#include <stdio.h>
#include <stdlib.h>

int main() {
	char fileName[124];
	int getf;
	FILE *fp;

	printf("ファイル名を入力してください>");
	scanf("%s" , fileName);

	fp = fopen(fileName , "r");
	if (fp == NULL) {
		printf("ファイルが開けませんでした");
		exit (1);
	}

	while(1) {
		if (ferror(fp)) {
			printf("エラーが発生しました");
			break;
		}
		getf = fgetc(fp);		
		if (!feof(fp)) printf("%c" , getf);
		else break;
	}

	fclose(fp);
	return 0;
}
もちろん、これ以外にも便利なファイル操作関数は存在します
次の章で、さらに詳しくファイル操作関数を紹介します


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

ファイルを開きます

ヘッダ - stdio.h
filename - ファイル名を指定します
mode - アクセスモードを指定します

戻り値 - 成功した時はファイルポインタ。そうでない場合はNULL

モード説明
"r" 読み込みようとしてファイルを開きます
ファイルが見つからなかった場合はNULL(エラー)を返します
"w" 書き込みモードでファイルを開きます
ファイルがない場合は新しく作成し、存在している場合はその内容を破壊します
"a" 追加モードでファイルを開きます
ファイルがない場合は作成します
"r+" 既存ファイルを対象に、読み込み/書き込みの両方のモードで開きます
ファイルがない場合はエラーを返します
"w+" ファイルを作成し、読み込み/書き込みの両方のモードで開きます
ファイルが存在している場合はその内容を破壊します
"a+" 読み込み/書き込みモードの両方のモードで開きます
ファイルが存在する場合は追加、ない場合は作成します

コンパイラによっては、さらに固有のモードが追加されていることもあります
モード名に続いてt(テキスト)もしくはb(バイナリ)を指定すると
それぞれテキストモード、バイナリモードで開くことができます ( "rb"など )
t または b を mode 中に指定しないと、デフォルトの変換モードはグローバル変数 _fmode によって定義されます

バイナリモードの指定は、そのファイルがバイナリファイルであることを表すだけであって
ANSI Cとの互換性のためだけに指定可能というものであり、実際の影響はない事があります

int fclose( FILE *stream );

ストリームを閉じます

ヘッダ - stdio.h
stream - 閉じるFILE構造体へのポインタを指定します

戻り値 - 正常に終了した時は0、そうでなければEOF

int fgetc( FILE *stream );

ストリームから文字を読み出します

ヘッダ - stdio.h
stream - FILE構造体へのポインタを指定します

戻り値 - 読み出した文字をint型にキャストした値。エラーかファイルの終わりに達成するとEOF

int feof( FILE *stream );

ファイルポインタの位置がファイルの終端かどうかを調べます

ヘッダ - stdio.h
stream - FILE構造体へのポインタを指定します

戻り値 - 現在の位置がファイルの終端でない場合は 0 、EOFの場合は0以外を返します

int ferror( FILE *stream );

ストリームのエラーをチェックします

ヘッダ - stdio.h
stream - FILE構造体へのポインタを指定します

戻り値 - stream上でエラーが発生していないと 0 を、それ以外の場合は 0 以外の値を返します

void exit( int status );

後処理後、呼び出し側プロセスを終了します

ヘッダ - stdlib.h
status - 一般的には正常終了の時は0、エラーの時は0以外を指定します




前のページへ戻る次のページへ