
C言語でハードディスクなどのファイル上にあるデータを読み込む方法を確認していきます。
通常、後から変更を加えたり、自由に編集したいデータはプログラムの中に定数で書くのではなく、 ファイル等で読み込むようにしておくと、プログラム本体を変更しなくともデータを編集すれば あとからゲームバランスの変更がしやすくなるというメリットがあります。
適当なテキストファイルをゲームのセーブデータと仮定して、中身を読み込んでみましょう。
まずはシンプルに、1文字づつファイルポインタのストリームから読み込んで、ファイルの内容をすべて表示することを考えます。
#include <stdio.h>
int main () {
FILE *fp = fopen("save1.txt", "r");
char c;
do {
c = fgetc(fp);
if( feof(fp) ) {
break ;
}
printf("%c", c);
} while(1);
fclose(fp);
}
同じフォルダに「save1.txt」ファイルが無ければ作ってみてください。 私は下のようにメモ帳などで作成しました。
name=ぷらんく
level=1
hp=10
プログラムを実行すると、下のような結果となりました。
name=ぷらんく
level=1
hp=10
プログラムから save1.txt のファイルの中身を読み取り、すべて表示できていますね!
6 行目で fgetc 関数が登場しています。
c = fgetc(fp);
ファイルストリームから、一文字(1つのcharぶん)を読み込む関数です。 fgetc で 1 char ぶんを正しく読み込めたら、ファイルストリーム(変数 fp)で読み込んでいるファイルの読み込み位置は一つ後ろにずれます。 読み込んだ 1文字は return で返ってきて、変数 c に代入されています。
7行目でも新しく feof 関数が登場しています。
if( feof(fp) ) {
feof(FILEポインタ) で呼び出すことができ、引数で渡したFILEポインタの読み込み位置が ファイルの終わりに来ているかを確認してくれます。
もしFILEポインタがファイルの末尾まで来ている場合、feof は 0 以外を返し、 まだFILEポインタがファイルの途中を読み込んでいる時は、feof は 0 を返します。
6 行目は、ファイルの終わりまで読み込んだ時に、true となり、if文の中が実行されることになります。
このプログラムでは、ファイルの終わりまで読み込んだら、 break 文により、whileの繰り返しから 抜け出すことができます。
ファイルの中身を一度、プログラム内でバッファとして全て読み込んでから、 中身を処理していくようなケースはよくあります。
その場合、読み込もうとしているファイルサイズが いったいどのくらいのデータ量なのかを前もって確認したほうが安全です。
C言語の標準仕様では、対象のファイルサイズを確認するための関数はないため、 今回は、指定したファイルのサイズを確認するオリジナル関数、getfilesize を作成します。
#include <stdio.h>
int getfilesize( const char *filename) {
int filesize;
FILE* fp = fopen(filename, "rb");
if ( fp == NULL ) {
return -1;
}
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
fclose(fp);
return filesize;
}
int main () {
int filesize = getfilesize("save1.txt");
printf("サイズは %d バイトでした!\n", filesize);
}
適当に、同じフォルダに「save1.txt」というファイルを作成してから このプログラムを実行すると、下のような結果になります。
サイズは 35 バイトでした!
コードの中身を確認していきます。 getfilesize 関数の中身を見てみます。
5行目で、fopen 関数を使い、変数 filename の文字列のファイルへアクセスをしています。
FILE* fp = fopen(filename, "rb");
モードは "rb" ですので、r:読み込み、b:バイナリ で、「バイナリファイルとして読み込みするよう、ファイルを開く」という 指定をしています。結果として、ファイルへのポインタ(ストリーム)は変数 fp に代入されます。
なにかしら上手くファイルを開けなかった場合、fopen関数は NULL を返しますので、 6~8 行目で条件分岐し、 -1 を返すようにします。
if ( fp == NULL ) {
return -1;
}
続いて 9行目~10行目で fseek 関数、ftell 関数が登場します。
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
fseek は開いたファイルポインタのストリーム上での読み取り位置を変更できます。
下のような関数で定義されています。
int fseek( FILEポインタ, 開始オフセット, 起点);
ファイルの終わり(よく End of file の頭文字を問って {imp]EOF と呼びます)へ、 読み取り位置をずらすには、第三引数に、定数である「SEEK_END」を指定します。
※この SEEK_END は stdio.h ヘッダーの中で定義されています。
次に、ftell 関数で現在のファイルポインタのストリーム上での読み取り位置を整数で返します。
fseek 関数により、ファイルの末尾にファイルの読み取り位置が移動されているため、 ファイルの末尾の位置が得られることになります。
ファイルのサイズは、ファイルの末尾の読み取り位置と一致するので、 これで無事に、ファイルのサイズを得ることができました!
ゲームデータやシナリオデータなど、読み込むテキストファイルのサイズが大きい場合、
malloc 関数等を使って、ヒープ領域に確保したメモリに読み込むことが必要です。
(通常の main 関数内で確保した char buf[サイズ]
では、確保できるメモリ量に限界があるためです)
先ほど作成した getfilesize 関数で得られたファイルのサイズに合わせて、 malloc 関数でメモリを確保して、そこにテキストファイルを読み込んでから表示してみましょう!
#include <stdio.h>
#include <stdlib.h>
int getfilesize( const char *filename) {
int filesize;
FILE* fp = fopen(filename, "rb");
if ( fp == NULL ) {
return -1;
}
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
fclose(fp);
return filesize;
}
char* readtextfile( const char *filename ) {
int filesize = getfilesize( filename );
char *data = NULL;
if ( filesize == -1 ) {
return NULL;
}
data = (char*) malloc( filesize + 1);
FILE *fp = fopen(filename, "r");
if ( fp == NULL ) {
return "";
}
fread( data, 1, filesize, fp);
data[filesize] = '\0';
return data;
}
int main () {
char *readbuffer = readtextfile("save1.txt");
printf("==ファイルの中身・ここから==\n");
printf("%s\n",readbuffer);
printf("==ファイルの中身・ここまで==\n");
free(readbuffer);
}
実行すると下のような結果となります。
==ファイルの中身・ここから==
name=ぷらんく
level=1
hp=10
==ファイルの中身・ここまで==
新しく readtextfile という自作関数を用意しました。 この関数の引数にファイルを渡すと、読み取るファイルのサイズに併せて ヒープ領域にメモリを確保して、ファイルから読み込んでくれます。
読み込んだテキストファイルの中身の先頭ポインタを return で返してくれます。
readtextfile 関数のコードの中身について、新しく登場した fread 関数 について確認していきましょう! fread 関数でファイルの中身を指定したサイズ分読み込む処理をしています。
fread( data, 1, filesize, fp);
fread 関数の使い方は下の通りです。
fread( 読み込む先のポインタ, 読み込む1要素のサイズ(バイト), 要素の数, 読み込む元のFILEポインタ );
英数字であれば、1文字は 1 バイトですので、読み込む 1 要素のサイズには 1 (バイト)を指定しています。 また、文字列として filesize 文字数ぶんを読み込むので要素の数に filesize を指定します。
これで第三引数に指定した FILEポインタから 1 × filesize ぶんのサイズを、ポインタ変数 data の中に 読み込んでくれます。
fread でテキストファイルを全て読み込んだ後に、以下の処理をしています。
data[filesize+1] = '\0';
これで、文字列としての終わりを定義しています。 作成したファイルの最後に「'\0'」(終端文字)がないため、自分で追加してあげています。
これをしないと、printf で変数 data を表示しようとしたときに、'\0' が存在せず、 文字列としての終わりがわからないため、おかしな表示をしてしまう可能性があります。
この終端文字を最後に入れておきたかったので、メモリを malloc 関数で確保する時に、欲しいサイズとして 1 バイトぶんプラスしています。
data = (char*) malloc( filesize + 1);
下は参考として、バイナリデータを読み込むための関数例です。 ※ファイルが存在しないときのエラーチェックは省略してます。
// 指定したファイルのファイルサイズを確認して return で返す
int getFileSize(const char *fileName) {
FILE* fp = fopen(fileName , "rb");
long fileSize = 0;
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
rewind(fp);
fclose(fp);
return fileSize;
}
// バイナリデータをヒープ領域のメモリに読み込む
char* readBinaryFile(const char* fileName) {
int fileSize = getFileSize(fileName);
char* fileData = (char*)calloc(sizeof(char), fileSize + 1);
FILE* fp = fopen(fileName, "rb");
int resultSize = fread(fileData, sizeof(char), fileSize, fp);
fclose(fp);
if (resultSize != fileSize) {
printf("システム:正しくファイルが読み込めませんでした。サイズが一致しません。\n");
exit(-1);
}
return fileData;
}
readBinaryFile 関数で引数に読み込みたいバイナリファイルを指定すると、 全てメモリのヒープ領域に読込、ポインタを返してくれます。
(英語) C11仕様 - N1570 - 7.21.9.2 The fseek function - https://port70.net/~nsz/c/c11/n1570.html#7.21.9.2
コメント、ありがとうございます。
ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。
Twitter:@NodachiSoft_jpお名前:以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください
お名前: