ファイルストリーム | Programming Place Plus C++編【標準ライブラリ】 第28章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要 🔗

この章の概要です。


ファイルストリーム 🔗

標準ライブラリのストリームクラスを使って、ファイルに対する入出力を行えます。これら、ファイルストリームのクラス群は、次のようになっています。

ファイルストリームのクラス構造

読み取り用の basic_ifstream<>、書き込み用の basic_ofstream<>、読み書き両用の basic_fstream<> があります。また、それぞれのワイド文字版として、basic_wifstream<>basic_wofstream<>basic_wfstream<> があります。

<> が付いていることから分かるように、これらはすべてクラステンプレートです。

この図から分かるとおり、ファイルストリームのクラステンプレートは、基底クラスから機能を引き継いでいます。これらの基底クラスは、前章で解説した標準入出力ストリームのクラス群のことです。繰り返しになるだけなので、この章では基底クラス側が持っている機能の説明はしていません。必要に応じて前章をご覧ください。

標準入出力ストリームと同じく、これらのクラスに対する typedef が定義されています。

namespace std {

    template <typename T, typename Traits = char_traits<T> >
    class basic_ifstream : public std::basic_istream<T, Traits> {
    };

    template <typename T, typename Traits = char_traits<T> >
    class basic_ofstream : public std::basic_ostream<T, Traits> {
    };

    template <typename T, typename Traits = char_traits<T> >
    class basic_fstream : public std::basic_iostream<T, Traits> {
    };


    typedef basic_ifstream<char>    ifstream;
    typedef basic_ifstream<wchar_t> wifstream;

    typedef basic_ofstream<char>    ofstream;
    typedef basic_ofstream<wchar_t> wofstream;

    typedef basic_fstream<char>     fstream;
    typedef basic_fstream<wchar_t>  wfstream;
}

ファイルストリームの各クラステンプレートや typedef された定義は、<fstream> という名前の標準ヘッダで定義されています。

オープン 🔗

ファイルの入出力を行うには、まずファイルをオープンする必要があります。そのためには、ファイルストリームクラスのオブジェクトを生成し、そのコンストラクタにファイル名を与えます。

std::ifstream ifs("test.dat");

前章で紹介したように、ストリームは「状態」を持ちます。もし、ファイルのオープンに失敗した場合、failbit や badbit が ON になっているので、これを調べることで判断できます。

std::ifstream ifs("test.dat");
if (!ifs) {
    // オープンに失敗している
}

あるいは、openメンバ関数を使ってオープンできます。

std::ifstream ifs;
ifs.open("test.dat");
if (!ifs) {
    // オープンに失敗している
}

openメンバ関数は戻り値が void型なので、エラーのチェックは、やはり「状態」を調べます。

なお、openメンバ関数を呼び出しても、それまでに ON になっていた状態のビットフラグはクリアされません。そのストリームで初めて行うオープンであれば正常な状態になっているはずですが、クローズとオープンを繰り返す場合には注意してください。

ファイルが正常にオープンできたら、入出力を行えます。前章の標準入出力ストリームと同じで、<< や >> の演算子を使ったり、いくつかの関数を使ったりできます。たとえば、次のサンプルプログラムは、ファイルの内容を標準出力へ出力します。

#include <iostream>
#include <fstream>
#include <cstdlib>

