入出力の書式化とマニピュレータ | Programming Place Plus C++編【標準ライブラリ】 第30章

トップページ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++編を作成中です。

この章の概要

この章の概要です。


マニピュレータ

ストリームに対して、何らかの操作を行うオブジェクトをマニピュレータと呼びます。以下は、標準で用意されている基本的なマニピュレータです。

マニピュレータ クラス 意味
flush basic_ostream バッファをフラッシュする
endl basic_ostream 改行文字(‘\n’)を出力し、バッファをフラッシュする
ends basic_ostream 文字列の終端文字(‘\0’)を出力する
ws basic_istream 書式化されない入力において、空白文字を読み飛ばす

マニピュレータは、<<演算子や >>演算子を使った入出力の際に用いられます。

std::cout << "xyz" << std::flush;

マニピュレータの正体は、引数と戻り値が basic_ostream や basic_istream の参照になっている関数です。たとえば、std::endl は次のように定義されています。

namespace std {
    template <typename T, typename Traits>
    basic_ostream<T, Traits>& endl(basic_ostream<T, Traits>& os)
    {
        os.put('\n');
        os.flush();
        return os;
    }
}

また、<<演算子や >>演算子は、関数ポインタを渡されると、その関数ポインタを経由して関数を呼び出すようにオーバーロードされています

namespace std {
    basic_ostream& operator<<(basic_ostream<T, Traits>& (*func)(basic_ostream<T, Traits>&));
}

そのため、「std::cout << std::endl」のように書くと、関数ポインタを引数に取る operator<< が使用され、間接的に std::endl関数の呼び出しが行われます。std::endl関数は、改行文字の出力と、バッファのフラッシュを行った後、引数で渡されてきたストリームの参照をそのまま return します。

このような仕組みになっているため、ストリームオブジェクトに対する <<演算子や >>演算子は、連鎖的に記述できます。また、std::endl関数のような形の関数を自作すれば、自作のマニピュレータを作ることも可能です。

wsマニピュレータは、たとえば、std::getline関数(第2章)を使った入力のときに活用できます。

#include <iostream>
#include <sstream>

int main()
{
    std::istringstream iss("  xyz");

    std::string s;

    getline(iss, s);
    std::cout << s << std::endl;

    iss.seekg(0, std::ios_base::beg);  // 読み取り位置を先頭に戻す
    getline(iss >> std::ws, s);
    std::cout << s << std::endl;
}

実行結果:

  xyz
xyz

先ほど説明したように、マニピュレータは、ストリームオブジェクトの参照を受け取り、同じものを返しているので、このサンプルプログラムのような使い方も可能です。

std::getline関数は、空白文字などは気にせず、1行分の文字列を読み取りますが、wsマニピュレータの処理を経由させることで、空白文字の取り扱い方が変更されたストリームを渡すことになるため、先頭部分の空白文字が読み飛ばされます。


書式フラグ

std::ios_baseクラスには、さまざまな書式に関する設定情報を管理する書式フラグや、書式フラグを操作するためのメンバ関数が定義されています。また、マニピュレータを使って操作を行うこともできます。

書式フラグには、以下のものがあります。

フラグ 意味
boolalpha bool型の値の表現に文字列形式を使う。
left 出力時、符号や値を左揃えにする。
right 出力時、符号や値を右揃えにする。
internal 出力時、符号を左揃え、値を右揃えにする。
showpos 出力時、正の数値に対して、+ の記号を付ける。
uppercase 出力時、数値表現に含まれるアルファベットを大文字にする(16進数や、科学的記数法(C言語編 第20章)に影響)
oct 数値を 8進数で入出力する。
dec 数値を 10進数で入出力する。
hex 数値を 16進数で入出力する。
showbase 数値の基数に応じた表現を付加する。
(8進数なら先頭に ‘0’ を付加。16進数なら先頭に “0x” または “0X” を付加。uppercase の影響も受ける)
fixed 浮動小数点数に、10進数表記を用いる。
scientific 浮動小数点数に、科学的記数法(C言語編 第20章)を用いる。
showpoint つねに、小数点と、有効桁を埋めるための 0 を書き込む。
skipws 入力時に、先行する空白文字を読み飛ばす。
unitbuf 出力に関する演算を呼び出すたびに、バッファをフラッシュする。

