Index

HOME > プログラムTOP > C++



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フラグ

sasaraan programming

Exposition

●バイナリ・アクセス

 バイナリデータへのアクセスには、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】
fseekseekp(long offset, int origin)seekg(long offset, int origin)
ftelllong tellp()long tellg()
 上記中、offset は移動バイト数、origin には、ios::seek_dir列挙体のいずれかの要素(下記)を指定します。また、位置を示すデータ型は long またはこれに類する整数型となります。
 ・ ios::seek_dir列挙体 ・・・ ios::beg : 先頭位置 / ios::cur : 現在位置 / ios::end : 終端位置

●エラー/EOF検知

 ファイルの終端 (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 メンバ関数でも、フラグの状態(ビットフラグ)を取得することができます。

www.sasaraan.net

(c) morijoh