C/C++ - 言語仕様編 第12回 〜ストリーム〜

入出力ストリーム

ファイル入出力ストリームクラス

fstream ヘッダファイルで定義される、std::fstream クラスは、ファイルへの入出力を行う。 C 言語における FILE 構造体のクラス化バージョンであると考えてよい。

C言語におけるファイルのオープン、クローズという作業は、コンストラクタ・デストラクタによって代行され、 その他の FILE * を引数とする関数も、メソッドとして再定義される。

コンストラクタは、

std::fstream::fstream(const char*, openmode);

として宣言される。 第1引数は、ファイル名を表す文字列リテラルであり、第2引数は、ファイルのオープンモードを決定する。

ファイルのオープンモードは、std::ios 名前空間において、入力を表すin、出力を表すout、追加を表す app、 ファイルの終端にシークする ate、バイナリファイルとして開く binary、ファイルをクリアする trunc が定義される。

第2引数においては、これらのオープンモードフラグの論理和をとることで指示を行う。例えば、 デフォルト引数として指定されるものは、

std::ios::in|std::ios::out

である。

なお、C 言語においては、オープンに失敗すると、NULL となるが、C++ においてこれを検出するには、

bool std::fstream::is_open(void)const;

を用いる。これが false ならばオープンに失敗している。

バイナリ出力

変数のバイナリ出力は、コンストラクタ第2引数として少なくとも、

std::ios::out|std::ios::binary

を指定し、追記するならば

std::ios::app

上書きするならば

std::ios::trunc

フラグを指定する。

C 言語におけるバイナリ出力関数

size_t fwrite(const void *, size_t, size_t, FILE *);

は、

std::fstream& std::fstream::write(const char*, streamsize);

メソッド として再定義される。

第1引数がconst char*型であることから、書き込む変数のポインタ p に対して、 reinterpret_cast<const char*>(p) とし、また、第2引数には書き込む全バイト数を指定する。

バイナリ入力

変数へのバイナリ入力は、コンストラクタ第2引数として少なくとも、

std::ios::in|std::ios::binary

を指定しなくてはならない。

C言語におけるバイナリ入力関数、

size_t fread(const void *, size_t, size_t, FILE *);

は、

std::fstream& std::fstraam::read(char*, streamsize);

メソッドとして再定義される。

書き込む変数のポインタ p に対して、reinterpret_cast<char*>(p)として、 第2引数には読み込む全バイト数を指定する。

ランダムアクセス

ファイルの位置指示子を取得する関数、

long ftell(FILE *);

は、

pos_type std::fstream::tellp(void);

メソッド として再定義される。

ファイル位置指示子の移動を行う関数、

int fseek(FILE*,long,int);

は、

std::fstream& std::fstream::seekp(off_type, seekdir);

メソッド として再定義される。

第2引数には、先頭を表すstd::ios::beg、 現在位置を表す std::ios::cur、終端位置を表す std::ios::endの 3つを指定し、第1引数はその位置からの相対位置を表す。

その他、

bool std::fstream::eof(void)const;

メソッドは、ファイルの終端位置であるかを返す。

文字列ストリーム

文字列ストリームクラス

sstream ヘッダファイルで定義される、std::stringstream クラスは、文字列オブジェクトの整形を行う。 C 言語における関数、

int sprintf(char *,const char *,...);
int sscanf(const char *, const char *,...);

のクラス化バージョンであると考えてよい。

基本的には、デフォルトコンストラクタを用いるか、なんらかの文字列オブジェクトで初期化することで インスタンス化を行う。

ストリームオペレータ

文字列ストリームオブジェクト ss に対して、ある変数 v を文字列として追加するには、

ss<<v;

とすればよい。変数 v の型が char であろうが、 double であろうが、あるいは、std::string であろうが、  ただ、出力ストリームオペレータ << を用いてストリームに流すことで文字列に整形される。

変数v1, v2, v3とある場合は、

ss<<v1<<v2<<v3;

