Nodachisoft Nodachi Sword Icon
  
@あまじ✎ 2021年3月12日に更新

第2章59 ファイルへの書き込み

イチからゲーム作りで覚えるC言語
第2章58 キャッシュ、メモ化を使用してのプログラム高速化 : PREV
NEXT : 第2章60 ファイルの読込 :

概要

ゲームでセーブやロード機能を作るとき、 ファイルとして補助記憶装置(HDDやSSD)に書き出すことや、 ネットワーク経由で、ゲームサーバにデータを送ることが一般的です。

ここではファイルとして補助記憶装置に書き出すことをイメージし、 C言語でファイルの読み書きや扱える方法を確認していきましょう!

ファイルの書き出し

早速ですが、適当なゲームデータを ファイルとして保存するイメージでプログラムを書いていきます。

 
writefile.c
#include <stdio.h>
int main () {
  char data[126] = "name=ぷらんく\nlevel=1\nhp=10\n";
  FILE *fp;
  fp = fopen("save1.txt", "w");
  if ( fp == NULL ) {
    printf("エラー!データの保存失敗!\n");
  } else {
    fprintf( fp , data );
    printf("データを保存しました\n");
    fclose(fp);
  }
}

これを実行してみると、下のように結果がでました。

結果
データを保存しました

プログラムを実行したフォルダに、「save1.txt」というテキストファイルが 出来ているかと思います。 開いてみると、下のような内容でデータが記述されています。

save1.txt
name=ぷらんく
level=1
hp=10

無事にゲームデータっぽく、名前、レベル、体力がテキストとして保存されました。

コードの中を追ってみる

コードを確認していきましょう!

3 行目で変数 data の宣言と定義をしています。

writefile.c
  char data[126] = "name=ぷらんく\nlevel=1\nhp=10\n";

この data に入っている文字列がテキストファイルとして保存されます。

保存するデータは「name=ぷらんく\nlevel=1\nhp=10\n」ですね。 「\n」は改行コードを表します。 ですので、これをテキストとして保存すると、「\n」は実際には「改行」となります。

4 行目に FILE ポインタ型が登場します。

writefile.c
  FILE *fp;

この FILE ポインタ型は、補助記憶装置から読み込むファイルへのアドレスを格納できます。

C言語でファイルへのアクセスをする場合、まずはファイルへのアドレスを取得して、 この FILE ポインタを専用の読み書きするための関数に渡すことで、データを読みだしたり書き出したりします。

今回は適当に、「fp」という変数で定義しました。(file pointer の頭文字をとってます)

5 行目の fopen 関数でファイルを新規作成しています。

writefile.c
  fp = fopen("save1.txt", "w");

fopen 関数は stdio ヘッダの中で下の用に定義されています。

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

今回は第一引数で「save1.txt」という名前のファイル名を扱うと指定しています。 また第二引数のモードで「w」を指定しています。

モード「w」は write の略で、書き込みを意味します。

このモードで fopen でファイルを指定すると、ファイルが無ければ新規作成し、 ファイルが既にあれば、上書きするモードでファイルを開きます。

また、書き込みするモードで開いている間は、他の「メモ帳」などからは 「save1.txt」を編集上書きはできない状態になります。

6 行目ではファイルへのポインタが得られなかった場合での条件分岐を書いています。 fopen は、与えられたファイル名をテキストファイルを書き込みモードで開こうとした結果、上手くいかなかった場合に NULL が返る仕様となっています。

writefile.c
  if ( fp == NULL ) {

例えば、要領いっぱいの USB や SD カードなどで、さらに書き込みをしようとした場合や、 CD や DVD などの読み込み専用メディアで実行しようとしたとき、 新しくファイルを作成できずに、エラーとなります。

このような場合に NULL が返ってきます。

エラーハンドリング

こういったエラーが発生したケースをちゃんと条件分岐で処理してあげることを エラーハンドリングと呼びます。 なにかのエラーが発生したとき、作者に問い合わせしたりバグ報告していただくためには エラーハンドリングをちゃんとプログラムに組み込んで、 なぜエラーが発生したのかを表示してあげると良いです。

ちゃんと fopen でファイルを開いた場合、下の 8行目~12行目の else 内が実行されます。

writefile.c
  } else {
    fprintf( fp , data );
    printf("データを保存しました\n");
    fclose(fp);
  }

ここで、新しく fprintf 関数が登場しています。 printf 関数になんだか似ていますね。

実際、コンソール(画面)に文字列を出力するか、 ファイルに出力するかの違いがある暗いです。

fprintf 関数は第一引数に、出力先の FILE ポインタを指定します。

fprintf_def
fprintf ( printf を出力する先の FILEポインタ, printfの引数と同じ... )

これを踏まえて、9 行目の fprintf 関数は FILEポインタ f に対して、 文字列の入った変数 data を出力していることになります。

例として fprintf は下のような書き方もできます。 第二引数以降に printf 関数と同じようにフォーマットを書いたり出来ます。

fprintf_ex
FILE *fp = fopen("save2.txt", "w");
fprintf( fp, "書き込むデータ\n");
fprintf( fp, "数字=%d\n", 100);
fprintf( fp, "名前:%s", "planc");

この例を実行した場合、下のようなテキストファイル「save2.txt」ができます。

