file
ファイル入出力

11章

リスト 11.1 の変更

リスト 11.1 のファイル名の指定

if ((cfPtr = fopen("clients.dat", "w")) == NULL)

リスト 11.1 の一部

では出力データが VS2005 で指定したプロジェクトのフォルダに 出力される.デフォルトでは C ドライブが指定されており,ファイルを保存することが できないのでファイル名を "Z:\\aprg\\clients.dat" に変更する.

if ((cfPtr = fopen("Z:\\aprg\\clients.dat", "w")) == NULL)

リスト 11.1 の一部変更後

11.4 続き

まず,FILE *cfPtr で FILE構造体へのポインタ変数を宣言する.

cfPtr = fopen("Z:\\aprg\\clients.dat", "w") で clients.dat という名前のファイルを 書き込みモードでオープンし,このファイルをアクセスするためのポインタを cfPtr へ格納する.以後,cfPtr の値を使っうことでファイルにアクセスできる. ファイルオープンに成功しなかった場合は fopen が NULL を返すので 代入式(cfPtr = fopen("Z:\\aprg\\clients.dat", "w"))は fopen に失敗した場合は NULL を値としてもつ.これを利用して if 文により失敗した場合にエラーを 表示している.これはファイルオープンのエラーチェックでよく使用される方法である.

feof(stdin) は,標準入力がファイル終端に達したとき真,そうでないときに偽 を返す.while(!feof(stdin)) は入力があるあいだ処理を繰り返し,ファイル 終端に達するとループから抜ける処理としてよく使用される.

fprintf は第一引数として書き込むファイルの FILE ポインタをとり, そのファイルに対して出力する以外は printf と同じである. プログラマは FILE ポインタ cfPtr を引数に渡すだけで,FILE ポインタの 内容を知る必要はない.

使用が終了したファイルの FILE ポインタを引数に fclose を実行する. これによりファイルが閉じられる.

図 11.3 は FILE ポインタから実際のディスク上のデータまでの流れ であるが,プログラマは詳細を知る必要はない.どのファイルを どの FILE ポインタで開いたかを把握していればよい.

リスト 11.1 (Z ドライブに出力するように変更したもの)を実行すると Z:\aprg ディレクトリに client.dat というファイル が生成されている.このファイルは単純なテキストファイルなので, メモ帳を起動してそのファイルを開くことで確認できる.

11.5

fopen("Z:\\aprg\\clients.dat", "r") で前節で作ったファイルから 内容を読み出し専用でバイナリモードで開くことができる. ここでも if((cfPtr = fopen("Z:\\aprg\\clients.dat", "r")) == NULL) によって fopen が返す値をチェックし,失敗した場合のエラーチェック処理 を用意している.実際のデータの読み込みは fscanf で行う. fscanf は読み込み対象のファイルが指定できる scanf で,使い方は scanf と同じである.

FILE ポインタ cfPtr で開いているファイルをもう一度先頭から読み直すには rewind(cfPtr); という文を使う.rewind 関数はファイル位置ポインタ(FILE ポインタ と混同しないように注意)をファイルの先頭に戻す.

リスト 11.2 を入力,実行せよ.リスト 11.2 でも 11.1 と同様に オープンするファイル名を "clients.dat" から "Z:\\aprg\\clients.dat" に変更せよ.VS2005 のソリューション,プロジェクトの考え方に沿えば らリスト 11.2 はリスト 11.1 と同じソリューションの別のプロジェクトと すべきであるが,この授業ではソリューション,プロジェクトは保存しないので 今まで通り個別のプログラムとして作成してよい.

シーケンシャルアクセスファイルを使う方法は一部のデータを頻繁に書き換えるのには向かない.

11.6

各レコードを固定長にすることでランダムアクセスファイルを実装できる. ランダムアクセスファイルではシーケンシャルファイルのように ファイルの先頭から読むことなしに,必要な場所だけ読み書きできる.

各レコードが固定長だから,任意のレコードに対してレコード番号にレコード長を 乗じることで直ちに先頭からのオフセットを知る事ができる.

11.7

fprintf は変数の内容を人間が読むための文字に変換して 出力するための関数であるが, fwrite は計算機の内部表現をそのままファイルに保存する. fwirte で書き込んだ値は,同じ変数型を指定して fread で 読み出すことができる.

以降では p.417 の以下の問題を処理するプログラムを作成するという前提で説明する.

