|
C++の壺・ファイル篇3
C++の基本テクニックのTipsです。ファイル篇1と2では、文字データをシーケンシャル・アクセス(主に、文字データを順番通りに処理する手法)で処理していきました。ファイル篇3では、文字以外の様々なデータを扱うことのできるファイル入出力の手法とエラー処理について記述しています。
| ページ | 項 目 | 内 容 |
| ファイル篇1
| ・Cのファイル処理 | : FILE構造体、fopen関数、fclose関数 |
| ・入出力関数 | : fgets関数、fputs関数、fprintf関数、fscanf関数 |
| ・更新モード | : rewind関数 |
| ・バイナリモード | : テキストモードとバイナリモード |
| ファイル篇2
| ・C++のファイル処理 | : ifstreamクラス、ofstreamクラス、openメンバ、closeメンバ |
| ・入出力処理 | : putメンバ、getメンバ、is_openメンバ、getlineメンバ |
| ・モードフラグ | : オープンモードフラグ |
| ファイル篇3
| ・バイナリ・アクセス | : fwriteとfread関数 / writeとreadメンバ |
| ・ランダム・アクセス | : fseekとftell / fgetposとfsetpos / seekp,seekg,tellp,tellg |
| ・エラー/EOF検知 | : feofとferror関数 / perrorとclearerr関数 / iostateフラグ |