これらの書式フラグは ios_baseクラスで定義されていますが、使用時には記述を短くするため、「ios::boolalpha」のように表記することがあります。「ios」は、ios_baseクラスから派生した basic_iosクラステンプレートに対する typedef名で、以下のように定義されています。

namespace std {
    typedef basic_ios<char>    ios;
    typedef basic_ios<wchar_t> wios;
}

書式フラグを直接的に設定したければ、ios_baseクラスにある各種メンバ関数を用います。fmtflags型は、先ほど挙げた書式フラグの組み合わせを扱うための型です。

fmtflags setf(fmtflags flags);                  // flags を ON にする
fmtflags setf(fmtflags flags, fmtflags mask);   // maskグループの書式フラグに flags を設定
void unsetf(fmtflags flags);                    // flags を OFF にする
fmtflags flags() const;                         // 現在のフラグを返す
fmtflags flags(fmtflags flags);                 // flags で上書きする

2つ目の setfメンバ関数について、mask という引数は、書式フラグのグループを指定します。グループというのは、ある同じ設定項目に対するフラグが複数種類ある場合に、それらをまとめて表現するものです。たとえば、数値の基数を指定する oct、dec、hex は1つのグループを構成しています。実際には、以下のようにグループごとのマスクビットが用意されているので、このいずれかを指定します。

マスク 意味
adjustfield left、right、internal に対するマスク
basefield oct、dec、hex に対するマスク
floatfield fixed、scientific に対するマスク


以下にサンプルプログラムを挙げておきます。

#include <iostream>

int main()
{
    // 数値の符号を出力させる
    std::cout.setf(std::ios::showpos);
    std::cout << 10 << std::endl;

    // 基数を表現するテキストを付加し、アルファベットの部分は大文字で出力
    // 16進数で出力する
    // 符号の出力についての設定はそのまま
    std::cout.setf(std::ios::showbase | std::ios::uppercase);
    std::cout.setf(std::ios::hex, std::ios::basefield);
    std::cout << 10 << std::endl;

    // 現在の書式フラグを取っておく
    const std::ios::fmtflags flags = std::cout.flags();

    // アルファベットの大文字化について解除
    std::cout.unsetf(std::ios::uppercase);
    std::cout << 10 << std::endl;

    // 数値の符号を出力させる
    // 上書きになるので、その他のフラグは OFF に戻る
    std::cout.flags(std::ios::showpos);
    std::cout << 10 << std::endl;

    // 最初に取っておいた書式フラグで上書き
    std::cout.flags(flags);
    std::cout << 10 << std::endl;
}

実行結果:

+10
0XA
0xa
+10
0XA

また、パラメータを伴う必要性から、フラグの形を採っていない設定項目があり、これらについては、以下の専用のメンバ関数を使って制御します。

// 以下は、ios_baseクラスで定義
streamsize width() const;                // フィールドの幅を返す
streamsize width(streamsize val);        // フィールドの幅を設定して、以前の幅を返す

streamsize precision() const;            // 浮動小数点数の精度を返す
streamsize precision(streamsize val);    // 浮動小数点数の精度を設定して、以前の精度を返す

// 以下は、basic_iosクラステンプレートで定義
T fill() const;    // 充填文字を返す
T fill(T c);       // 充填文字を設定して、以前の充填文字を返す

フィールドの幅は、出力時には最小の幅(文字数)を、入力時には最大の幅を意味します。

出力する文字数がフィールドの幅に満たないとき、足りない文字を充填文字で補います。フィールドの幅のデフォルトは 0 で、これは特に何もしないことを表す値でもあります。また、充填文字のデフォルトは空白です。

また、adjustfieldグループの書式フラグは、フィールドの幅に応じて、左寄せや右寄せといった処理を行います。

widthメンバ関数による設定については、次回の出力処理にだけ影響を与えることに注意してください。この挙動は、次のサンプルプログラムで確認できます。

#include <iostream>

