PPPUC++ #8.666666....
6.6 Trying the first version
- さあ、準備はできたので main()をこさえて、get_token()をつけて、実行してみよう
- 調理済みの未完成品はこちらにあります。 http://www.stroustrup.com/Programming/calculator00.cpp
- 当然ですが、動作はおかしい。
- まず数字単体をいれても動かん。どうなってんぞ。
- さらに入れる。動かん。。。。もう一文字入れる。やっと帰ってくる。
- 単純な足し算いれてみる。最初の値が帰ってくる。
- わーい。 xをいれたら Bad Tokenって出たぞー、仕様通りだー。と、これくらいしか喜ぶ場所が無いです。
1
2
3
= 1
4
5
6
= 4
9+1
= 9
x
Bad token
1+3_
= 4
2+2*3_
= 8
(2+2)*3_
')' expected
6.7 Trying the second version
- という修正を行った版(まだ無い)を試してみるとまだバグがある。
- 最後の結果が表示されない、というヤツ。
- 本書では ';' で終端。そうだなあ、そっちの方が自然だよねしまった。
- これで問題なく動くようになりました。
- putbackしまくり。個人的には苦手なコード
- とりあえずの初版としてはこんなもんでおk。ここから直していく
- まず肝心な、Token Streamが未実装だったり
6.8 Token streams
- 入力なしでは何も出来ない。これ先にやんなきゃ。なんだけど完成イメージを優先したそうな。
- 仕様としては以下
- classメンバのpublic: private: の話。この区別が必要なのって、結局のところconvension、「ここまでは間違いない」という切り分けをするため、と思っておいていいのだよね。
- 本文中では「コードを構造化する為の強力なツール」ということになっている
- Token_streamで必要になる public I/Fは コンストラクタ、get(), putback()の3つ。
- putback()などと、仰々しい名前にしたのは put()だと対称と勘違いされる危険がある(つまり get(), put()が対等と思われる)というのと、iostreamでそうなっているから。名前の一貫性重要。
6.8.1 Implementing Token_stream
- どうかく?
- ついでにクラス定義外部でのメンバ関数の書き方 (class_name) :: (member_name) の説明
- なぜクラス定義外で書くのか? = 主に明確さのため。クラス定義はクラスが「何を出来るか」を記述する場で「どうやっているか」は分けるべきことが多い。
- またクラス定義が長くなるとわかりにくいから(理想は一画面にすべて収まること)。
- もう一つ、効率、があるとおもうんだけど、この時点では触れてないようです。
- putback()の定義は preconditionとして fullフラグがfalse (=バッファが空)であることをチェック。先客がいたらエラー
6.8.2 Reading tokens
- 最後に残ったget()が実際の作業をやります
- fullフラグがtrue(先客あり)ならば、それを返してフラグをfalseに
- fullフラグがfalse(先客がいない)ならば、cinから文字列読み込んでトークンを構築します
- やり方は単純で、一文字読んで cin >> ch 、その値が '+'とかの演算子ならそのままそれでTokenを作成して返します
- 数字だった場合、一文字数字をcin.putback()で戻してから cin >> dval; をやり直して Tokenを作成して返します
- 素晴らしいことに指数表現でも最初は単に数字か'.'なので、あとはcinが良きにはからってくれます。0x, 0, 0bとかの接頭字はさすがにムリですが feature creep!
- 想定外の場合は error("Bad token");
6.8.3 Reading numbers
- 先走ってしまった。上の数字だった場合、の内容です。
- 楽しまくってることは意味があるのです
- 「良いプログラマは怠惰である :-P」 *すばらしい*
6.9 Program structure
- 格言に「木を見て森を見ず」とあるけれど、たしかにfunction, class, などなど具象に目を囚われていると、プログラム全体を見失うことがあります。
- というわけで単純化してみせるよ! という話
- vim使いにとっては foldmethod=syntax状態ですね :D
#include "..." class Token { /* ... */ }; class Token_stream {/* ... */}; Token_stream::Token_stream() .... { } Toekn_stream::putback(Token t) { /* ... */ } Token_stream::get() { /* ... */ } Token_stream ts; double expression(); double expression(); // forward declare double primary() { /* ... */ } double term() { /* ... */ } double expression() { /* ... */ } int main() { /* ... */ }
- 順番重要. C++では宣言される前の名前は使えない
- あれれ? オブジェクトの依存グラフを書いてみると
-
- 循環あるよ>< なので何らかの方法で先に定義をしないと順番重要、の要件を満たせない。ので、ここでは expression()を前方参照(定義)にしています。
- さて、これで完成?
- いやまだだ。
- 経緯:
- 6.6 ファーストバージョン :全然ダメ。計算どころかそのまますらでない。バグputback忘れを修正
- 6.7 セカンドバージョン : ちょっとマシ。でもやっぱり何かがおかしい。バグ終端忘れを修正して、なんとかOK
- まだまだ。とりあえず基本の考え方を確かめるには至ったけれども、やることいっぱい。
- というわけで次の章に続く。。。