バイナリデータへのアクセスには、fwrite関数と fread関数の利用が適しています。いずれも、データのサイズと項目数を指定して読み書きするものです。これらの関数では、書式なしのデータをそのまま読み書きすることができます。なお、バイナリデータを扱う時は、ファイルのアクセスモードに、 "b" を付加する必要があります。
| fwrite function |
| ・書式 | size_t fwrite(const void *buf, size_t size, size_t n, FILE *fp) |
| ・機能 | ファイルにデータを書き出す |
| ・引数 | buf...データへのポインタ, size...項目サイズ, n...最大項目数, fp...FILE構造体へのポインタ |
| ・戻り値 | 書き出した項目数 |
| ・要件 | stdio.h のインクルード |
| fread function |
| ・書式 | size_t fread(void *buf, size_t size, size_t n, FILE *fp) |
| ・機能 | ファイルからデータを読み込む |
| ・引数 | buf...データの格納場所, size...項目サイズ, n...最大項目数, fp...FILE構造体へのポインタ |
| ・戻り値 | 読み込んだ項目数 |
| ・要件 | stdio.h のインクルード |
size_t は sizeof演算子の結果を表すデータ型です。通常は unsigned int かこれに準ずる整数型に定義されています。下記に、構造体をファイルに保存するサンプルを掲載しておきます。
// バイナリアクセス(fwriteとfread)
#include <stdio.h> // printf,fwrite,fread
#include <conio.h> // getch
// xdata構造体
struct xdata {
int index;
char name[64];
};
// main 関数
void main()
{
xdata x1 = {9999, "Sherlock Holmes"};
xdata x2;
FILE* fp;
char szFile[] = "xdata.bin";
// ファイルへの書き出し
if ((fp = fopen(szFile, "wb")) == NULL)
return;
fwrite(&x1, sizeof(xdata), 1, fp);
fclose(fp);
// ファイルからの読み込み
if ((fp = fopen(szFile, "rb")) == NULL)
return;
fread(&x2, sizeof(xdata), 1, fp);
fclose(fp);
printf("%d, %s\n", x2.index, x2.name);
if (getch()) return;
}
|
// バイナリアクセス(writeとread)
#include <stdio.h> // printf
#include <conio.h> // getch
#include <fstream> // ostream,istream
using namespace std;
// xdata構造体
struct xdata {
int index;
char name[64];
};
// main 関数
void main() {
xdata x1 = {9999, "Sherlock Holmes"};
xdata x2;
char szFile[] = "xdata.bin";
// ファイルへの書き出し
ofstream os(szFile, ios::binary);
if (!os.is_open()) return;
os.write((char*)&x1, sizeof(xdata));
os.close();
// ファイルからの読み込み
ifstream is(szFile, ios::binary);
if (!is.is_open()) return;
is.read((char*)&x2, sizeof(xdata));
is.close();
printf("%d, %s\n", x2.index, x2.name);
if (getch()) return;
}
|
C++の入出力オブジェクトを使用する場合は、writeメンバ関数と readメンバ関数を利用することもできます。バイナリデータを扱う時は、フラグに ios::binary を指定する必要があります。ちなみに、上記の二つのサンプルは、いずれも "9999, Sherlock Holmes" を出力します。
| write member |
| ・書式 | : stream& write(const char *str, int n) |
| ・機能 | : 出力ストリームにデータを書き出す |
| ・引数 | : str...文字列へのポインタ, n...文字数 |
| ・戻り値 | : ストリームへの参照 |
| read member |
| ・書式 | : stream& read(char *str, int n) |
| ・機能 | : 入力ストリームからデータを読み込む |
| ・引数 | : str...格納場所, n...文字数 |
| ・戻り値 | : ストリームへの参照 |
ランダム・アクセスでは、ファイル内の任意の位置にポインタを移動させて、各データにアクセスすることができます。これにより、ファイルの全データを読み書きしなくても、データを取得・変更・追加が可能となります。 通常は、管理しやすいことから、固定長(バイト数が固定)のバイナリデータを扱います。静的位置へのファイルポインタの移動には fseek関数を利用します。これは、基準位置(起点)から指定バイト数移動した位置にポインタを配置する機能を持ちます。さらに、ファイルポインタの位置を記憶できる ftell関数を使うと、動的に位置を取得することも可能です。
| fseek function |
| ・書式 | : | int fseek(FILE *fp, long offset, int origin) |
| ・機能 | : | 基準位置から指定バイトの位置にファイルポインタを移動させる |
| ・引数 | : | fp...FILE構造体へのポインタ, offset...移動バイト数, origin...基準位置(下記参照)
SEEK_SET : 先頭位置 / SEEK_CUR : 現在位置 / SEEK_END : 終端位置 |
| ・戻り値 | : | 成功時...0, 失敗時...0以外 |
| ・要件 | : | stdio.h のインクルード |
ftell関数は、その時々のファイル位置を取得します。この関数で取得した値は、fseek関数の offsetの値に利用します。この時、ファイルの先頭が位置の基準となりますので、SEEK_SET を指定するようにします。
| ftell function |
| ・書式 | : | long ftell(FILE *fp) |
| ・機能 | : | 現在のファイルポインタの位置(先頭からの移動バイト数)を取得する |
| ・引数 | : | fp...FILE構造体へのポインタ |
| ・戻り値 | : | 成功時...現在のファイル位置, 失敗時...-1L |
| ・要件 | : | stdio.h のインクルード |
以下のサンプルでは固定長レコードを扱っています。なお、ファイルの入出力には fprintf, fscanf を使用してデータを整形しています。これらの仕様については、お手数でも 「 ファイル篇1」 をご覧ください。
// ランダムアクセス (fseek と ftell)
#include <stdio.h> // printf, fopen, fclose, fprintf, fscanf, fseek, ftell
#include <conio.h> // getch
// main 関数
void main()
{
const int nSize = 68; // レコード長
const char szFmt[] = "%4d%64s"; // レコードフォーマット
char szFile[] = "xdata.bin"; // ファイル名
int nIndex = 0; // データの格納場所(数値部)
char szName[64] =""; // データの格納場所(文字列部)
FILE *fp; // ファイルポインタ
long nPos; // ファイルポインタの位置
// @ ファイルに書き出し
if ((fp=fopen(szFile,"wb"))==NULL) return;
fprintf(fp, szFmt, 111, "上杉謙信"); // 1レコード目
fprintf(fp, szFmt, 222, "武田信玄"); // 2レコード目
fprintf(fp, szFmt, 333, "北条氏康"); // 3レコード目
fclose(fp);
/* 更新前のデータ : {111上杉謙信, 222武田信玄, 333北条氏康} */
// A ファイルの更新
if ((fp=fopen(szFile,"r+b"))==NULL) return;
fseek(fp, nSize * 2, SEEK_SET); // 3レコード目に移動
nPos = ftell(fp); // 修正するレコードの位置の記録
fprintf(fp, szFmt, 444, "今川義元"); // 3レコード目を修正
fseek(fp, 0, SEEK_END); // 終端に移動
fprintf(fp, szFmt, 555, "毛利元就"); // 4レコード目を追加
fclose(fp);
/* 更新後のデータ : {111上杉謙信, 222武田信玄, 444今川義元, 555毛利元就} */
// B ファイルの読み込み
if ((fp=fopen(szFile,"rb"))==NULL) return;
fseek(fp, nPos, SEEK_SET); // 修正したレコードの位置へ移動
fscanf(fp, szFmt, &nIndex, szName); // 修正したレコードの読み込み
printf("%d : %s\n", nIndex, szName); // 画面出力 "444 : 今川義元"
fclose(fp);
if (getch()) return;
}
ファイルポインタの記憶と復帰には、fgetpos と fsetpos の両関数を利用することもできます。特に、fseek を使わないでファイルポインタの位置を制御する時は、こちらをセットで使用します。この時使われるデータ型は fpos_t となります。これはファイル位置を記録するための特殊なデータ型で、通常は long 、もしくはこれに準ずる整数型に定義されています。
| fgetpos function |
| ・書式 | : int fgetpos(FILE *fp, fpos_t *pos) |
| ・機能 | : ファイル位置を取得する |
| ・引数 | : fp...FILE構造体へのポインタ, pos...ファイルポインタの位置 |
| ・戻り値 | : 成功時...0, 失敗時...0以外 |
| ・要件 | : stdio.h のインクルード |
| fsetpos function |
| ・書式 | : int fsetpos(FILE *fp, const fpos_t *pos) |
| ・機能 | : ファイルポインタを移動させる |
| ・引数 | : fp...FILE構造体へのポインタ, pos...ファイルポインタの位置 |
| ・戻り値 | : 成功時...0, 失敗時...0以外 |
| ・要件 | : stdio.h のインクルード |
なお、C++の入出力クラスを利用する時は、それぞれ以下のメンバが同様の機能を持ちます。
| 【C】 | 【ofstream】 | 【ifstream】 |
| fseek | seekp(long offset, int origin) | seekg(long offset, int origin) |
| ftell | long tellp() | long tellg() |
上記中、offset は移動バイト数、origin には、ios::seek_dir列挙体のいずれかの要素(下記)を指定します。また、位置を示すデータ型は long またはこれに類する整数型となります。
・ ios::seek_dir列挙体 ・・・ ios::beg : 先頭位置 / ios::cur : 現在位置 / ios::end : 終端位置
ファイルの終端 (EOF) に達しているかどうかは、feof関数で調べることができます。この関数は、EOF を超えた状態で、なおかつ、次に読み込みに行った時に EOF を検知します。現在の位置が EOF を超えているだけでは検知しませんので注意が必要です。(下記サンプルコード参照) また、ファイル入出力中のエラーは、ferror関数で調べることができます。
| feof function |
| ・書式 | : | int feof(FILE *fp) |
| ・機能 | : | ファイルの終端の読み込みを検知 |
| ・引数 | : | fp...FILE構造体へのポインタ |
| ・戻り値 | : | 終端を検知...0以外, 終端を未検知...0 |
| ・要件 | : | stdio.h のインクルード |
| ferror function |
| ・書式 | : | int ferror(FILE *fp) |
| ・機能 | : | ファイルエラーの検知 |
| ・引数 | : | fp...FILE構造体へのポインタ |
| ・戻り値 | : | エラーあり...0以外, エラーなし...0 |
| ・要件 | : | stdio.h のインクルード |
// EOFの検知テスト
#include <stdio.h> // printf, fopen, fclose, fputs, fgets, feof
#include <conio.h> // getch
// main 関数
void main() {
const char szFile[] = "xdata.txt"; // ファイル名
FILE *fp;
char src[] = "0123456789\n";
char buf[32];
// ファイルに書き出し
if ((fp = fopen(szFile, "w")) == NULL) return;
fputs(src, fp);
fclose(fp);
// ファイルから読み込み
if ((fp = fopen(szFile, "r")) == NULL) return;
fgets(buf, 32, fp); // データを読み込み → EOFに到達
feof(fp)? printf("EOF\n"): printf("NOT EOF\n"); // EOF未検知 : "NOT EOF"
fgets(buf, 32, fp); // EOF を超えて再読み込み
feof(fp)? printf("EOF\n"): printf("NOT EOF\n"); // EOF検知 : "EOF"
fclose(fp);
if (getch()) return;
}
perror 関数を使うと、システムが持つエラーメッセージ(改行つき)を表示させることができます。このメッセージの内容は環境により異なります。この時、引数に文字列を指定すると、独自のメッセージをいっしょに表示させることができます。
| peeror(NULL); | → | "No such file or directory" |
| perror("ファイルが開けません"); | → | "ファイルが開けません: No such file or directory" |
また、EOFを検知すると、rewind、fsetpos、fseek、clearerr のいずれかの関数が呼び出されるまで EOF を検知し続けます。一方、エラーを検知すると、ファイルが閉じられるか、rewind 、clearerr のいずれかが呼び出されるまでエラー情報を保持し続けます。 clearerr関数は、EOF情報とエラー情報の両方をクリアする機能を持ちます。
| perror function |
| ・書式 | : | void perror(const char *str) |
| ・機能 | : | エラー文字列の表示 |
| ・引数 | : | str...表示するメッセージ |
| ・戻り値 | : | なし |
| ・要件 | : | stdio.h または stdlib.h のインクルード |
| clearerr function |
| ・書式 | : | void clearerr(FILE *fp) |
| ・機能 | : | エラー情報とEOF情報のクリア |
| ・引数 | : | fp...FILE構造体へのポインタ |
| ・戻り値 | : | なし |
| ・要件 | : | stdio.h のインクルード |
// エラー検知テスト
#include <stdio.h> // printf, ferror, perror. clearerr
#include <conio.h> // getch
// main 関数
void main() {
const char szFile[] = "xdata.txt"; // ファイル名
FILE *fp;
// ファイルを開いてエラーを発生させる
if ((fp = fopen(szFile, "r")) == NULL) {
perror("エラー"); // 表示 - "エラー: No such file or directory"
} else {
fputc('a', fp); // "r"モードのファイルに書き出し → エラー発生
if (ferror(fp)) printf("エラー(書き出し不可)\n"); // エラー検知
if (ferror(fp)) printf("エラー(エラーがあります)\n"); // 再チェック → エラーのまま
clearerr(fp); // エラーをクリア
if (!ferror(fp)) printf("エラーはありません\n"); // 再チェック → エラーなし
fclose(fp);
}
if (getch()) return;
}
C++の入出力クラス (ofstream / ifstream) を使う場合は、以下のメンバを使用することができます。それぞれ、関数内部では iostate フラグを使って判断しています。
| 書 式 | 説 明 |
| bool eof() | EOF を検知する (ios::eofbit) と true, 未検知の時は false |
| bool fail() | データ抽出エラーの時 (ios::failbit) に true, 正常時は false |
| bool bad() | 致命的なエラーの時 (ios::badbit) に true, 正常時は false |
| void clear([int flag]) | 指定された エラー/EOF フラグのクリア(省略時はすべてクリア) |
| 【iostateフラグ】 |
| ios::eofbit | ファイルの終端 (EOF) に達した |
| ios::failbit | 有効なデータの抽出に失敗した(継続処理は可能) |
| ios::badbit | バッファの完全性が喪失した(継続処理は困難) |
| ios::goodbit | 正常処理した(エラーなし / EOF未検知) |
なお、rdstate メンバ関数でも、フラグの状態(ビットフラグ)を取得することができます。
|