と順番に流せばよい。

ss がなんらかの文字列を持つとき、ある変数 v に対して、

ss>>v;

とすると、変数 v の型であると文字列を解釈して代入する。

文字列リテラル "10+20" と、int型変数 a,c char型変数 bに対して、

ss<<"10+20";
ss>>a>>b>>c;

などとして、10 と '+' と 20 というように分離することも、入力ストリームオペレータ >> によって実現される。

マニピュレータ

例えば、int 型変数 n を16進数として整形するには、

ss<<std::hex<<n;

と流せばよい。ここで、std::hex は16進数として整形するために用いるもので、マニピュレータと呼ばれる。

マニピュレータには、iomanip ヘッダファイルに定義される、引数を指定するものもあり、

ss<<std::setw(10)<<std::internal<<n;

のように流すと、n の符号を左揃えに、数値を右揃えにし、全体の幅を 10 文字分とするように整形する。

代表的なマニピュレータには次のようなものがある。なお、すべて std 名前空間内で定義される。

endl :
  改行しストリームをフラッシュする
showbase/noshowbase :
  n進数の基底を表示する / 表示しない
showpos/noshowpos :
  符号'+'を明示的に表示する / 表示しない
oct :
  8進数に整形する、showbase を流す場合、0 を出力する。
dec :
  10進数に整形する
hex :
  16進数に整形する、showbase を流す場合、0x を出力する。
uppercase / nouppercase :
  16進数に整形するとき、アルファベットを大文字 / 小文字とする。
setw(int n) :
  指定した幅に整形する 
left / right :
  幅を指定しているとき、左寄せ / 右寄せするように整形する
internal :
  符号を左寄せ、数字を右寄せにするように整形する
setfill(int f) :
  幅を指定する場合の空白文字を指定して整形する。
fixed / scientific :
  double型数値を 固定小数点数 / 浮動小数点数 として整形する
setprecision(int p) :
  浮動小数点数表示の表示桁数を指定して整形する	

文字列オブジェクトと文字列ストリーム

文字列ストリーム ss に流すことで整形されたデータは、

std::string std::stringstream::str(void)const;

メソッド を用いることで文字列オブジェクトとして 取得することができる。

また、文字列ストリーム ss に対して、

void std::stringstream::str(const std::string&);

メソッド を用いることで、文字列ストリームに文字列オブジェクトをセットすることができる。 例えば、引数として "" を指定することで、ストリームをリセットすることができる。

クラスとストリーム

入出力ストリーム基底クラス

std::ostream は、出力ストリーム基底クラスであり、出力ストリームオペレータや write メソッドなどが定義される ストリームクラスの基底クラスである。

std::istreamは、入力ストリーム基底クラスであり、入力ストリームオペレータや read メソッドなどが定義される ストリームクラスの基底クラスである。

従って、std::ostream&型オブジェクトは、std::ostreamから派生する、std::fstream型オブジェクトや、 std::stringstream型オブジェクトなどを参照することで、多態性を示す。

特に、std::ostream std::cout は、標準出力を表す出力ストリームオブジェクトとして、 std::istream std::cinは、標準入力を表す入力ストリームオブジェクトとして std 名前空間でグローバルに定義される。

フレンド関数とフレンドオペレータ

クラス C において、

class C{
	P p;
	friend T m(C c);
	...
};

と宣言すると、関数 m は、クラスCの private なプロパティ p にアクセス可能となる。

ここで、m は、関数であり、定義するときも、

T m(C c)
{
	T t;
	...
	return t;
}

のように、クラス C のメソッドとしてではなく、通常の関数として定義する。

m 関数においては、c のプロパティ p を直接呼ぶことが出来る。

同様にして、二項オペレータ # に対しても、

class C{
	friend C& operator #(A& a, C& c);
	...
};  

などと定義することで、a#c を定義することが出来る。