最高 100 人の顧客をもつ会社用に,100 個の固定長レコードを処理できる取引残高処理 プログラムを作れ.各レコードはレコードキーとして使われる口座番号,名字,名前,取引残高 からなるものとする.このプログラムは既存の取引口座を更新し,新しい取引口座を追加し, 不要な取引口座を削除し,すべての取引口座レコードをフォーマットしてテキストファイルに 出力しなければならない.ランダムアクセスファイルを使うこと.

まず,ランダムアクセスファイルを作り,初期化するリスト 11.5 を入力し,実行せよ. ここでもオープンするファイル名を "credit.dat" から "Z:\\aprg\\credit.dat" に変更せよ.

また,ファイルにアクセスするモードとして,テキストモードとバイナリモード があり,利用に適したモードで保存すべきである. リスト 11.5, 11.6, 11.8 はファイルに計算機の内部表現の形で保存する. これはバイナリモードでファイルを利用していることになるためバイナリモード を利用すべきである.本授業での環境ではこれらのプログラムはバイナリモード でファイルをアクセスしなければ不具合がおこる.テキストモードでの 書き込みモードが "w" であるのに対し,バイナリモードでの書き込みモード は "wb" である.結局,リスト 11.5 の fopen を行う行

if ((cfPtr = fopen("credit.dat", "w")) == NULL)

リスト 11.5 の一部

を以下のように書き換えよ.

if ((cfPtr = fopen("Z:\\aprg\\credit.dat", "wb")) == NULL)

リスト 11.1 の一部変更後

ファイルへのアクセスは,ファイル全体をテープと考え テープ上のヘッドを移動させ,ヘッドがある位置から始まるデータを読み書きする 様子をイメージするとよい. fwrite は ファイルに対し, 現在ヘッドのある位置から指定したバイト数だけ書き込みを行い, ヘッドを次の位置まで進める関数である.リスト 11.5 ではこれを 顧客番号分繰り返すことでファイルを作成,初期化している.

保存されたデータは計算機の内部表現そのままで,文字列にはなっていないため 通常のテキスト表示ソフトでは確認できない.MS-DOS の場合, debug というソフトウェアで任意のコンピュータデータを確認できる. コマンドプロンプトを起動させ,debug コマンドに 引数として ファイル名を与え起動する.プロンプトに対して "d" コマンドを入力すると そのファイルの内容が表示される."q" コマンドで debug は終了する.

11.8

ランダムアクセスファイルに書き込みを行う 11.6 を入力せよ. ここでもオープンするファイル名を "credit.dat" から "Z:\\aprg\\credit.dat" に,モードを "r+" から "r+b" 変更せよ.

ランダムアクセスファイルに対し,顧客番号が決まればファイル中での使用すべき 場所がきまる.顧客番号は 1 から 100 であるが,1 番の顧客のデータは オフセット 0 すなわちファイルの先頭から始まり,2 番の顧客のデータは sizeof(struct clientData) 後の番地から始まる. これをオフセットが sizeof(struct clientData) であるという. 一般に i 番の顧客のデータのオフセットは (i - 1) * sizeof(struct clientData) で求められる.

ランダムアクセスではファイルの途中の特定の場所を読み書きできる. ファイル全体をテープと考えた場合,テープ上のヘッドを目的の場所へ 移動させ,その位置から始まるデータを読み書きする様子を考えればよい. このヘッドを目的どおりに動かすことがランダムアクセスの要であり, これには fseek 関数を使う.

fseek(cfPtr, <オフセット>, SEEK_SET);

で cfPtr の指すファイルのファイル一ポインタを <オフセット> だけ 進めることができる.顧客番号は構造体 clientData 型の変数 client のメンバで,client.acctNum でアクセスできる. この顧客データの先頭までヘッドを進めるには <オフセット> として (client.acctNum - 1) * sizeof(struct clientData) を与えればよい.

リスト 11.6 を実行し,リスト 11.7 のように入力せよ.

11.9

fread は fwrite と対になる関数で,ファイルの現在ヘッドのある位置 から指定したバイト数だけ読み込み,ヘッドを次まで進める関数である.

リスト 11.8 はランダムアクセスファイルをシーケンシャルに読むプログラムである. オープンするファイル名を "credit.dat" から "Z:\\aprg\\credit.dat" に,モードを "r" から "rb" 変更して 入力し,実行せよ.リスト 11.8 では fread をファイルのはじめから終わりまで行い(シーケンシャルな操作), 要素が記入されているレコードだけを出力している.


Updated in December 10, 2007, schedule, Yamamoto Hiroshi