C++言語解説:2-7.C++の入出力システム
2002-08-08 |
[C++ Index Top] [Prev] [Next] |
[概要] C++ではクラス/型の独自定義とポリモーフィズムの機能をサポートするため、入 出力についても独自機能を実装できるようになっています。独自入出力システム の定義方法を理解します。 [構成]・ストリーム * ストリームとは * 定義済みストリーム * ストリームクラス * 入出力演算子のオーバーロード ・書式制御 * iosクラスの書式定数 * マニピュレータ * マニピュレータのユーザ定義 ・ファイルの入出力 * オープン/クローズ * 書式化テキストファイル入出力 * byte単位のファイル入出力 * ブロック単位のファイル入出力 * その他の入出力関数 * ランダムアクセス * ステータス管理 ●ストリーム ◆ストリームとは ・ストリーム(stream)とは、コンピュータを構成するデバイスへの論理的インターフ ェースです。情報を入出力するための論理インターフェースであり、コンパイラと OSによりコンピュータの物理デバイスへ対応付けられています。 ・C++のストリームはファイル(file)に対する論理インタフェースです。C++で言うと ころのファイルには広い意味があり ディスク上のファイル 画面(ディスプレイ) キーボード シリアル/パラレル等のポート テープデバイス等のアーカイブファイル を指します。 ・大事なことは、C++のストリームは実際の入出力を行う物理デバイスに関係なく、ほ ぼ同じような論理インターフェースを提供するという点です。 ◆定義済みストリーム ・今まで、おまじないとして<iostram>ヘッダをincludeし、cout/cinを経由して画面 やキーボードとデータ入出力を行っていました。これについて改めて考えてみたい と思います。 ・<iostream>ヘッダについては後に説明しますが、この中ですでに定義されているス トリームオブジェクトがあります。それは cin, cout, cerr, clog の4種です。 ・cinは標準入力(standard input)、cout/cerr/clogは標準出力(standard output)に 関係付けされています。 ・clogとcerrも標準出力へのストリームですが、cerrが直接標準出力へデータを送る のに対し、clogはバッファを経由します。バッファの制御手段も提供されています が、それは別の場で扱いたいと思います。 ・尚C++ではワイド文字(16bit)versionの定義済みストリームとして wcin, wcout, wcerr, wclog もあります。 ◆ストリームクラス ・ストリームを扱う際には、<iostream>ヘッダをincludeしますが、このヘッダはC++ でストリームを扱うためのクラス階層を定義しています。 ・例えば、cinはistreamクラスの定義済みオブジェクトであり、coutはostreamクラス の定義済みオブジェクトです。そしてistreamクラスやostreamクラスは、テンプレ ートクラスであるbasic_istream<>やbasic_ostream<>のchar型パラメータクラスと して定義されています。 typedef basic_istream<char> istream; istream cin; typedef basic_ostream<char> ostream; ostream cout; ・先ほど紹介したwcinやwcoutについても同様です。 typedef basic_istream<wchar_t> wistream; wistream wcin; typedef basic_ostream<wchar_t> wostream; wostream cout; ・入出力に関わるテンプレートクラスは複数のクラス階層で構成されており、 <iostream>ヘッダには、その定義が記述されています。Fig1にC++入出力クラスの継 承関係を示します。 [Fig1.入出力クラスの継承関係] ┌───────────────────────────────────┐ ios_base ↑ basic_ios<> --→ basic_streambuf<> ↑ ├───────────────────┐ basic_istream<> basic_ostream<> ↑ ↑ ↑ ↑ │ └───────┬───────┘ │ │ │ │ │ basic_iostream<> │ │ ↑ │ basic_ifstream<> basic_fstream<> basic_ofstream<> └───────────────────────────────────┘ ・Fig1のios_baseクラスはパラメータ<>に依存しない機能をサポートするクラスです。 basic_ios<>クラスから伸びている「--→basic_streambuf<>」は、basic_ios<>クラ スには、basic_streambuf<>クラスオブジェクトを指すポインタがあることを意味し ています。そして、basic_ios<>クラスは、basic_istream<>とbasic_ostream<>の仮 想基底クラスとなっています。 ・Fig1に示したテンプレートクラスがcharでパラメタライズされたときのクラス名を List1に示します。尚、wchar_tに関しては先頭にwを付けるだけなので省略します。 [List1.テンプレートクラスとcharベースクラスの関係] ┌───────────────────────────────────┐ typedef basic_strambuf<char> streambuf; ┐ typedef basic_ios<char> ios; │ typedef basic_istream<char> istream; ├ <iostream>ヘッダ typedef basic_ostream<char> ostream; │ typedef basic_iostream<char> iostream; ┘ typedef basic_ifstream<char> ifstream; ┐ typedef basic_ofstream<char> ofstream; ├ <fstream>ヘッダ typedef basic_fstream<char> fstream; ┘ └───────────────────────────────────┘ ・実際のプログラムでは、List1に示したクラス名を使用します。また各クラスを定義 するヘッダファイルも分割されているのですが、実際にプログラムに使用するヘッ ダは階層構造を定義する都合上、内部でincludeされているので、<iostream>ヘッダ と<fstream>ヘッダを使用することになります。 ◆入出力演算子のオーバーロード ・C++では各クラスにおいて演算子をオーバーロードすることができます。ストリーム とデータをやり取りする >> と << も演算子であり、それぞれ << : 挿入(insert)演算子 >> : 抽出(extraction)演算子 と呼ばれています。 ・そしてこれらの演算子も当然オーバーロードすることができます。それではList2で それぞれの演算子オーバロードを行ってみたいと思います。 [List2.>>と<<の演算子オーバーロード例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; class Sample { int mi_a; int mi_b; public: Sample(int i_a, int i_b){ mi_a = i_a; mi_b = i_b; return; } friend ostream& operator<<(ostream& stream, Sample& obj); friend istream& operator>>(istream& stream, Sample& obj); }; ostream& operator<<(ostream& stream, Sample& obj){ stream << obj.mi_a << " " << obj.mi_b; return stream; } istream& operator>>(istream& stream, Sample& obj){ stream >> obj.mi_a >> obj.mi_b; return stream; } int main() { Sample sample(5,5); cout << "データ出力 :"; //通常の<< cout << sample << endl; //OverLoad版<< + 通常の<< cout << "データ入力(a b) :"; //通常の<< cin >> sample; //OverLoad版>> cout << "データ出力 :"; //通常の<< cout << sample << endl; //OverLoad版<< + 通常の<< return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] データ出力 :5 5 データ入力(a b) :8 10 データ出力 :8 10 └───────────────────────────────────┘ ・実装にはfriend関数を使用しています。それは、演算子関数を呼び出すのが、演算 子の左側にあるストリームオブジェクト自身なので、クラスのメンバ演算子関数と して定義することができないからです。そして、クラスのprivateメンバへアクセス するにはfriendキーワードを付ける必要があります。 ・>>演算子及び<<演算子は「連結」で使用するので、演算子はストリームオブジェク ト自身への参照が戻り値となります。 ●書式制御 ◆iosクラスの書式定数 ・ストリームの書式を定義する方法は2種類あります。一つは基本クラスiosの書式定 数を操作する方法。もう一つはマニピュレータを使用する方法です。始めにiosクラ スの書式定数操作を説明します。 ・iosクラスにはfmtflagsと呼ばれる書式制御のビットマスクがあります。List3に一 覧を示します。 [List3.fmtflags一覧] ┌───────────────────────────────────┐ [単独で指定] skipws ............ 入力ホワイトスペース読み飛ばし showbase .......... 基数の表示 showpoint ......... 小数点末尾0出力 showpos ........... 正数の'+'を出力 uppercase ......... 基数を大文字で表示(E,X等) boolalpha ......... true/falseのシンボル表現を使う unitbuf ........... 出力ストリームのフラッシュ [組で指定] adjustfield ....... フィールド位置揃えフラグ ├ left ......... 左寄せ ├ right ........ 右寄せ └ internal ..... 符号/基数と数値の間に埋め込み basefield ......... 基数に関連したフラグ ├ dec .......... 10進出力 ├ hex .......... 16進出力 └ oct .......... 8進出力 floatfield ........ 浮動小数点に関連したフラグ ├ scientific ... 指数表現 └ fixed ........ 小数点表現 └───────────────────────────────────┘ ・単独で指定できる書式フラグは、setf()メンバ関数で指定することができます。 (例) cout.setf(ios::showbase); ・単独で設定できるフラグはビット演算子|(or)で結ぶことができます。 (例) cout.setf(ios::showbase | ios::uppercase); ・これらのフラグはunsetf()関数で解除することができます。 (例) cout.unsetf(ios::showpos); ・basefield系等、組で指定するフラグは以下のように設定します。 (例) cout.setf(ios::hex, ios::basefield); ・組指定フラグを解除する場合にもunsetf関数を使用しますが、そのときはフラグを 単独で指定します。 (例) cout.insetf(ios::hex); ・ではList4にsetf()/unsetf()関数の使用例を示します。 [List4.setf()/unsetf()関数の使用例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; void showval(); int main() { cout.setf(ios::hex, ios::basefield); cout.setf(ios::showbase | ios::uppercase); showval(); cout.unsetf(ios::uppercase); showval(); cout.unsetf(ios::hex); showval(); return 0; } void showval() { cout << 145 << endl; cout << 20 << endl; return; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] 0X91 0X14 0x91 0x14 145 20 └───────────────────────────────────┘ ・その他の書式設定メンバ関数として以下のものがあります。 streamsize width(streamsize len); ...... フィールド幅設定 char fill(char ch); .................... 埋め込み文字設定 streamsize precision(streamsize num); .. 小数点表示桁 streamsize型は符号付き整数として定義されています。List5に使用例を示します。 [List5.その他の書式設定メンバ関数の使用例] ┌───────────────────────────────────┐ #include <iostream> using namespace std; int main() { cout.setf(ios::fixed,ios::floatfield); cout.precision(8); cout.width(20); cout << 145.12 << endl; //1回目 cout << 145.12 << endl; //2回目 cout.fill('#'); cout.width(20); cout << 145.12 << endl; //1回目 cout << 145.12 << endl; //2回目 return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] 145.12000000 145.12000000 ########145.12000000 145.12000000 └───────────────────────────────────┘ ・width()/fill()関数についてはリセットされていることがわかります。関数の実装 によっては必要となる度に設定しなければならない点は要注意です。 ・従って、これらの用途については、この後説明するマニピュレータを使用する方が スマートだと思われます。 ◆マニピュレータ ・書式指定を入出力式内に記述する方法があります。これにはマニピュレータ (manipulator)と呼ばれる関数を使用します。 (例) cout << hex << 100; ・List6にマニピュレータの一覧を示します。List6の中で引数付きのマニピュレータ を使用する場合は<iomanip>ヘッダが必要となります。 [List6.マニピュレータ一覧] ┌───────────────────────────────────┐ [<iomanip>不要のマニピュレータ] boolalpha ..... true/falseシンボル表現ON noboolalpha ... 〃 OFF showbase ...... 基数表示ON noshowbase .... 〃 OFF showpoint ..... 小数点末尾出力ON noshowpoint ... 〃 OFF showpos ....... +符号表示ON noshowpos ..... 〃 OFF skipws ........ ホワイトスペーススキップON noskipws ...... 〃 OFF unitbuf ....... ストリームフラッシュON nounitbuf ..... 〃 OFF uppercase ..... 基数大文字表示ON nouppercase ... 〃 OFF ws ............ 前のホワイトスペースをスキップ dec ........... 10進表示 hex ........... 16進表示 oct ........... 8進表示 fixed ......... 固定小数点表示 scientific .... 指数表示 left .......... 左寄せ right ......... 右寄せ internal ...... 符号/基数と数値の間に埋め込み endl .......... ストリームをフラッシュして改行 ends .......... ヌルを出力 flush ......... ストリームをフラッシュ [<iomanip>必要のマニピュレータ] setiosflags(fmtflags f) ..... 指定フラグをON resetiosflags(fmtflags f) ... 指定フラグをOFF setbase(int base) ........... 基数をbaseに設定 setfill(int ch) ............. 埋め込み文字をchに設定 setprecision(int p) ......... 小数点表示桁をpに設定 setw(int w) ................. フィールド幅をwに設定 └───────────────────────────────────┘ ◆マニピュレータのユーザ定義 ・マニピュレータ関数はユーザ定義することができます。マニピュレータ関数は以下 のように定義します。 ┌───────────────────────────────────┐ [出力マニピュレータ] ostream& マニピュレータ名(ostream& stream) { : return stream; } [入力マニピュレータ] istream& マニピュレータ名(istream& stream) { : return stream; } └───────────────────────────────────┘ ・例としてフィールド幅を20文字に設定して右寄せし、空白部分を「.」で埋めるマニ ピュレータcustom_setを作ってみます。 [List7.マニピュレータユーザ定義例] ┌───────────────────────────────────┐ #include <iostream> #include <iomanip> using namespace std; ostream& custom_setup(ostream& stream) { stream << setw(20) << setfill('.'); return stream; } int main() { cout << custom_setup << 100; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] .................100 └───────────────────────────────────┘ ・仮引数を取るマニピュレータの作成については、関数ポインタ等の知識が必要なた め別の場で扱いたいと思います。 ●ファイルの入出力 ◆オープン/クローズ ・ファイルは1つのストリームに関連付けて扱います。ストリームは入力/出力/入出力 オブジェクトとして定義します。 ifstream オブジェクト; //入力用 ofstream オブジェクト; //出力用 fstream オブジェクト; //入出力用 上記のクラスを使用するには<fstream>ヘッダが必要です。 ・ストリームオブジェクト定義後は、open()メンバ関数で、ファイルへ関連づけます。 それぞれのクラスのopen()関数は以下のプロトタイプとなっています。 ┌───────────────────────────────────┐ void ifstream::open(const char* fname, openmode mode = ios::in); void ofstream::open(const char* fname, openmode mode = ios::out | ios::trunc); void fstream::open(const char* fname, openmode mode); └───────────────────────────────────┘ ・fnameはパス名を含めたファイル名(ファイルディスクリプタ)です。openmodeはios クラスで定義された列挙定数です。 [List8.openmode一覧] ┌───────────────────────────────────┐ ios::app ...... 出力をファイル末尾に追加(出力専用) ios::ate ...... ファイル末尾シーク ios::binary ... バイナリモード ios::in ....... 入力モード ios::out ...... 出力モード ios::trunc .... 既存ファイル名破棄して上書き └───────────────────────────────────┘ ・ifstream/ofstream/fstreamクラスのコンストラクタはここで説明したopen()関数と 同じ機能を持っています。従って、ストリームオブジェクト定義と同時に対象ファ イルとopenmodeが判明している場合、open()関数を使用する必要はありません。 ・ファイルが存在しない等の理由でオープンに失敗した場合、ストリームオブジェク トの評価結果はfalseとなります。明示的には bool is_open(); メンバ関数を使用します。 ・最後にストリームをクローズするときはclose()関数を使用します。List9にファイ ルストリームのオープン/クローズ例を示します。 [List9.ファイルストリームのオープン/クローズ例] ┌───────────────────────────────────┐ #include <iostream> #include <fstream> using namespace std; int main() { char fname_in[256]; cout << "オープンファイル名は? : "; cin >> fname_in; ifstream in(fname_in); //存在するファイル if (!in) { cout << fname_in << " が見つかりません"; } else { cout << fname_in << " は存在します"; } in.close(); return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] C:>prog オープンファイル名は? : dummy.cpp dummy.cpp が見つかりません C:>prog オープンファイル名は? : prog.cpp prog.cpp は存在します └───────────────────────────────────┘ ◆書式化テキストファイルの入出力 ・テキストファイルとしてストリームからの入出力を行う場合は、オープンしたスト リームオブジェクトに対し、cout又はcinと同様な処理を行います。例をList10に示 します。 [List10.テキストファイルの入出力] ┌───────────────────────────────────┐ #include <iostream> #include <fstream> using namespace std; int main() { char fname[256]; char c_buf1[64]; int i_buf; char c_buf2; cout << "ファイル名は? : "; cin >> fname; ofstream out(fname); if(out){ out << "test_string" << "\n"; out << 100 << "\n"; out << 'A' << "\n"; } out.close(); ifstream in(fname); if(in) { in >> c_buf1; in >> i_buf; in >> c_buf2; } in.close(); cout << "c_buf1 :" << c_buf1 << endl; cout << "i_buf :" << i_buf << endl; cout << "c_buf2 :" << c_buf2 << endl; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] ファイル名は? : test.dat c_buf1 :test_string i_buf :100 c_buf2 :A └───────────────────────────────────┘ ・テキストモードで「<<」又は>>演算子による入出力を行う場合、ホワイトスペース 文字がデータの区切りとして扱われることに注意して下さい。例えばList9で書き込 み文字列を"test string"とした場合、c_buf1に読み込まれるのは、"test"のみとな ります。 ◆byte単位のファイル入出力 ・byte単位でファイル入出力を行うには、get()/put()メンバ関数を使用します。それ ぞれの関数のプロトタイプを示します。 istream& get(char& ch); ostream& put(char& ch); ・get()関数は、ストリームから1byteのデータを読み込み、それをchar参照引数に代 入します。get()関数はストリームへの参照を返し、EOFを検出するとnullになりま す。put()関数はchar参照引数の値をストリームへ出力します。 ・これらの関数を使用する場合、暗黙の文字変換を避けるため、通常はバイナリモー ドでファイル入出力を行います。ではList11に例を示します。 [List11.テキストファイルの入出力] ┌───────────────────────────────────┐ /* ============================================= 1byteのコピープログラム data.txt → data2.txt ============================================= */ #include <iostream> #include <fstream> using namespace std; int main() { char c_buf; ifstream in("data.txt", ios::in | ios::binary); ofstream out("data2.txt", ios::out | ios::binary | ios::trunc); if (!in) return 1; if (!out) return 1; while(in.get(c_buf)){ cout << c_buf; //画面へ出力 out << c_buf; //ファイルへ出力 } in.close(); out.close(); return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] (data1.txt) (data2.txt) ┌───────────────┐ ┌───────────────┐ │R.E.M │ │R.E.M │ │U2 │ │U2 │ │Lenny Kravitz │ │Lenny Kravitz │ │Boston │ │Boston │ │Tom Petty & The Heart Breakers│ │Tom Petty & The Heart Breakers│ └───────────────┘ └───────────────┘ └───────────────────────────────────┘ ◆ブロック単位のファイル入出力 ・バイナリデータを指定byte単位で読み書きするには、read()/write()メンバ関数を 使用します。 istream& read(char* buf, streamsize size); ostream& write(char* buf, streamsize size); ・これらの関数は、char*ポインタbufが指すメモリにsizeバイトのデータをストリー ム読み書きします。読み込みデータがsize以下の場合、read()関数は読み込みを終 了します。ストリームからの読み込みデータサイズは streamsize gcount(); メンバ関数で求めることができます。 ・実際のread()/write()関数の読み書き終了を判定するには、通常eof()関数を使用し ます。 bool eof(); ファイル末尾到達時はtrue、それ以外はfalseとなります。 [List12.Nバイト単位のファイルコピー] ┌───────────────────────────────────┐ /* ======================= Nbyteのコピープログラム ======================= */ #include <iostream> #include <fstream> using namespace std; int main(int argc, char* argv[]) { int i_size = 64; ifstream in; ofstream out; char* buf = new char[i_size]; if (argc < 3) { cout << "引数が足りません"; return 1; } in.open(argv[1], ios::in|ios::binary); if (!in) { cout << "ファイル" << argv[1] << "がありません。"; return 1; } out.open(argv[2], ios::out|ios::binary|ios::trunc); if (!out) { cout << "ファイル" << argv[2] << "が開けません"; in.close(); return 1; } while(!in.eof()){ in.read(buf, sizeof(buf)); out.write(buf, in.gcount()); } in.close(); out.close(); delete[] buf; return 0; } └───────────────────────────────────┘ ┌───────────────────────────────────┐ [出力結果] C:> prog dummy.exe dummy2.exe <--- dummy.exeをdummy2.exeにコピー C:> dummy Hello World C:> dummy2 Hello World └───────────────────────────────────┘ ◆その他の入出力関数 ・get()メンバ関数にはオーバーロードVersionが存在します。 (1): istream& get(char* buf, streamsize size); (2): istream& get(char* buf, streamsize size, char del); (3): int get(); ・(1)の場合size-1の文字か、改行/EOFを検出するまでbufへデータを取り込みます。 データ読みとり後、bufの指すデータの最後にnull文字が付加されます。改行の場合 はbufへ抽出されず、次の読み込みまでストリームに残留します。 ・(2)は、デリミタ文字を指定できます。デリミタは次の読み込みまでストリームに残 留します。sizeやnull文字付加については(1)と同じです。 ・(3)はストリームの次の文字を返し、ファイル末尾ではEOF(マクロ)を返します。 ・そしてget()メンバ関数(1),(2)で改行又はデリミタが「読み飛ばされる」タイプの 動きをするメンバ関数が以下です。 (1): istream& getline(char* buf, streamsize size); (2): istream& getline(char* buf, streamsize size, char del); ・ストリームから文字を取り出さずに読むメンバ関数もあります。 int peek(); ・最後に読んだ文字をストリームへ戻すメンバ関数は以下です。 istream& putback(char ch); ・ストリームバッファをフラッシュするメンバ関数もあります。 ostream& flush(); ◆ランダムアクセス ・ランダムアクセスを行うには、以下の関数を使用します。 istream& seekg(off_type offset, seekdir org); ostream& seekp(off_type offset, seekdir org); seekg()関数はファイルの取得ポインタ(get pointer)、seekp()関数はファイルの出 力ポインタ(put pointer)を、orgからoffsetバイト移動します。 ・seekdirは以下の列挙値となっています。 ios::beg --- ファイル先頭 ios::cur --- 現在位置 ios::end --- ファイル末尾 ・尚、現在の取得/出力ポインタを得るには以下の関数を使用します。 pos_type tellg(); 取得ポインタ位置 pos_type tellp(); 出力ポインタ位置 pos_typeはファイルポインタを受け取る型です。 ・pos_typeを受けて、指定位置へファイルポインタを移動するには、seekg()とseekp() 関数のオーバーロードVersionを使用します。 istream& seekg(pos_type pos); ostream& seekp(pos_type pos); ◆ステータス管理 ・ストリームオブジェクトを介したデータ入出力を行った際、ステータスを取得する ことができます。ステータス(iostate)は以下の列挙値となっています ios::goodbid --- エラー無し ios::eofbit --- ファイル末尾 ios::failbit --- 致命的でない入出力エラー ios::badbit --- 致命的な入出力エラー ・それぞれのステータスのtrue/falseは以下の関数で取得できます。 ios::goodbit --- bool good(); ios::eofbit --- bool eof(); ios::failbit --- bool fail(); ios::badbit --- bool bad(); ・エラー検出時に指定ステータスのフラグをクリアするにはclear()関数を使用します。 void clear(iostate flag = ios::goodbit); 引数省略時には、ios::goodbitがセットされ、結果的に全てのエラーフラグがクリ アされます。 [Revision Table] |Revision |Date |Comments |----------|-----------|----------------------------------------------------- |1.00 |2002-08-08 |初版 [end] |
Copyright(C) 2002 Altmo
|
[C++ Index Top] [Prev] [Next] |