入出力ストリームはとっても便利です!


演算子

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 << できるようにするには?

・Boostとは?