文字列ストリーム | Programming Place Plus C++編【標準ライブラリ】 第29章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


文字列ストリーム

標準ライブラリのストリームクラスを使って、文字列に対する入出力を行えます。これは、C言語における sprintf関数sscanf関数の代替になる機能です。

文字列ストリームのクラス群は、次のようになっています。

文字列ストリームのクラス構造

読み取り用の basic_istringstream<>、書き込み用の basic_ostringstream<>、読み書き両用の basic_stringstream<> があります。また、それぞれのワイド文字版として、basic_wistringstream<>basic_wostringstream<>basic_wstringstream<> があります。

<> が付いていることから分かるように、これらはすべてクラステンプレートです。

標準入出力ストリームやファイルストリームと同じく、これらのクラスに対する typedef が定義されています。

namespace std {

    template <typename T, typename Traits = char_traits<T> >
    class basic_istringstream : public std::basic_istream<T, Traits> {
    };

    template <typename T, typename Traits = char_traits<T> >
    class basic_ostringstream : public std::basic_ostream<T, Traits> {
    };

    template <typename T, typename Traits = char_traits<T> >
    class basic_stringstream : public std::basic_iostream<T, Traits> {
    };


    typedef basic_istringstream<char>    istringstream;
    typedef basic_istringstream<wchar_t> wistringstream;

    typedef basic_ostringstream<char>    ostringstream;
    typedef basic_ostringstream<wchar_t> wostringstream;

    typedef basic_stringstream<char>     stringstream;
    typedef basic_stringstream<wchar_t>  wstringstream;
}

文字列ストリームの各クラステンプレートや typedef された定義は、<sstream> という名前の標準ヘッダで定義されています。

なお、文字列ストリームのクラス(クラステンプレート)はそれぞれ、標準入出力ストリームのクラスから派生する形で定義されていますから、第27章で紹介した内容はそのまま当てはまります。

文字列ストリームへの書き込み (ostringstream)

文字列ストリームを使って、文字列へ書き込みを行うには、まず ostingstream のオブジェクトを生成し、そこへ <<演算子などの方法でデータを流し込んでいきます。そして、最終的に、strメンバ関数を呼び出すと、完成した文字列を basic_string型で受け取れます。

つまり、文字列ストリームは、ストリーム自身がバッファを持っており、そこへ書き込みを行い、任意のタイミングで、バッファの内容を返すという使い方になっています。メモリ管理の責任が文字列ストリーム側にあるため、バッファオーバーフローを起こすことも、メモリ解放を忘れてしまうこともなく安全であると言えます。

#include <iostream>
#include <sstream>

int main()
{
    std::ostringstream oss;

    oss << "test" << 123 << std::endl;

    std::cout << oss.str() << std::endl;
}

実行結果:

test123

このサンプルプログラムの場合、文字列ストリームへの書き込み時と、その結果を標準出力ストリームへ書き込む時の2か所で、std::endlマニピュレータを使っているため、改行が2度行われてしまいます。改行無しでフラッシュだけを行う std::flushマニピュレータを使えば、余分な改行をなくせます。マニピュレータについては、第30章であらためて解説します。

#include <iostream>
#include <sstream>

int main()
{
    std::ostringstream oss;

    oss << "test" << 123 << std::flush;

    std::cout << oss.str() << std::endl;
}

実行結果:

test123

引数がない strメンバ関数は、その時点でのバッファの内容のコピーを basic_string型で返します。また、引数に basic_string型の const参照を取る strメンバ関数もあり、こちらはバッファの内容を直接的に書き換えます。これは、バッファの内容をクリアしたいときに利用できる方法です。

oss.str("");  // バッファをクリア

ちなみに、clearメンバ関数もありますが、これは第27章で取り上げているように、ストリームの状態フラグをクリアするための関数なので、バッファをクリアするものではありません。

また、すでに文字列がある場合、その文字列を初期値として与えることも可能です。

std::string s("test");
std::ostringstream oss(s);

この場合、文字列ストリームのバッファには、与えた文字列のコピーが保持されます。そのため、上の例で言えば、後で変数 s の内容を変更したとしても、文字列ストリームに与えた文字列には影響しません。

ただし、この後、<<演算子を使うなどして書き込みを行うと、文字列が上書きされてしまいます。これは、書き込み位置がバッファの先頭になっていることが原因なので、ファイルストリームのランダムアクセスのところで説明したように、seekpメンバ関数を使って書き込み位置を変更してやれば解消します。

#include <iostream>
#include <sstream>

int main()
{
    std::string s("test");
    std::ostringstream oss(s);

    oss.seekp(0, std::ios_base::end);  // 書き込み位置を末尾へ移動する

    oss << 123 << std::flush;

    std::cout << oss.str() << std::endl;
}

実行結果:

test123

あるいは、ファイルストリームの場合と同様で、オープンモードを指定することが可能なので、追記用のフラグを与えても良いです。

#include <iostream>
#include <sstream>

int main()
{
    std::string s("test");
    std::ostringstream oss(s, std::ios_base::out | std::ios_base::app);  // std::ios_base::app があると末尾へ書き込む

    oss << 123 << std::flush;

    std::cout << oss.str() << std::endl;
}

実行結果:

test123

文字列ストリームからの読み込み (istringstream)

文字列ストリームからの読み込みは、istringstream のオブジェクトに文字列を渡した後、>>演算子などを使って、変数へ値を受け取っていきます。

#include <iostream>
#include <sstream>

int main()
{
    std::string s("10.55");
    std::istringstream iss(s);

    int i;
    double f;
    iss >> i >> f;

    std::cout << i << " " << f << std::endl;
}

実行結果:

10 0.55

ostringstream の場合と同じく、文字列ストリームに文字列を渡すときには、コピーが行われているので、後から元の文字列を書き換えても、影響はありません

読み込みの場合は、文字列のフォーマットと、読込先の変数の型とが適切に一致していなければエラーが発生することに注意しなければなりません。たとえば、先ほどのサンプルプログラムで、変数 f の型が int型であったら、「10.55」の小数点の部分を読み取れずにエラーになります。

#include <iostream>
#include <sstream>

int main()
{
    std::string s("10.55");
    std::istringstream iss(s);

    int i;
    int f;
    iss >> i >> f;

    if (iss) {
        std::cout << i << " " << f << std::endl;
    }
    else {
        std::cout << "error" << std::endl;
    }
}

実行結果:

error


練習問題

問題① 任意の型の数値を文字列に変換する関数テンプレートを実装してください。


解答ページはこちら

参考リンク


更新履歴

’2017/2/7 新規作成。



前の章へ (第28章 ファイルストリーム)

次の章へ (第30章 入出力の書式化とマニピュレータ)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る