ファイルへの出力

この節では、ファイルへデータを書き込む方法について学びます。

ファイルに出力する

プログラミング A で最初に作ったプログラムを思い出して下さい。 ただ単に標準出力(ターミナル)に表示するだけのものです。

#include <stdio.h>

int main() {
  printf("My name is Enokida Yuuichirou.\n");
  return 0;
}

これは My name is Enokida Yuuichirou. という表示をただ行うプログラムですが、これの出力を標準出力ではなくファイルに行うようにします。 つまり printf による出力先をファイルにするのです。

実験

プログラムは以下のようになります。 手元に保存して、実行してください。

#include <stdio.h> 
#include <stdlib.h>

int main() {
  FILE *outputfile;         // 出力ストリーム
  
  outputfile = fopen("d.txt", "w");  // ファイルを書き込み用にオープン(開く)
  if (outputfile == NULL) {          // オープンに失敗した場合
    printf("cannot open\n");         // エラーメッセージを出して
    exit(1);                         // 異常終了
  }
  
  fprintf(outputfile, "My name is Enokida Yuuichirou.\n"); // ファイルに書く

  fclose(outputfile);          // ファイルをクローズ(閉じる)
  return 0;
}

(ソースコード filewrite2.c

実行例です。

% cat d.txt
cat: d.txt: No such file or directory  ←この時点ではファイルが存在しなかった
% cc -o filewrite2 filewrite2.c
% ./filewrite2                         ←実行したことによって
% cat d.txt 
My name is Enokida Yuuichirou.         ←ファイルができた
% 

以下のように比較すると、ファイルに出力するために何が増えて何が変更されたかがわかるでしょう。

FILE ポインタを用意し、fopen() によってオープンし、fclose() によって閉じるまでの間に処理をする、という点では入力処理とまったく同じ構造です。 fopen がエラーを起こした時(有り得ないファイル名を指定してしまった等)のエラー対応の仕組みについても全く同じです。 目新しいのは以下の二点でしょう。

  1. fopen のモード指定(第二引数)が "r" ではなく "w" である
  2. fprintf() 関数が初めて登場した

以降にそれらの点について順次説明します。

書き込みモードでファイルをオープン

モード指定(第二引数)を "r" ではなく "w" にすることで、fopen はファイルを書き込み用にオープンします。 (もちろん、"w" は "write" に由来します。)

outputfile = fopen(ファイル名, "w");  // ファイルを書き込み用にオープン

ファイルが無事にオープンできたとき、 fopen はやはり FILE 構造体へのポインタ(ストリーム)を返しますので、それ以降、 そのポインタを通じてファイルへの出力を行います。

書き込みモードが指定されたとき、fopen は、第1引数に与えられたファイル名を持つファイルがまだ存在しなければ、それを作成します。作成直後、ファイルの内容は空です。気をつけなければならないのは、指定されたファイル名のファイルがすでに存在する場合です。 その場合、そのファイルの内容はいったん完全に消され、空になってしまいます。 ファイルの内容が消されては困る場合、 このモードでオープンしないように注意しなければなりません。

ファイルオープンモードのまとめ

ここまでで、"r" と "w" という2つのファイルオープンモードについて学びましたが、他のファイルオープンモードについても少し触れておきましょう。

"w" モードでは、すでに存在するファイルを開くとその内容が空になるのでしたが、 そうではなく、現在の内容はそのままにしておいて、 その末尾に新たな内容を書き足したい場合もよくあるでしょう。 例えば、ソフトウェアの動作記録をファイルに残していくような場合、 以前の記録をそのままにして、新たな記録をファイル末尾に書き足せばいいはずです。 そのようなときは、"a" モード(追加モード、"a" は append に由来)を使用します。

また、すでに存在するファイルの内容を更新するような場合、現在の内容の読み出しと、更新後の内容の書き込みの両方を行わねばならないので、ファイルを読み書き両用にオープンする必要があります。そのような場合は、"r+", "w+", "a+" の3つのモードを用います。ただし、この3つのモードの使い方は少し複雑なので、この章では詳しく解説しません。

以上の6つのモードについて、表にまとめておきます:

ファイルオープンモード
モード働き
"r"読み出し(のみの)モードでファイルをオープンする。ファイルが存在しないときはオープンできない。
"w"書き込み(のみの)モードでファイルをオープンする。ファイルがなければ作成し、存在していれば最初にその内容を空にする。
"a"追加モードでファイルをオープンする。ファイルがなければ作成し、存在していれば、現在の内容はそのままで、ファイル末尾に追加書き込みが行われる。
"r+"読み書き両用のモードでファイルをオープンする。ファイルが存在しないときはオープンできない。
"w+"読み書き両用のモードでファイルをオープンする。ファイルがなければ作成し、存在していれば最初にその内容を空にする。
"a+"読み書き両用のモードでファイルをオープンする。ファイルがなければ作成し、存在していれば、現在の内容はそのままで、ファイル末尾に追加書き込みが行われる。

ファイルへの書き込み (fprintf)

ファイルへデータを書き込む方法は何種類もありますが、例題では fprintf 関数を使っています。 fprintf 関数は次のような形で用いられます:

fprintf(出力ストリーム, フォーマット文字列, ...)

fprintf の第1引数は FILE 構造体へのポインタで、出力ストリームを指定します。 この出力ストリームに対して、第2引数のフォーマット文字列に従った出力が行われます。 printf 関数と fprintf 関数の違いは、printf 関数が標準出力に出力を行うのに対して、 fprintf 関数は第1引数で指定された出力ストリームに書き込みをする、 というだけのことです。すなわち、fprintf の第2引数以降の引数は、 printf の引数と同じ働きをします。

ファイルへの1バイト書き込み (fputc)

printf() 関数と同じ働きをする、ファイル出力用の fprintf() 関数が用意されていたように、1 バイトだけ標準出力に書く putchar() 関数にも同様のファイル出力用関数として fputc() 関数が用意されています。

    fputc(c, outputfile);               // c を出力ストリームに出力

第1引数は int 型の数値です。第2引数には出力ストリームを与えます。 fputc は第1引数 c の最下位8ビットを出力ストリームに書き込みます。 それ以外は putchar と同じです。使い方に関する注意点も同様です。

課題

ファイル処理の典型例ともいえるプログラムを試します。

課題 1.

ファイルからの入力 で作成した 課題2. のプログラムを修正して、出力が標準出力ではなくファイルに出力されるようにしてください。

以下に作成にあたってのヒントと注意を列記しておきます。

  1. 入力ファイルは a.txt 、出力ファイルは d.txt などとすると良いでしょう
  2. 入力ファイルと出力ファイルの名前を同じにしないように( w モードでオープンした途端に中味を消去されます)
  3. FILE ポインタは二つ必要になります
  4. fopen, fclose もそれぞれ二つ書かなければなりません
  5. fopen のエラー処理も同じく二つ書くでしょうが、エラーメッセージを違えるなどして、どちらの fopen に失敗したのか分かるようにしましょう

課題 2.

ファイルからの入力 で作成した 課題3. のプログラムを修正して、出力が標準出力ではなくファイルに出力されるようにします。 データは同じものを使って下さい。

(サンプルデータ cost.txt もしくは漢字版(EUCコード) costj.txt

ただし、単価が150万円未満の商品と150万円以上の商品とに分けて、 別々のファイルに出力してください。

以下に作成にあたってのヒントと注意を列記しておきます。

  1. 入力ファイルは a.txt 、出力ファイルは low.txt と high.txt などとすると良いでしょう
  2. FILE ポインタは三つ必要になります
  3. fopen, fclose、それにエラー処理もそれぞれ三つ書かなければなりません

課題 3.

こうしたファイル処理はコンピュータを使って行う処理の典型的なもののひとつです。 事務処理計算は当然のこと、それ以外の技術計算でもファイル入出力を伴う処理を頻繁に行います。

特に一連の課題で使っているデータは実際の全国の売り上げ台数の一日平均と、kakaku.com における最安値価格です。 ここは訓練のしどころだと思って、余裕があれば以下のようなより現実的なプログラムに取り組んでみてはどうでしょう。 気に入ったものを選んで挑戦してください。

(サンプルデータ cost.txt もしくは漢字版(EUCコード) costj.txt

課題 3.1

ファイルからの入力 で作成した 課題4. で組み込んだ売り上げ比較の機能を、課題 2. のプログラムに組み込んで、単価の高い車と安い車それぞれで最も総売り上げ額が高かったものを調べて出力せよ。 (出力の形式は問いません。判るようにやってくれればそれでいいです。)

課題 3.2

サンプルデータは二つありますが、車種名が英字か漢字かの違いだけで、他の内容は同じです。 よく考えると英字と漢字の車種名が下記のように両方並んでいる方が便利そうです。

ヴィッツ   Vitz     T 318  1050000
カローラ   COROLLA  T 315  1302000
エスティマ ESTIMA   T 308  2667000
フィット   Fit      H 233  1123500
.....(以下略)....

二つのデータファイルを読んで、このような形式のファイルを一つ作って下さい。 データを見た時に分かりやすいように桁位置も揃えておきましょう。

課題 3.3

ファイルからの入力 で作成した 課題3. で利用したデータには、第二フィールドにメーカー記号が含まれています。 これを利用して、(課題 3. では単価によって分けていましたが)メーカーごとに仕分けて複数のファイルに出力するように修正してください。

課題 3.4

腕に覚えのある人なら、一つ上の課題 3.3 では、こんなにたくさん似たような記述を並べるより、FILE ポインタを配列で取ってコードを集約した方が良さそうだ、ということがわかると思います。 トライしてみませんか?