通常の、二項オペレータでは、左辺は必ず自身のクラス C でなければならないが、 このように、フレンドオペレータとして定義すると、左辺を C に依存することなく任意に定義できる。

クラスに依存しないため、関数はクラス C オブジェクトへの this ポインタなどなく、 継承されることもない。

ストリームオペレータの実装

自身で構築した任意のクラスは、通常、ストリームオペレータ << や >> を使用することができない。 他の変数や、std::string などと同様に、これを利用するためにはクラスごとに定義しなくてはならない。

出力ストリームクラス std::ostream と、入力ストリームクラス std::istream は、 std::fstream や、 std::stringstream というストリームクラスの基底クラスであり、 参照で派生クラスのオブジェクトを受け取ることにより、多態性を示す。

出力ストリームオペレータ << は、左辺を出力ストリーム std::ostream& 型オブジェクトが担い、 右辺に自身のクラスオブジェクト、そして、std::ostream& 型オブジェクトを返すように作る。

friend std::ostream& operator <<(
	std::ostream& output, const C& c
);

このオペレータを定義することで、クラス C は、出力ストリームオペレータに対応し、 出力ストリームとしての資格を持つクラス、std::fstream や std::stringstream などのオブジェクト ss へ、

ss<<c;

のように、オブジェクト c を流すことが可能となる。

同様に、入力ストリームオペレータ >> は、左辺を入力ストリーム std::istream& 型オブジェクトが担い、 右辺に自身のクラスオブジェクト、そして、std::istream& 型オブジェクトを返すように作る。

friend std::istream& operator >>(std::istream& input, C& c);

このオペレータを定義することで、クラス C は、入力ストリームオペレータに対応し、 入力ストリームとしての資格を持つクラスのオブジェクト ss から、

ss>>c;

として、オブジェクト c に流すことが可能となる。

マニピュレータの実装

引数のない出力マニピュレータ M0 の実装は、引数、返り値の型が std::ostream& となる関数、

std::ostream& M0(std::ostream& output);

として実装することで、M0 は出力ストリームとしての機能を持つオブジェクト output へ流すことのできる、 引数なしマニピュレータとして実装される。

これは、std::ostream において、引数、返り値の型が std::ostream& となる関数ポインタを 出力オペレータ << が引数としてとることができるように定義されるためである。

一方、引数のある出力マニピュレータ M1(int n) の実装は、引数のない場合のように、 関数ポインタのような実装はできない。従って、マニピュレータ自身を、ファンクタ M1 のオブジェクトとして実装する。

M1には、private なプロパティ int p; と、std::ostream& を引数にとり、std::ostream& を返す private な関数オペレータを定義し、さらに、クラス M1 のオブジェクトを std::ostream& へ流す フレンド出力ストリームオペレータを定義する。

class M1{
	friend std::ostream& operator<<(
		std::osteram& output, const M1& manipulator
	);
	int p;
	std::ostream& operator()(std::ostream& output);
public:
	M1(int n);	
};

コンストラクタは、引数の int n; をプロパティ int p; へ代入し、privateな関数オペレータは、 このプロパティ p を用いて出力ストリーム処理を行うように定義する。

出力ストリームオペレータ << において、引数の manipulatorに対して、 private な関数オペレータを用いて、manipulator(output) として、出力ストリーム処理を 関数オペレータに行わせる。

実際に、出力ストリームオペレータにおいて、マニピュレータ M1 をコンストラクタ引数にint型を指定することで オブジェクトとして構築することで、これが実現される。

以上、全12章をもって、C/C++ における主要な言語仕様の解説を終えるが、 実際には、ここまでの情報のみでプログラミングすることなど不可能である。

アルゴリズム、そしてその実装系であるライブラリを実装し、利用することによりプログラミングを 進めていかなくてはならない。C++は、構造的プログラミング・オブジェクト指向プログラミング・ ジェネリックプログラミング・メタプログラミングなど、さまざまなプログラミングスタイルをとることができ、 それに応じた技法やそれに特化するライブラリが存在する。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])