C応用4 ファイル処理(1)
C応用4 ファイル処理(1)
今までデータを記録していたメモリは、メインメモリと呼ばれるメモリです。メインメモリは高価で、プログラムが終了すると記録していたデータは無くなってしまいます。
メインメモリとは別に補助メモリというものがあり、安価で、プログラムが終了しても、データを保持しておくことが出来ます。補助メモリのデータは、ファイルという枠の中に記録されます。
ファイルにデータを記録することをファイル出力すると言います。ファイルに書き込む、という言い方もします。ファイルに書き込まれたデータをメインメモリにコピーすることを、ファイル入力すると言います。ファイルから読み込む、という言い方もします。
本章では、ファイルにデータを書き込んだり、読み込んだりするファイル処理が、コンピュータの中で、どの様に処理されているのかを説明し、そのプログラムをどのように書くのかを勉強します。
ファイル処理により、アスキーアートはプログラムの中に書かず、ファイルとして作成する事で、アスキーアートの修正では、プログラム修正が不要になります。また、ゲーム途中のデータをファイルに記録させることで、ゲーム途中でプログラムを終了させても、ゲームを再開させることが可能になります。
本章の練習問題では、引き続きバトルゲームを作成します。ステータスを機能アップし、コマンドで呪文やアイテムを選択して戦うようにしています。
ファイルの中のデータを扱うためには、オープン、出力、入力、クローズといった処理が必要になります。ファイルを扱う基本的な処理がどのようなものなのか、また、プログラムをどの様に書くのかを説明しています。
・ファイル処理はオープンから始まる
ファイルを扱うは、最初にfopen関数を呼び出す必要があります。fopen関数は、FILE*型のファイルポインタと呼ばれるポインタを返すので、これを変数に記録します。
この後、このファイルポインタを使って、ファイルの入力や出力を行います。
・ファイルオープンではモードを指定する
モードは沢山の指定方法がありますが、最初は、新規にテキストファイルを作成する"w"(writeモード)と、既にあるテキストファイルを入力する"r"(readモード)というモードを覚えると良いでしょう。
バイナリファイルについては「4.4 バイナリファイル」で説明します。
・fprintfとprintfは似ている
printf関数で画面に表示していたものを、画面ではなく、fopenしたファイルに出力するのがfprintf関数です。
fprintf関数の1つめの引数に、fopenした時に返されたファイルポインタを指定します。2つ目以降の引数は、printf関数の引数と同じ様に指定します。
・fopenで指定するファイル名は文字列
ファイル名は文字列のため、以下の3つの方法で扱うことができます。
const char*型の文字ポインタ(固定した名前の場合)
char型の文字配列(ファイル名を入力する場合等)
"ファイル名"の様な文字列定数
ファイル入力する場合、既にあるファイル名を指定することになりますが、存在しなファイル名を指定するとfopenはNULLを返します。ファイルポインタがNULLの場合は、オープン出来なかった旨をエラーメッセージとして表示すると良いでしょう。この場合、fopenする時と、エラーメッセージを表示する時に、同じファイル名を指定するため、ファイル名は、文字ポインタ若しくは文字配列で扱うと良いでしょう。
・ファイル入力はfscanf, fgets, getcを使い分ける
空白文字(スペース、タブ、改行)で区切られた文字列を入力する場合はfscanfが便利です。
アスキーアートはスペースもデータとして入力するので、スペースを含む文字列を入力する場合はfgets関数が便利です。
1文字ずつ調べながら入力する場合はfgetcが便利です。
xxx.txtというファイルはwriteモードでオープンします。yyy.txtというファイルはaddモードでオープンします。適当な文字列をfprintf関数で出力した後、fcloseするプログラムを作成しましょう。
xxx.txtというファイルと、yyy.txtというファイルが何処に作成させるか、エクスプローラで確認してください。また、xxx.txtとyyy.txtに、どの様なデータが作成されたか、メモ帳を使って見てください。
プログラムを何度か実行すると、writeモードとaddモードの違いを確認することができます。
ファイル名をキー入力し、指定したファイルの内容を表示しなさい。なお、指定されたファイルが存在しない場合を考慮する事。ファイル入力する関数は、fgets関数を使ってみましょう。
練習問題19と同様に、ファイル名をキー入力し、指定したファイルの内容を表示します。ただ、今回は、getc関数を使って、1文字ずつ読込み、読み込んだ文字が半角英小文字の場合は、英大文字に変換して表示する様にしてみましょう。
<注意> ファイルの中に全角文字が入っていると、全角文字が文字化けする可能性があります。
キー入力は標準入力、画面表示は標準出力と呼んでいます。標準入出力は、一般ファイルと同じ関数を使って入出力することができます。
一般ファイルの入出力関数を使って、キー入力や画面表示する方法を説明します。
・標準入出力のオープンとクローズは自分でしなくてよい
一般ファイルの入出力と同様の関数が、標準入出力にも用意されています。一般ファイルと標準入出力の違いは、一般ファイルは、オープンとクローズを自分で行わなければならないのに対し、標準入出力は、オープンとクローズを、自分で行う必要がありません。
・標準入出力のファイルポインタとしてstdinとstdoutが使える
一般ファイル用の入出力関数に対して、stdinやstdoutをファイルポインタとして指定すると、キー入力や画面表示を行うことができます。
クイズファイル(question.txt)を事前に、メモ帳で作成します。このクイズファイルを入力して、クイズを出題するプログラムを作成しなさい。最後に、採点結果を結果ファイル(result.txt)に出力しなさい。
練習問題21のプログラムで、採点結果ファイル(result.txt)がwriteオープンできない場合は、出力先を画面に切り替えなさい(画面出力ではstdoutを使う)。
アスキーアートをファイルに作成し、ファイル入力して画面に表示するプログラムを例に、二次元配列を使った方法と、動的メモリを使った方法を説明します。
動的メモリは、プログラム実行中に獲得するメモリのことで、ポインタを多用した、少し難しいプログラムになります。これを勉強することで、ポインタを深く理解するのに役立つでしょう。
・アスキーアートのファイルは二次元配列に入力できる
ファイルに入っているアスキーアートの大きさが分かっていて、変更されることがない場合は、二次元配列にファイル入力するプログラムが一番簡単です。
ただ、左記に書いた欠点があります。
・malloc, calloc関数で動的メモリが獲得できる
必要な量のメモリを、関数呼出しすることで獲得できます。malloc関数やcalloc関数は、獲得したメモリのアドレスを返してくれるので、予め、ポインタ変数を宣言し、そこに動的メモリのアドレスを記録します。
動的メモリは、使い終えたならばfree関数で返却するのが作法になっています。
・ファイルの大きさを調べてから動的メモリを獲得する
アスキーアートを文字ポインタ配列として扱える様にするためには、文字数と行数を調べる必要があります。
文字数と行数が分かると、ファイル入力に必要なメモリサイズが分かります。必要なメモリを動的メモリから獲得し、ファイル入力しながら、文字列としてメモリに記録していきます。
・ファイル入力しながら動的メモリで文字列配列を作る
獲得した動的メモリの最初の方に文字ポインタ配列を作り、その下部に文字列を作っていきます。
この様なプログラムを作っておくと、アスキーアートだけでなく、色々な情報をテキストファイルに作成しておき、プログラムで利用できるようになります。
アスキーアートを、メモ帳などを使って、ファイルに作成しておきます。このファイルを二次元配列に入力した後、適当な行桁位置に表示するプログラムを作成しましょう。
練習問題23で作成したプログラムを、動的メモリを利用するやり方に変えなさい。なお、文字列は文字ポインタ配列を使ったやり方にして、今までのアスキーアートよりも大きなアスキーアートも表示できる事を確認しなさい。
練習問題24のプログラムの中の、ファイル入力した文字列を動的メモリに読み込む処理を関数化し、複数のアスキーアートの入ったファイルを読み込んで、画面上の色々な場所に、ファイルから読み込んだアスキーアートを表示してみましょう。
このプログラムは、次章で、バトルゲームに組み込んで、アスキーアートを使ったバトルゲームに成長させます。
練習問題15で作成したバトルゲームのステータス自動生成関数をレベルアップしたものを用意しました(YouTube動画の説明の所からソースファイルがダウンロードできます)。
ダウンロードしたプログラムでは、対戦相手の名前は、配列の初期化でセットしています。新たなステータスの自動生成関数により、対戦相手は、名前から算出された種データを元に職業が決まり、呪文やアイテムを所持する様になります。自動生成されたステータス情報を表示した後、プレイヤーの名前を入力、自動生成されたプレイヤーのステータス情報を表示して、プログラムは終了します。
このプログラムを、ファイル入力した名前を使う様に変更しなさい。ファイルにセットされている名前の数は不定とし、動的メモリを使って入力するようにしましょう。
<注意> 今までのバトルゲームでは、体力、ちから、素早さというステータスしかありませんでしたが、この問題以降のバトルゲームでは、HP, MP, ちから, 素早さというステータスに加え、職業、呪文、アイテムを持つようになります。
練習問題26で作成した対戦相手のステータス情報を、画面表示ではなく、ファイルに出力する様にしなさい。また、プレイヤーの名前入力を繰返し行う様にして、Enterキーの場合は、直前のプレイヤー名が入力されたと見なし、プレイヤーの名前に0を入力した場合に、プログラム終了するようにしなさい。
Enterキーだけを入力した時、直前のプレイヤー名が入力されたとみなすためには、scanf関数は使えません。scanf関数は、何らかの文字列を入力されるまで、呼出し元に戻りません。fgets関数でファイルポインタにstdinを指定して入力するようにしてみましょう。1行データが改行文字を含めて入力されます。fgets関数で入力した1行データの中に入っている文字列は、sscanf関数を使って取り出すことができます。
練習問題27のプログラムにバトル処理を追加したものを用意しました(YouTube動画の説明の所からソースファイルがダウンロードできます)。
バトルは、プレイヤーが指定した番号の対戦相手と行います。また、勝敗決定後も、別の対戦相手とバトルを繰り返せるようにします。
バトルでは、「戦う」か「呪文」を選んで、攻撃します。「戦う」は、アイテムを装備して、ちからで相手にダメージを与えます。「呪文」は、MPを持っている場合に使うことができますが、時には失敗することもまります。設定された確率で成功すると、相手にダメージを与えることができます。「戦う」の時も、設定された確率で「会心の一撃」を出すことができます。「会心の一撃」を出す確率は、アイテムを装備していると低くなるようにします。確率は、後で調整し易くするため、配列の初期化で値を設定しています。
練習問題28のプログラムでは、バトルの経緯を画面に表示していましたが、buttle関数に、writeオープンしたファイルポインタを渡すと、バトル経緯をファイル出力できます。
この機能を使って、全ての対戦相手と100回ずつバトルした結果の勝率を表示するようにします。また、対戦相手毎の勝率については、一覧形式でファイル出力します。
一覧形式の結果を見て、天敵(絶対勝てない対戦相手)がいないかを確認します。次の練習問題30では、天敵が出ないように、バトル処理を変更していきます。
勇者と僧侶は、神様のご加護を受けることが出来るようにします。神様のご加護を受けると、相手の攻撃のダメージを受けなくなります。
また、新しい呪文を追加します。今回のプログラムでは、呪文はディスパッチテーブルを使った方法で実装しています。ディスパッチテーブルの追加方法を説明します。
プレイヤーは対戦相手に勝つと、「ちから」、「素早さ」、「最大HP」、「最大MP」のいずれかのステータス値が、1から3ポイントアップするようにします。また、ステータス値は、自分で決めた最大値(今回は80)を超えないようにします。
今回、プレイヤーは、ランダムに選ばれた生き残っている対戦相手とバトルします。そして、全ての対戦相手に勝つまでバトルを繰返すようにします。練習問題30と同様に、バトルの経緯は、画面には表示せず、プレイヤーの名前の付いたファイルに出力します。
練習問題31を元に、プレイヤーのバトル経緯を画面に表示するように変更します。buttle関数のファイルポインタにstdoutを指定することで、プレイヤーの攻撃はコマンドを選択する方式に変わります。
コマンドの中に「逃げる」があります。「逃げる」が選択された場合は、バトル継続が問われて、継続しないを選ぶと、別のプレイヤーの名前を入力するようにします。
このゲームは、全ての対戦相手を倒すことでゲームクリアになりますが、このサンプルの場合、99人を倒さなければゲームクリアになりません。かなり時間のかかるゲームになりますので、途中でゲームを中断し、再度、ゲームを再開出来るようにする機能が必要になってきます。このゲーム中断と再開機能については、次のバイナリファイルの所で作成します。