PPPUC++#13

11. Customizing Input and Output

"Keep it simple : as simple as possible, but no simpler." -- Albert Einstein
iostreamの調整についてです。泥臭いところもたっくさん。最後はセパレータについての例で。

11.1 Regularity and irregularity

  • ここまでは入力文字列、というのを全て同質に扱ってきました
    • でも実際には、入力ではソースの違い(通信ポートとか)だとか、出力では桁数を変えたりとか
    • てなことをいじります
  • プログラマとしては規則性を重視したいけれど、現実は個人の趣味とか主観的なものとか、現実の物体が関わってくるためそうはいかない
  • プログラムの複雑度とユーザの好みへの適応のバランスをとらなければなのです

11.2 Output formatting

  • 個人の嗜好、専門分野、アプリケーションの都合、デバイスの都合....
      • かえるもいろいろあるけれど(違
  • 出力ってのはいろんな事情でいろんな方法で表示する必要があります
    • プログラマはこれらの要求を可能なかぎり満たす必要があります。ただし、どんなに小奇麗でも曖昧とか微妙とか間違った表示は論外
      • しかしうまくマッチさせないと結構癇に障るもんだったり
      • ダメな人間(僕)は、こう言った詳細にイツまでもハマってしまい時間をムダにするのです><
  • 大抵入力に対するオプションに比べ、出力に対するオプションは広大
    • 小数点、桁数、科学技術表示、符号、多国語対応 etc.etc....
  • 細かな事は[自著の宣伝] にて。ここでやるのはほんの一部です
11.2.1 Integer output
  • 整数出力は8進、10進、16進
  • ほとんどは10進
    • ハードウェア関連のものは 16進が多い
    • 8進はCが開発された当時はビットパターンとしてよく使われていた
  • ios::hex , ios::oct を coutに食わせると、それぞれ表示が変換される、というやつ。これはios::setwと違って持続する(sticky)
      • ios::base()みたいな任意基数のは無いのかな
11.2.2 Integer input
  • 入力も同様です。hex を食わせれば 16進として扱うし、というやつ。当然ながらsticky
  • 前置(0xとか 0 とか)によって適切に変換させる事も可能です
    • そうするためにはhex, oct, decなどが立ってたらダメなのでこれを消します
      • 本文で出てくる方法は unsetf()にて一つずつ消してますが、次節にて出てくる手法 setf(0, ios::basefield)を使えば一発でできたり
      • また、意味合いをわかりやすくするために、同じく次節と同様に マニピュレータを定義した方が良いと思う
inline ios_base& autobase(ios_base& is) { is.setf(ios::fmtflags(0), ios::basefield); return is;}
main()
{
    int a,b,c;
    cin >> autobase >> a >> b >> c;
    cout << ">>" << a << " " << b << " " << c << endl;
}
// 0x30 030 30
// >> 48 24 30
  • これ、ios_baseではなく istreamに限定して、 ostreamに適用した場合にエラーにしたいのですが、function -> boolという暗黙の変換(?)で上手く行かないようです。何か上手い手ないかな。。。http://d.hatena.ne.jp/tkuro/20110306/1299396311
11.2.3 Floating-point output
  • マニピュレータ scientific(指数表現), fixed(固定小数点表現), そして無指定(scientific or fixedを桁数に応じて自動的に選ぶ)
    • 実は無指定に戻すマニピュレータは無い(なんと!確かにw)
      • 上記のように setf(0, ios::floatfield)みたいな事すればおk
    • 本文では マニピュレータ generalを作ってる。
11.2.4 Precision
  • 覚えるべきは四捨五入ルールとsetprecision()の効果のみ
general
全体の桁数 (5) 123.456 -> 123.46
scientific
仮数部の小数点以下の桁数 (5) 123.4567 -> 1.23457e+02
fixed
小数点以下の桁数 (5) 1.234567 -> 1.23457
11.2.5 Fields
  • integerでも setw()で桁を指定できるよーとのこと。Fieldと呼ぶそうな。しらんかった
  • 桁あふれした場合は桁指定無効
    • 間違った出力よりは間違った見栄えの方がマシとな。まあそりゃそか
  • もちろんこの効果を受けるのはintだけではありません
  • あとさっきも触れたけど setw()は non-sticky ! なんという一貫性の無さ

11.3 File opening and positioning

  • ファイル自体は単なるバイトストリームなんだけど、それをどうアクセスすんのか、というプロトコルの部分
  • これを決定しているのがファイルオープン時に指定するモードですよという話
    • Read/Writeもその一つ
11.3.1 File open modes
  • ios_base::in, ios_base::out がその read/write
  • 他にも ios_base::app (append) , ios_base::ate (at end), ios_base::binary, ios_base::trunc (truncate)
    • 変に略すんじゃねい!
      • 略してるのと略してないのの一貫性0な気が。。。ate とか何だよ。食っちゃうんかと思うじゃんw という一方で binary って binで良いじゃんとか・・・
  • *stream()コンストラクタの第二引数にいれるのです
  • systemcallの open()と違ってデフォルトで createするので便利です
11.3.2 Binary files
  • iostreamはデフォルトでは文字集合としてファイルを見なしている
  • ios_base::binary指定でその呪縛から解放を!
    • 実は、と言う訳でもないだったりする
    • 実際POSIX系ならoperator>> or << を使わず、istream::read(), ostream::write()を使えば普通にバイトストリームアクセス可能
    • 結局このopenmode指定というのは、例えば libstdc++の場合
    switch (mode & (in|out|trunc|app|binary))
      {
      case (   out                 ): return "w";
      case (   out      |app       ): return "a";
      case (             app       ): return "a";
     :
      case (   out          |binary): return "wb";
      case (   out      |app|binary): return "ab";
      case (             app|binary): return "ab";
      case (   out|trunc    |binary): return "wb";
      case (in              |binary): return "rb";
     :
    • のようにfopenフラグ文字列に変換しているだけだったり。binaryの場合は'b'を足しているだけ。よって

The mode string can also include the letter 'b' either as a last character or as a character between the characters
in any of the two-character strings described above. This is strictly for compatibility with C89 and has no effect;
the 'b' is ignored on all POSIX conforming systems, including Linux.
(モード文字列は'b'を末尾または他のモード文字間に含むことができます。これは厳密にはC89への互換性のためで何の効果もありません。
 'b'はLinuxを含むすべてのPOSIX互換システムで無視されます)
-- from manual fopen(3)

    • の記述通り、単に無視されます。Windowsシステムだとおそらく CRLFをなんぞしてくれると思うんですが、そうですよね>識者の方
      • しかし、可搬性と「これバイナリ操作!」という表明の意味で一応書いておいたほうが良いとは思います
  • 次、read, writeの第一引数は char* なワケでこまったな、という話。
      • 痛切に思う。なんで(void*)じゃないの?
    • ここでは細かいことは追求せずに std_lib_facilities.h の as_byte() というラッパ関数でごまかすようです
    • これ自体は暗黙変換(!)で(void*)化したものを static_cast(!)するという一口で二度汚いってやつ
      • 正直この辺りは(runtimeで解決できるdynamic_castを除いて)僕が設計者たちを bk なんじゃないかと思っていた(る)ところ*1
      • キャスト悪、という発想に囚われすぎ。イミフ&薄ら汚く下劣な構文、そのくせ逃げがちゃんとあるので反って意味がなくなっている、というしょぼい会社のセキュリティ施策みたいになってます。というか普通に (char*) にキャストしたほうが(この場合は)明確だしキレイと思います(そもそも char*なのがバカだと思いますが)
      • 低レベルなものは低レベルに書けるべき。田舎モノが多少身なりを正したところで、細かいところで育ちは隠せません
  • まあバイナリは最後の手段なので、可能な限り >>, << を使いましょうというような・・・
    • で、標準では解決できないような場合はバイナリ使いましょうという
      • >>, << 自体を実装する際とかかな。それすら全部 get() で読めばすむ気も
11.3.3 Positioning in files
  • seekg()と seekp()
    • ここも僕がブチキレなところです。 なんで read(), write() だったのに seekになると get, put で、streamは in, outなのよ。一貫性どこよ
      • 名前重要。このあたりの一貫性の皆無さがコミッティー症候群ってやつなんでしょうか
    • あとここ記述も微妙。"so the figure represents the state of the program 'after' execution." として put position : 2 , get position : 6としてますが・・・
    • 実際には件のコードのラストに以下を追加すると両方とも 2になります。
    cout << "tellg = " << fs.tellg() << endl
         << "tellp = " << fs.tellp() << endl;
      • これは、少なくともg++というかlibstdc++では
istream.tcc-    seekg(pos_type __pos)
istream.tcc-    {
                       :
istream.tcc:          const pos_type __p = this->rdbuf()->pubseekpos(__pos,
istream.tcc-                                                         ios_base::in);

--
ostream.tcc-    basic_ostream<_CharT, _Traits>::
ostream.tcc-    seekp(pos_type __pos)
ostream.tcc-    {
                       :
ostream.tcc:          const pos_type __p = this->rdbuf()->pubseekpos(__pos,
ostream.tcc-                                                         ios_base::out);
      • と一見分かれているかに見えるんだけど、この pubseekpos() → seekpos()が曲者。
    seekpos(pos_type __pos, ios_base::openmode) {
      • ・・・modeすててる
      • まあ普通システムも複数のファイルポインタは持ちませんよね・・・
  • あと明示的なエラーチェック無いから気をつけましょう

11.4 String streams

  • stringを istream, ostreamにできる便利クラス -- istringstream, ostringstream
  • バッファとかに便利(本文では文字から型変換とGUI部品のタイトルとかを例としてあげてます)
  • str_to_double()みたいなの
      • そっか。まだtemplateやってないんだっけ? (シレっと何度か出てきてる気もするが・・・)
    • これはこれで面倒な人には boost::lexical_cast<>というのがあります。内部でやってることは同じだけれど http://d.hatena.ne.jp/tkuro/20091217/1261018470
  • ostringstream の場合は << にて文字を構成していき、最後に .str()とするとそれまでの結果が取得できます
    • いやー簡単ですね

11.5 Line-oriented input

  • getline()使いましょう
  • 終了
    • いやいや
      • 正直これも・・・(最近文句が多いなw)
      • インターフェイスも、getline(cin, string) って・・・わざわざ>>スタイルをブレイクせんでも マニピュレータでseparator文字列を変えれたらOKだったんじゃね? と思います
    • 全般的にこの辺り中途半端なんですよね。実用プログラムで使わないから本気じゃない、といわれれば、はあそうですか、なのですが
  • なんぞ、「こんな機能使うな」オーラが満ち溢れてます. 折角パースしてやってるのに文句あんのか!? って?
    • チャットだけでなく、シェルですらgetline必要ですよね・・・・

11.6 Character classification

  • 一文字ずつ取りたい場合 cin.get()
    • >>(char)との違いは white spaceを読むか否か
      • くどいようですがこれもマニピュレータでseparatorを(ry
  • クラス分けには is*()シリーズをつかってね、という話
  • case-insensitiveにするためのtoupper(), tolower(). なるべくなら tolower()を使いましょう
    • ちょっとトリビア: これはドイツ語などnum_lower > num_upper な言語でupperにしてしまうと情報欠損するので、とのこと

11.7 Using nonstandard separators

  • おぉ、Mr.Sもistreamのセパレータには文句があるっぽい "Unfortunately, istream doesn't offer a facility for us to define what characters make up white space or in some other way directly change how >> reads a string"
  • どうすんべ?
  • ここでの例は句読点(, ; : ! ?とか)を含む場合にこれ消したいよねーという話
    • getline()で全部読み込んで、一度全体ナメて句読点を ' 'スペースに変換して stringstreamで読み直すというなんという・・・
  • もっと汎用化しましょう、という流れ

:goal:

  ps.whitespace(";:,.");  // punctuation stream の略らしい
  string word;
  while (ps>>word) vs.push_back(word);
    • そうか、別に大掛かりに継承しなくても委譲だけで作れるのか、
      • というわけで感動して自分でもそっこで作ってみた。
class pstream {
    istream&      is;
    string        punc;
    istringstream buffer;

    istream& read_a_line() {
        string line;
        if (cin) {
            getline(cin,line);
            for (int i=0; i< line.length(); i++)
                if (punc.find(line[i])!= -1)
                    line[i] = ' ';
            buffer.str(line);
        }
        return cin;
    }

public:
    pstream(istream& is, string punc="."): is(is), punc(punc) {
        read_a_line();
    }

    pstream& operator>>(string& s) {
        buffer >> s;
        if (!buffer) {
            buffer.clear();
            read_a_line();
            buffer >> s;
        }
        return *this;
    }

    operator bool() {
        return bool(is);
    }
};
    • UGOITA!
    • 一気に1行読むんじゃなくget()で読んでstring+=もありですね
      • buffer.bad()を考慮し忘れた><
  • 本物のistreamじゃないので、定義してないI/Fは使えない。継承でやるのはもう少し訓練積んでから、とのこと
  • 今は難しそうに見えるこのコードも、もっと訓練したらもっと良いコードが書けるようになるだろうね、と
    • プログラマになるには、洗練されてないコードだってタラフク読む必要があるのです
      • 痛み入っていいものなのか、言い訳と見ていいものなのかw

11.8 And there is so much more

  • I/Oというのは永遠の課題
      • inventiveness = 独創性、capliciousness = 気まぐれさ とかいう単語を覚えたw
  • 自然言語とか、
    • ロケールとかどうよとか、、、うーん、ちょっとこれ欲しくなった。ぽちろうかな

Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference

Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference

  • あとバッファの複雑性とか、、、うーんやっぱこれもイルのかなー・・・(なんかMr.Sのワナにはまってる気もする)

C++ Programming Language, The: Special Edition

C++ Programming Language, The: Special Edition

  • C の printf(), scanf()にもぶちあたるかも。K&Rを勧められた TT
  • さあ、次章からは GUIです

*1:いやきっと僕の理解できていない沢山の理由があるんだとは思うんですが