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

この章の概要

この章の概要です。

ファイルストリーム

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

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

読み取り用の 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メンバ関数を呼び出します。

fs.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メンバ関数を使い、状態フラグをクリアして下さい。


練習問題

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

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


解答ページはこちら

参考リンク

更新履歴

'2017/2/6 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