save2.txt
書き込むデータ
数字=100
名前:planc

続いて 11 行目で fclose() 関数が登場しています。

writefile.c
    fclose(fp);

fclose 関数を呼び出すと 指定した FILEポインタでのファイル操作を終了します。

具体的には、今まで fprintf 等で実行していた書き込み処理は 1 バイトずつ毎回ファイルに書き込んでいるのではなく、 一次的にウラでバッファに蓄えられ、効率よく一度にファイルに書き込みする処理を実施していますが、 そういった書き込み未済のバッファを、fclose でもれなく書き込むという処理をしています。

ファイルへの操作が終わったら忘れずに fclose 関数を読んでおきましょう!

なお、fclose 関数は正しくファイル操作が完了できたら、 0 を返し、何かのエラーが発生すると EOF を返す仕様です。

streamとflush

ファイルの読み書きをする時に、1バイトずつディスクからデータをとってくるのは、 遅くなります。ディスクやメモリは一度に決まったサイズを取得すると効率よくなる仕組みがあるためです。 直接プログラマが意識して操作するケースは少ないですが、高速化のため、 実際にはメモリ上にため込んで(バッファと呼んだりします)効率よくタイミングをみてディスクに書き込んだり、 一度にデータの塊を読み込んだりしています。 このようにバッファに連続したファイルデータの塊を読み込んで、効率よく扱えるようにした仕組みをストリーム(stream) と呼ばれます。 また、強制的にストリームの中身をファイル等に反映されることを フラッシュ(flush)と呼びます。

補足 fopen のモード一覧まとめ

いろいろなモードがありますが、 渡しは単純にテキストデータやバイナリデータの読み書きが出来れば良いことが多いので、 とりあえず「r」「w」「rb」「wb」だけ理解しておけば十分でした。

必要に応じて、他のモードを確認する位ですが、一応備忘を含めて C11 仕様書から 一覧と効果をまとめておきます。

モード 効果
r read。テキスト読み込み
w write。新規テキストファイル作成、もしくは上書き
wx 上書き。対象ファイルが存在しなければ NULL を返す
a append。対象テキストファイルの末尾に追記
rb read binary。バイナリファイルを読み込み
wb write binary, バイナリファイルを書き込み
wbx バイナリファイル作成。対象ファイルが存在しないと NULL を返す
ab 対象バイナリファイルの末尾にデータ追記
r+ テキストファイルを読み込み。更新モード。
w+ テキストファイルを新規作成で書き込み、更新モード。
w+x テキストファイルを上書き。更新モード。対象ファイルが無いと NULL を返す。
a+ ファイル末尾から追記。更新モード。
r+b
rb+
バイナリファイルを読み込み。更新モード。
w+b
wb+
バイナリファイルを新規作成、もしくは上書き。更新モード。
w+bx
wb+x
バイナリファイルを書き込み、更新モード。
a+b
ab+
バイナリファイルの末尾から追記。更新モード。

いろんなモードがありますね。

上の表で、'+' が含まれているモードは更新モード(for update)で開きます。 すでに存在するファイルの内容を更新したい場合では、現在の内容の読み出しと、更新後の内容の書き込みの両方を行う必要がありますが、 ここでは詳細は割愛します。

※更新モードは巨大なファイルの一部を高速に更新するときには便利ですが、 そこそこのサイズのファイル操作であれば、単純に "r" 読み込みと、 "w" 上書き書き込みでやりたいことのほとんど満たせるとは思います。

参考

(英語) C11仕様 - N1570 - 7.21.5.3 The foopen function - https://port70.net/~nsz/c/c11/n1570.html#7.21.5.3

(英語) C11仕様 - N1570 - 7.21.5.1 The fclose function - https://port70.net/~nsz/c/c11/n1570.html#7.21.5.3

(英語) C11仕様 - N1570 - 7.21.6.1 The fprintf function - https://port70.net/~nsz/c/c11/n1570.html#7.21.6.1

イチからゲーム作りで覚えるC言語
第2章58 キャッシュ、メモ化を使用してのプログラム高速化 : PREV
NEXT : 第2章60 ファイルの読込 :
 
 
送信しました!

コメント、ありがとうございます。

なんかエラーでした

ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。

Twitter:@NodachiSoft_jp
お名前:
 
連絡先:
 
メッセージ:
 
戻る
内容の確認!

以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください

お名前:
 
連絡先:
 
メッセージ:
 
Roboto からの操作ではないという確認のため確認キーを入れてください。
確認キー=95
戻る
 / 
送信確認へ
コメント欄
コメント送信確認へ

関連ありそうな記事(5件)です!

第1章01 Visual Studio Community 2019 のインストール手順

#C11仕様#C言語#ゲームプログラミング✎ 2021-08-08
C言語でプログラミングをするために、無料で使える Visual Studio Community を使った開発環境を揃えていく手順や注意点をお話しています。
目次
第2章59 ファイルへの書き込み
第2章59 ファイルへの書き込み
概要
概要
ファイルの書き出し
ファイルの書き出し
コードの中を追ってみる
コードの中を追ってみる
補足 fopen のモード一覧まとめ
補足 fopen のモード一覧まとめ
参考
参考
Nodachisoft © 2021