ストラウストラップのプログラミング入門(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の良い言葉。

「コードをより明確にするためだけに」型を追加することをためらってはならない。クラスを定義するのは、概念に関する考え方とコードとをより直接的に対応させるためである。

オペレータ()の再定義

15.6.3のScaleクラスのメンバ関数が面白い。

Class Scale{
// ...略...
	int opertor()(int v) const {return cbase + (v - vbase) * scale; }
};

これって、オペレータ()をオーバーライドしてるんですね。最初わからなくて、誤植かと思いましたw

Javaで()や[]の使い方を変えるには、ランタイムで字句解析している部分を書き換えない限り、できないのでは?

あっさり再定義できてしまうことに、びっくりしました。

ドリルめも

グラフのドリル

なんか上に飛び出しているな。。。

あと、ドリル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コンパイラなどは、引数の型も含めて関数を識別する)と、某方面から教えてもらったのですが、データと関数という種類の違うメンバー同士で名前の重複が許されないのも、ひょっとして同じ理由なのでしょうか。