ストラウストラップのプログラミング入門(24) 15章「関数とデータのグラフ化」
『ストラウストラップのプログラミング入門』を読む。今日は第15章「単純な関数のグラフ化」です。
オーバーライドに関する理解は保留中ですが、先に進むことにしました。
15章では、シンプルなグラフを色々描画します。
ここで示すグラフィックス機能よりも、ここで示す設計手法、プログラミング手法、基本的な数学ツールのほうに長期的な価値があることがわかるだろう。
公式から配布されているGraph.hに、Functionという名前のクラスが定義されており、それを使ってグラフを描きます。このクラスのコンストラクタの第一引数に、グラフを描くための関数自体を渡します。
読書めも
コンストラクタのデフォルト引数
15.3.1で登場したコンストラクタのデフォルト引数は、いいなあと思いました。
「1つ以上の引数が省略されたときのためのコンストラクタ」と、「意味や抽象度の異なる値を引数に取るためのコンストラクタ」は、性質が違います。デフォルト引数によって、前者に属するコンストラクタ群を1つにまとめて書ければ、コードの読みやすさが上がると思います。
クラスがpublicなオブジェクトを複数持つ場合
クラスのメンバが、コードの他の部分で独立して扱われるオブジェクトであれば、publicで持った方が操作しやすい場合もある。メンバだからといって、何でもかんでもカプセル化するのがよいとは限らない。…てなことが分かりました。
15.4のグラフの軸を表すAxisクラスは、その例です。
Axisは、軸を表す線分、軸ラベル、目盛りという3つのオブジェクトを持ちます。このうち、軸ラベルはpublicなTextクラス、目盛りはpublicなLine(線)クラスです。それぞれ、Axisクラスの外でも、個別に使われます。
3つのオブジェクトを個別に扱う例として「色の設定」が、セットで扱う例として「移動」の操作が取り上げられています。
クラスを定義する理由
15.6.1の良い言葉。
「コードをより明確にするためだけに」型を追加することをためらってはならない。クラスを定義するのは、概念に関する考え方とコードとをより直接的に対応させるためである。
ドリルめも
グラフのドリル
なんか上に飛び出しているな。。。
あと、ドリル7の「ウィンドウにコンソールを追加する(新しい関数は記述しない)」の意味がわかりません。コンソールって何? コサイン?
クラス定義のドリル
1〜8はできたのですが、最後のドリル9で悩んでいます。
Personの表現を変更し、nameの代わりにfirst_nameとsecond_nameを持つようにする。first_nameとsecond_nameを両方とも指定しない場合はエラーにする。>>と<<も変更する。テストする。
Personクラスのコンストラクタは、string first_name、string second_name、int ageを引数に取ります。そして、first_name、あるいは、second_nameのいずれかが1文字以上とageが与えられたときに、インスタンスの生成を許したいわけです。そうするためのコンストラクタと入力演算子(>>)を上手く定義できません。。。
仮に、「first_name : second_name : age」というフォーマットで受け取ることにしましょう。
このとき、first_name、あるいは、second_nameの省略を許すと、入力値は次のいずれかの形を取ります。
- 「 : second_name : age」
- 「first_name : : age」
いつものイディオムでこれらを処理できず、困りました。 # isはistreamのインスタンス
is >> first_name >> ch1 >> second_name >> ch2 >> age; if(ch1 != ':' || ch2 != ':'){ // フォーマットエラーを処理 }
というわけで、諦めて、first_name、second_name、ageを1つずつ受け取ることにしました。
すると、今度は省略した場合(空白)の入力を受け取れない。ひー
あと、時間が開いてすっかり色々忘れていたことなど。
ユーザ定義の入力演算子で、読込みが終わらない。→ 11章の読書メモで自己解決。
hoge:12フォーマットでnameとageを読み込めない。→ :の前後に空白がないと1つの文字列として認識されてしまう。
メンバ変数とメンバ関数で同じ名前を定義して怒られる。→名前を変えるべし。
これだと(VC++の場合)C2365エラーになる。 http://msdn.microsoft.com/ja-jp/library/86y793k4(v=vs.80).aspx
struct Person{ Person(string n, int a) : name(n), age(a) { }; string name() const { return name; } int age() const { return age; } private: string name; int age; };
たとえば、こうする。
struct Person{ Person(string name, int age) : n(name), a(age) { }; string name() const { return n; } int age() const { return a; } private: string n; int a; };
基底クラスの関数と同じ名前の関数を派生クラスで定義すると、引数の型が違ってもオーバーライドされるのは、コンパイラが関数の名前しか見ないためである(それに対して、Javaのコンパイラなどは、引数の型も含めて関数を識別する)と、某方面から教えてもらったのですが、データと関数という種類の違うメンバー同士で名前の重複が許されないのも、ひょっとして同じ理由なのでしょうか。