入出力ストリームはとっても便利です!
演算子
ostream& operator << (ostream& , const クラス&);
istream& operator >> (istream& , クラス&);
を定義しておけば、cout, cinだけでなく
ifstream, ofstream, istringstream, ostringstreamなど標準のすべてのストリームで<<, >>が使用できます。
ostream, istreamがこれらすべての基底クラスになっているからです。
C++は、すばらしい!
ここでは、さらにC++のよさを実感してもらうため、イテレータでストリームを扱ってみます。
<やろうとしていること>
マップの内容をファイルに保存して、さらにファイルからマップを元に戻します。
【サンプル】
#include <cstdlib>
#include <cctype>
#include <iostream>
#include <iomanip>
#include <iterator>
#include <algorithm>
#include <map>
#include <fstream>
#include <sstream>
#include "boost/io/ios_state.hpp"
using namespace std;
using namespace boost;
using namespace boost::io;
/**
保存用のデータクラス。
stringのpairの定義を作りたくなかったので、派生しました。ただし、stringにキャストして使わないこと。
*/
class SaveData : public string
{
public:
SaveData()
: string()
{};
SaveData(const string& str)
: string(str)
{};
SaveData& operator =(const char* str)
{
return (*this = SaveData(str));
};
};
//
typedef map<string, SaveData> SaveDataMap;
///文字を数字に変換する
inline const unsigned int toUInt(const char& c)
{
return (unsigned int)*(unsigned char*)&c;
};
///文字列の空白を除去する。
inline const string deleteSpace(string ret)
{
string::iterator i = remove(ret.begin(), ret.end(), ' ');
ret.erase(i, ret.end());
return ret;
};
///16進数文字列を文字列に戻す
const string toString(const string& str)
{
string ret;
char tmp[3];
char* endstr;
istringstream is(str);
//2文字ずつ読んでいく。get()は最後にNULLが入る。
while(is.get(tmp, 3)){
unsigned char c = (unsigned char)strtol(tmp, &endstr, 16);
ret += *(char*)&c;
}
return ret;
}
/**
出力のために演算子定義
pairのキー名には英数字のみ使用可能。空白文字や全角文字などを使用しないこと。
*/
ostream& operator <<(ostream& os, const pair<const string, SaveData>& data)
{
ios_flags_saver ifs(os);//現状のストリームの書式などを保存
os << hex << uppercase << setfill('0');
//キー名の出力
string::const_iterator i;
for(i=data.first.begin(); i!=data.first.end(); ++i){
os << *i;
}
os << "=";
//値の出力
for(i=data.second.begin(); i!=data.second.end(); ++i){
os << setw(2) << toUInt(*i);
}
os << ";";
return os;
};
/**
入力のために演算子定義
*/
istream& operator >>(istream& is, pair<string, SaveData>& data)
{
stringbuf buf;
//キーを取得
is.get(buf, '=');
data.first = deleteSpace(buf.str());
if(is.eof()) return is;
//=を読み飛ばす
is.get();
//値を取得
buf.str("");
is.get(buf, ';');
data.second = toString(deleteSpace(buf.str()));
//;を読み飛ばす
if(!is.eof()) is.get();
return is;
};
typedef map<string, SaveData> SaveDataMap;
/**
データをファイルに保存する。 イテレータで出力ストリームを操っています。
@return ofstream.fail()
*/
bool saveData(const SaveDataMap& smap, const string& file)
{
ofstream ofs(file.c_str());
ostream_iterator<pair<string, SaveData> > itrOut(ofs);
//イテレータでコピーしていく
copy(smap.begin(), smap.end(), itrOut);
ofs.close();
return !ofs;
};
/**
データをファイルから読み込む。イテレータで入力ストリームを操っています
@return ifstream.fail()
*/
bool loadData(SaveDataMap& smap, const string& file)
{
typedef istream_iterator<pair<string, SaveData> > SDIstream;
ifstream ifs(file.c_str());
SDIstream itrIn(ifs), iend;
//イテレータで挿入していく
for(; itrIn !=iend; ++itrIn) smap.insert(*itrIn);
return !ifs;
};
int main(int argc, char *argv[])
{
map<string, SaveData> saveDataMap;
SaveData tmp;
tmp = "##";
saveDataMap["abc"] = SaveData("abc");
saveDataMap["fgh"] = SaveData("あいうえお");
//出力
char* file = "out.txt";
bool bl = saveData(saveDataMap, file);
//入力
map<string, SaveData> inMap;
loadData(inMap, file);
cout << (inMap.find("fgh") == inMap.end() ? "true":"false") << "##" << endl;
cout << inMap["fgh"] << "##" << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
【出力】
false##
あいうえお##
続行するには何かキーを押してください . . .
【説明】
みごとに「あいうえお」が読み込めてますね!
さて、出力ストリームをイテレータとして扱うには、ostream_iteratorを使用します。
saveData()を見てもらえればとおもいます。
演算子<<を定義しておくと、このようにostream_iteratorでどんどん出力することもできるのです。
使用しているのはアルゴリズムのcopyだけです。
簡単ですよね!
入力ストリームも同じです。
istream_iteratorを使用すればイテレータとして使用できます!(^^)<保存のためのキーポイント>
値には改行やもしかしたら、タブや特殊なコードが入っている可能性があります。
そこで、16進数に変化して保存しました。
【補足】
ファイルに保存するだけなら、boost::serialization を使用すると色々な形式でクラスの保存と復元ができます。
ここは、お遊びと思って、遊んでくださいませ~。
ただ、Boostの場合はコンパイルが必要です。
参考:
・出力ストリーム(coutなど)でsetfill()などの有効範囲を関数内だけにするには?
・独自で作成したクラスを cout << できるようにするには?