int main()
{
    std::ifstream ifs("test.dat");
    if (!ifs) {
        std::cerr << "error" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    char c;
    while (ifs.get(c)) {
        std::cout.put(c);
    }
}

test.dat:

xxx

yyyyy

zzzzzzz

実行結果:

xxx

yyyyy

zzzzzzz

標準入力と違い、ファイルからの入力の場合は、ファイルの終わりの検出が必要になります。ファイルの終わりに達した後、さらにもう1度読み取りを行おうとすると、eofbit と failbit が ON になります

そのため、上記のサンプルプログラムのように「while (ifs.get(c))」とすると、failbit が ON になることによってループを抜けることができます。もちろん、この方法だと、ファイルの終わりと、他の要因によるエラーの発生とを区別できないので、必要であれば、eofメンバ関数を使ってください。

char c;
while (ifs.get(c)) {
    std::cout.put(c);
}
if (ifs.eof()) {
    std::cout << "eof" << std::endl;
}
else {
    std::cout << "error" << std::endl;
}

クローズ 🔗

ファイルを使い終わったら、クローズを行うことが基本ですが、ファイルストリームクラスの場合は、デストラクタが自動的に行ってくれます。

もし、明示的なクローズが必要であれば、closeメンバ関数を呼び出します。

ifs.close();

オープンモード 🔗

ファイルをオープンする際、コンストラクタや openメンバ関数の第2引数に、動作を指定するためのフラグを渡せます。このフラグは、以下の組み合わせで、すべて std::ios_baseクラスで定義されています。

フラグ 意味
in 読み取りのためにオープン。
out 書き込みのためにオープン。
app 書き込みを、つねにファイルの末尾へ行う。
ate オープン後、ファイルの末尾へ移動する(名前は “at end” の略)
trunc ファイルの元の内容を削除する。
binary バイナリモードでオープン(改行文字の変換を行わない)

ifstream は「in」が、ofstream は「out」が、fstream は「in | out」がデフォルトのフラグとして指定されます。これら以外の組み合わせのフラグが必要な場合には、明示的に指定します。

たとえば、ifstream に「out」を指定することは可能ですが、出力系の機能を持っていないため意味を成しません。

参考までに、C言語の fopen関数のオープンモードの指定(C言語編第39章)との対応関係は以下のようになります。

fopen C++ のファイルストリーム
r in
w out | trunc
a out | app
rb in | binary
wb out | trunc | binary
ab out | app | binary
r+ in | out
w+ in | out | trunc
a+ in | out | app
r+b in | out | binary
w+b in | out | trunc | binary
a+b in | out | app | binary

バイナリモードの使用方法については、【言語解説】第6章で説明しているので、そちらを参照してください。

ランダムアクセス 🔗

ランダムアクセスを行うために、読み書きの位置を移動および取得するメンバ関数が用意されています。

読み取り位置の取得には basic_istream<>::tellgメンバ関数を、移動には basic_istream<>::seekgメンバ関数を使用します。また、書き込み位置の取得には basic_ostream<>::tellpメンバ関数を、移動には basic_ostream<>::seekpメンバ関数を使用します。関数名の末尾に付く「g」は「get」、「p」は「put」のことです。

いずれも、ファイルストリームのクラスではなく、基底クラス側で定義されています。そのため、標準入出力ストリームでも、これらのメンバ関数を呼び出せますが、効果はありません。これは、ストリームの種類を区別せずに、基底クラスの型を使ってプログラムを書くための配慮だと思われます。

basic_fstream の場合、tellg、seekg、tellp、seekp の各メンバ関数がすべて使えますが、実際には、読み取り位置と書き込み位置はつねに連動して動きます。

tellgメンバ関数、tellpメンバ関数は、以下のように宣言されています。

pos_type tellg();

pos_type tellp();

pos_type型の正体は環境依存であり、単なる整数型ではないことがあります。また、返される値は「ファイルの先頭からのバイト数」のような意味の値ではありません。返された値は取っておいて、seekgメンバ関数や seekpメンバ関数でその位置に戻ってくるという用途以外には使えません。

なお、ストリームの failbit や badbit が ON になっているときには、これらのメンバ関数は -1 を pos_type型にキャストした値を返します

seekgメンバ関数、seekpメンバ関数については、以下のように2つのバージョンが宣言されています。

basic_istream& seekg(pos_type pos);
basic_istream& seekg(off_type off, std::ios_base::seekdir dir);

basic_ostream& seekp(pos_type pos);
basic_ostream& seekp(off_type off, std::ios_base::seekdir dir);

引数が1つだけの方は、絶対的な位置を指定します。これは、tellgメンバ関数および tellpメンバ関数が返した値のことで、それ以外の手段で作り出した値が正しい意味を持つ保証はありません。

引数が2つある方は、相対的な位置を指定します。第1引数に、基準値に対する相対的な距離を、第2引数に基準値を指定します。これはちょうど、C言語の fseek関数と同じ仕組みです。

off_type型は、符号付き整数型です。また、std::ios_base::seekdir型は、以下の3つの定数値を表現する型です。

意味
std::ios_base::beg ファイルの先頭を基準とする
std::ios_base::cur 現在の位置を基準とする
std::ios_base::end ファイルの末尾を基準とする

基準位置に相対値を加えた結果、ファイル内の有効な位置からはみだしてしまった場合は未定義の動作なので注意してください。

また、failbit や badbit が ON になっていると、seekgメンバ関数や seekpメンバ関数は、位置の変更を行いません。これは特に、ファイルの末尾まで到達した後、読み取り位置を手前に移動させようとしたときに問題になります。このようなときは先に clearメンバ関数を使い、状態フラグをクリアしてください。


練習問題 🔗

問題① ファイルの内容を、別のファイルへコピーするプログラムを作成してください。

問題② fstream において、読み取り位置や書き込み位置を操作して、それぞれの現在位置がどのように変化するか確認してください。


解答ページはこちら

参考リンク 🔗


更新履歴 🔗

 「クローズ」の項のサンプルコード内の変数名を修正。

 新規作成。



前の章へ (第27章 標準入出力ストリーム)

次の章へ (第29章 文字列ストリーム)

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る