配列、構造体と、扱うデータ量が増えてくると必然的に、それらのデータを別のファイルに保存したい、あるいは別のファイルに保存してあるデータを参照した、いという要求がでてくるでしょう。そこで今回はファイル操作をする方法の説明です。また、今回使うファイル操作に関わる関数及び変数型「FILE」の定義は「stdio.h」にあります。
/*01*/ #include <stdio.h>
/*02*/ #define FNAME "d:\\my documents\\data.txt"
/*03*/
/*04*/ main()
/*05*/ {
/*06*/ int age;
/*07*/ char name[32];
/*08*/ FILE *fp;
/*09*/
/*10*/ printf("あなたの名前を入力してください。 -> ");
/*11*/ scanf("%s", name);
/*12*/ printf("あなたの年齢を入力してください。 -> ");
/*13*/ scanf("%d", &age);
/*14*/
/*15*/ fp = fopen(FNAME, "w");
/*16*/ fprintf(fp, "Hello, world.\n");
/*17*/ fprintf(fp, "あなたの名前は %s です。\n", name);
/*18*/ fprintf(fp, "あなたの年齢は %d です。\n", age);
/*19*/ fclose(fp);
/*20*/
/*21*/ printf("処理が終わりました。\n");
/*22*/
/*23*/ return 0;
/*24*/ }
ファイルを開く時は fopen 関数を使い、引数部分に対象となるファイルの名前を書くのですが、ズラズラとファイル名を書くのはあまりいい方法ではないので、ファイル名を定義しておきましょう。(ファイル名を定義しておくか、直接書くかは状況によって使い分けてください。)
この時、2つ注意して欲しい点があります。
1つ目は、フォルダ(ディレクトリ)の指定方法です。ディレクトリを指定する時、Win 上では「\(もしくはバックスラッシュ)」記号を使いますが、「\」記号というのはエスケープシーケンスをあらわす特別な記号です。ですから、「\\」というふうに記号を2つ続けて書かなければいけません。(UNIX では「/」記号のパス形式に従う:「//」とする必要はありません。また、Windows も「\」の代わりに「/」を使うことができます。)
2つ目は、ファイルの場所です。この例では、ファイルの場所をフルパス(つまり明確に)で書いています。こうすると、確かにその場所のファイルを参照するようになります。これに対し、ファイル名だけ書くと、プログラムのあるディレクトリを参照します。そのプログラム用のデータファイルであれば、こちらの方法を使ってプログラムとセットで扱うようにしてもいいでしょう。また、プログラムが動いている(つまりプログラムがある)ディレクトリを「カレントディレクトリ」といいます。ちなみに Visual C++ の場合、コンパイルされたファイルは、ソースファイルのあるフォルダに Debug というフォルダが作られ、その中に実行ファイルが作られますよね。この時、普通に(つまりエクスプローラなどから)実行した場合、カレントディレクトリはプログラムのあるディレクトリになりますが、Visual C++ から試験実行した場合、カレントディレクトリはソースファイルのあるディレクトリになります。実用化する時は気をつけてください。
また、ファイル名は拡張子も含め好きなものにしてかまいません。Win 環境で使っているのでしたらメモ帳などで編集できるように拡張子を「txt」としてもいいでしょう。(拡張子ごとにデータの保持形式が変わるということはありません。ですから、拡張子を dat などとしてもメモ帳で開くことができます。)
ファイル・ポインタ = fopen(ファイル名, モード); 例: fp = fopen(FNAME, "w");
この時「"w"」と「"a"」は、指定されたファイルが見つからなかった場合、指定された名前の空のファイルが自動生成されます。指定されたファイルがある場合は、「"w"」ならファイルの内容を上書き保存し、「"a"」ならそのファイルの1番後ろから追加書き込みします。(「"w"」の場合、当然それまでのデータは消去されます。)
それに対し「"r"」はファイルが見つからなかった場合、fopen 関数が「NULL」を返してきますので、これを利用してエラー制御を行ないましょう。下は「"r"」モードによる、ファイルのオープンとエラー発生時の処理をまとめたものです。
if((fp = fopen(FNAME, "r")) == NULL){
printf("ファイルがありません。終了します。\n");
return 0;
}
また、モードごとの違いをまとめておきました。
モード ファイルが無かった時 ファイルに対する操作 "w" 自動生成 ファイルにデータを上書き "a" 自動生成 ファイルにデータを追加書き込み "r" NULL を返す ファイルのデータを読みこむ "w+" 自動生成 ファイルにデータを上書き、読み込みも可 "a+" 自動生成 ファイルにデータを追加書き込み、読み込みも可 "r+" NULL を返す ファイルのデータを読みこむ、書き込みも可
さて、実際に開かれたファイルはどのように操作されるのでしょうか。それほど難しくありません。画面への出力、キーボードからの入力とほとんど同じで、ファイルを1枚の紙に見たてれば左上から順にファイ・ポインタが移動して、書き込みや読み込みを行ないます。そういう意味では、ファイルは「紙」、ファイ・ポインタは「ペン」と見たてることができるかもしれません。
では、実際の書き込みです。
name に totobon, 年齢に 300 と入力した場合
fprintf(fp, "Hello, world.\n"); /*(1)*/
fprintf(fp, "あなたの名前は %s です。\n", name); /*(2)*/
fprintf(fp, "あなたの年齢は %d です。\n", old); /*(3)*/
/* 以下、ファイルに書きこまれた内容と、各 fprintf 後の fp の位置 */
(1):Hello, world.<(fp)
(2):あなたの名前は totobon です。<(fp)
(3):あなたの年齢は 300 です。<(fp)
/*「<」は改行コード(つまり「\n」)*/
また、書きこみを行なう関数は fprintf のほかにもいくつか用意されています。
fputc(1文字, fp); /*1文字を書き込む*/
fputs(文字列, fp); /*文字列を書きこむ*/
/*「fp」はファイルポインタ*/
最後にファイルに対する操作が終わったら、ファイルを閉じます。下のほうに「fclose」という関数がありますね。これの引数に操作したファイルのファイル・ポインタを書くだけです。
fclose(ファイル・ポインタ); 例: fclose(fp);
次は、ファイル内容を読みこんでみましょう。対象ファイルは、上のリストで作ったファイルであるとします。
/*01*/ #include <stdio.h>
/*02*/ #define FNAME "d:\\my documents\\data.txt"
/*03*/
/*04*/ main()
/*05*/ {
/*06*/ char ch;
/*07*/ FILE *fp;
/*08*/
/*09*/ if((fp = fopen(FNAME, "r")) == NULL){
/*10*/ printf("ファイルがありません。終了します。\n");
/*11*/ return 0;
/*12*/ }
/*13*/ while((ch = fgetc(fp)) != EOF){
/*14*/ putchar(ch);
/*15*/ }
/*16*/ fclose(fp);
/*17*/
/*18*/ return 0;
/*19*/ }
while 文で
fscanf(fp, scanf と同じ); /*scanf のように読みこむ*/
fgets(配列, n, fp); /*最大 n-1 文字の文字列を配列に格納*/
/*「fp」はファイルポインタ*/