int main()
{
    std::cout.width(5);
    std::cout.fill('?');

    std::cout << "abc" << std::endl;
    std::cout << "abc" << std::endl;

    std::cout.width(5);
    std::cout.setf(std::ios::left, std::ios::adjustfield);
    std::cout << "abc" << std::endl;
}

実行結果:

??abc
abc
abc??

入力の場合の挙動も確認しておきましょう。

#include <iostream>

int main()
{
    char s[5];

    std::cin.width(sizeof(s));
    std::cin >> s;

    std::cout << s << std::endl;
}

実行結果:

abcde   <-- 入力した文字列
abcd

文字列の終端文字についても考慮されるため、5文字目は受け取られていないことが分かります。

widthメンバ関数の挙動は分かりづらいので、毎回の出力時に、<<演算子の連鎖の中に、setwマニピュレータを、組み込んだ方が良いかもしれません。その場合、たとえば「std::cout << std::setw(5) << “abc” << std::endl;」のようになります。

マニピュレータによる書式フラグの制御

書式フラグを制御するマニピュレータとして、以下のものがあります。これらは、<ios> という標準ヘッダに定義されています。

マニピュレータ 意味
boolalpha boolalphaフラグを設定する。
noboolalpha boolalphaフラグをクリアする。
left 符号や値を左揃えにする。
right 符号や値を右揃えにする。
internal 符号を左揃え、値を右揃えにする。
showpos showposフラグを設定する。
noshowpos showposフラグをクリアする。
uppercase uppercaseフラグを設定する。
nouppercase uppercaseフラグをクリアする。
oct 数値を 8進数で入出力する。
dec 数値を 10進数で入出力する。
hex 数値を 16進数で入出力する。
showbase showbaseフラグを設定する。
noshowbase showbaseフラグをクリアする。
showpoint showpointフラグを設定する。
noshowpoint showpointフラグをクリアする。
fixed 浮動小数点数に、10進数表記を用いる。
scientific 浮動小数点数に、科学的記数法を用いる。
skipws skipwsフラグを設定する。
noskipws skipwsフラグをクリアする。
unitbuf unitbufフラグを設定する。
nounitbuf unitbufフラグをクリアする。

また、書式に関するパラメータを制御するマニピュレータとして、以下のものがあります。これらは、<iomanip> という標準ヘッダに定義されています。

マニピュレータ | 意味
setiosflags(flags) 書式フラグに flags を設定する。
ストリーム s に対して、s.setf(flag) とすることと同じ。
resetiosflags(mask)

指定グループの書式フラグをクリアする。

ストリーム s に対して、s.setf(0, mask) とすることと同じ。

setw(val)

フィールドの幅を設定する。
以前の値を返さないことを除いて、ストリーム

s に対して、s.width(val) とすることと同じ。

setfill(c)

充填文字を設定する。
以前の値を返さないことを除いて、ストリーム

s に対して、s.fill(c) とすることと同じ。

setbase(base)

基数を設定する。
base が 8、10、16 のいずれかならば、その基数が設定され、それ以外を指定するとデフォル トの設定に戻される。デフォルトは、出力については

10進数、入力については数値の先頭に

0 や 0x といった基数を表す表記があればそれに従い、無ければ

10進数になる。

setprecision(val)

浮動小数点数の精度を設定する。
以前の値を返さないことを除いて、ストリーム

s に対して、s.precision(val) とすることと同じ。


練習問題

問題① フィールドの幅が 10 で、つねに右寄せに出力する myformatマニピュレータを自作してください。

問題② myformatマニピュレータを改造して、以下のようにフィールドの幅を指定できるようにしてください。

std::cout << myformat(10) << "xyz" << std::endl;

問題③ myformatマニピュレータは、フィールドの幅をテンプレート実引数で指定する形にすると、動的な変更はできなくなりますが、より単純に実装できます。以下のように使用できる myformatマニピュレータを作成してください。

std::cout << myformat<10> << "xyz" << std::endl;


解答ページはこちら

参考リンク


更新履歴

’2017/2/10 新規作成。



前の章へ (第29章 文字列ストリーム)

次の章へ (第31章 ストリームイテレータ)

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

Programming Place Plus のトップページへ



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