ファイル操作について
この項目で学ぶこと
ファイル操作について
今回はテキストファイルの操作のみ紹介します。ファイル操作をするにはstdio.hに定義されているFILE構造体を用います。ファイル操作が出来るようになるとゲームのセーブデータなどを作ることが出来るようになります。
ファイルを出力する
まずはサンプルソースを見てください。
#include<stdio.h> int main(){ FILE *fp; // ファイルポインタを宣言 fp = fopen("output.txt","w"); // ファイル名,オープンモード fprintf(fp,"output.txtを出力します。\n"); // ファイルポインタ、出力する文字列。 fclose(fp); // ファイルを閉じる。閉じないと何かと危ない。 return 0; }
ちなみにいちいちついてるfはfailの略です。説明は後で。
ファイルを読み込む
まずはサンプルソースを見てください
先ほどのoutput.txtを読み込み、画面に出力する
#include<stdio.h> int main(){ char Buf[256]; FILE *fp; // ファイルポインタを宣言 fp = fopen("output.txt","r"); // ファイル名,オープンモード fscanf(fp,"%256s",Buf); // 文字列を変数に格納する printf("%s\n",Buf); // 読み込んだ文字列を出力する fclose(fp); // ファイルを閉じる。閉じないと何かと危ない。 return 0; }
ゲームのセーブデータなどを作る場合、基本的にオープンモードはwとrの2つを覚えれば大丈夫です。
w ファイルを書き込み用に開く (テキストモード) r ファイルを読み込み用に開く (テキストモード) wb ファイルを書き込み用に開く (バイナリモード) rb ファイルを読み込み用に開く (バイナリモード)
ファイルオープンをするときに注意しないといけないのは、ファイルが存在するかどうかです。
オープンモードwでファイルが存在しなかった場合、ファイル名のテキストファイルが新規作成されます。上記の出力サンプルソースでは新規作成されています。ファイルが存在した場合は上書きされます。ファイルが存在して、書き込み権限がなかった場合、fopen関数はNULLポインタを返します。
オープンモードrでファイルが存在しなかった場合、fopen関数はNULLポインタを返します。NULLポインタはどこも指していないことをあらわします。この場合、fscanfなどでファイルを読み込もうとしてもファイルが存在しないのでエラーになり、プログラムが強制終了します。このような事態を回避するため、if文による分岐でファイルの読み込みに成功したかどうかによって処理を分けるのが普通です。サンプルソースを見てください。fscanf関数で読み込む位置は、最後にテキストを読み込んだ位置からになります。どういうことかはサンプルソースを(ry
オープンモードwでファイルが存在しなかった場合、ファイル名のテキストファイルが新規作成されます。上記の出力サンプルソースでは新規作成されています。ファイルが存在した場合は上書きされます。ファイルが存在して、書き込み権限がなかった場合、fopen関数はNULLポインタを返します。
オープンモードrでファイルが存在しなかった場合、fopen関数はNULLポインタを返します。NULLポインタはどこも指していないことをあらわします。この場合、fscanfなどでファイルを読み込もうとしてもファイルが存在しないのでエラーになり、プログラムが強制終了します。このような事態を回避するため、if文による分岐でファイルの読み込みに成功したかどうかによって処理を分けるのが普通です。サンプルソースを見てください。fscanf関数で読み込む位置は、最後にテキストを読み込んだ位置からになります。どういうことかはサンプルソースを(ry
標準入出力ストリーム
キーボードからの入力や、ディスプレイへの出力は、ファイルと同様に扱うことができます。
stdin 標準入力ストリーム (キーボードからの入力、リダイレクトするとファイルから入力) stdout 標準出力ストリーム (ディスプレイへの出力、リダイレクトするとファイルへ出力) stderr 標準エラーストリーム (ディスプレイへの出力、リダイレクトできない)
これらはFILEポインタとなっており、最初からオープンされているので、いつでも使えます。
これまで scanf を使ってデータを入力してきましたが、これは (ユーザが) 入力ミスしたときに危険なので使わない方がいいです。代わりに、 fgets 関数を使って標準入力 (stdin) から文字列を入力し、 atoi 関数 (文字列を整数にする) や atof 関数 (文字列を実数にする) などを用いて文字列を解析し数値などを得る方法がよくとられます。
関数の解説
これまで scanf を使ってデータを入力してきましたが、これは (ユーザが) 入力ミスしたときに危険なので使わない方がいいです。代わりに、 fgets 関数を使って標準入力 (stdin) から文字列を入力し、 atoi 関数 (文字列を整数にする) や atof 関数 (文字列を実数にする) などを用いて文字列を解析し数値などを得る方法がよくとられます。
関数の解説
- char *fgets(char *s, int n, FILE *stream)
- ファイルポインタ stream からの文字列を n 文字までまたは改行まで入力し、文字配列 s に書き込む。 stdio.h をインクルードする必要がある。
- int atoi(const char *str)
- 文字列 str を解析し、整数 (int型) として返す。例えば、 atoi("10saidesu!") とやると、 10 を返す。 stdlib.h をインクルードする必要がある。
- double atof(const char *str)
- 文字列 str を解析し、浮動小数点数 (double 型) として返す。例えば、 atof("3.14159265358979e+3") とやると、 3141.59265358979 を返す。 stdlib.h をインクルードする必要がある。
例
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]){ char buf[256]; int data; fputs("値を入力してください:", stderr); /* puts 関数と合わせて自習せよ。ここでは単に文字列を表示している。 */ fgets(buf, 256, stdin); /* stdin (標準入力) から文字列を入力し、配列 buf に代入する。 */ data = atoi(buf); /* 文字列 buf を調べ、 int 型の整数値として解釈し、 data に代入する。 */ printf("%d は%s\n", data, ((data & 1) == 0) ? "偶数" : "奇数"); /* ビット演算や条件演算子については自習せよ。 */ return 0; }
サンプルソース
入力したデータから平均値を求めるプログラム
長ったらしいので資料として配るときは印刷しなくていいです。
ACM/ICPC (ACM国際大学対抗プログラミングコンテスト) 風にしてみました。
入力は複数行のデータセットから構成される。まずデータ数 n のみからなる行から始まる。ただし n は正の整数であり、0以下が入力されると終了である。平均を求めたいデータを n 行入力する。
出力は各データセットの平均値である。
サンプルインプット
ACM/ICPC (ACM国際大学対抗プログラミングコンテスト) 風にしてみました。
入力は複数行のデータセットから構成される。まずデータ数 n のみからなる行から始まる。ただし n は正の整数であり、0以下が入力されると終了である。平均を求めたいデータを n 行入力する。
出力は各データセットの平均値である。
サンプルインプット
4 96 90 100 94 3 18.6 9.3 14.2 0
サンプルアウトプット
95.00 14.03
ソース
#include <stdio.h> #include <stdlib.h> #include <string.h> int inputInt(FILE *fp); /* ファイルポインタ fp から1行入力し、 int 型として返す。 */ double inputDouble(FILE *fp); /* ファイルポインタ fp から1行入力し、 double 型として返す。 */ void myFgets(char *s, size_t len, FILE *fp); /* ファイルポインタ fp から1行入力し文字列 s に入れる。 (fgets に皮をかぶせた関数) */ int main(int argc, char *argv[]){ int n; /* データ数 */ double data, sum, ave; /* 入力データ、総和、平均 */ FILE *fi; /* 入力ファイルポインタ */ int i; /* 制御変数 */ /* ファイルオープン関連処理 */ if(argc < 2){ /* コマンドライン引数指定がなかった場合 */ fi = stdin; /* 標準入力 (キーボード) から入力 */ }else{ fi = fopen(argv[1], "r"); /* ファイルオープン */ if(fi == NULL){ /* ファイルを開けなかった場合 */ fprintf(stderr, "ERROR: cannot open file \"%s\".\n", argv[1]); return -1; /* プログラムを終了 */ } } /* データ入力 */ while(1){ /* 無限ループ */ n = inputInt(fi); /* データ数を入力 */ if(n <= 0) { break; } /* 0以下で終了 */ sum = 0; for(i = 0; i < n; ++i){ data = inputDouble(fi); /* データを入力 */ sum += data; } ave = sum / n; printf("%.2lf\n", ave); } /* ファイルクローズ */ if(fi != stdin){ fclose(fi); } return 0; } int inputInt(FILE *fp){ char buf[32]; myFgets(buf, 32, fp); /* 1行入力 */ return atoi(buf); } double inputDouble(FILE *fp){ char buf[32]; myFgets(buf, 32, fp); /* 1行入力 */ return atof(buf); } void myFgets(char *s, size_t len, FILE *fp){ fgets(s, len, fp); /* fgets で1行入力 */ if(s[len = strnlen(s, len) - 1] == '\n'){ s[len] = '\0'; /* fgets は改行も含むためそれを取り除く処理 */ }else{ fflush(fp); /* 1行が長いとき残りのストリームを消す */ } return; }