直交座標の表すクラス euclid
と極座標のクラス polar
があるとする.
このとき,両方のクラスで
原点からの距離や偏角を求めたり,
原点を中心に回転したり,などの共通した操作を行いたいとする.
このとき,それぞれを,
class euclid { double x, y; public: double absolute() { return sqrt(x * x + y * y); } /* 絶対値 */ double angle() { double atan2(y, x); } /* 偏角 */ void rotate(double th); /* 回転 */ }; class polar { double r, theta; public: double absolute() { return r; } /* 絶対値 */ double angle() { return theta; } /* 偏角 */ void rotate(double th) { theta += th; } /* 回転 */ };のように,別のクラスで定義しても良い.
このとき,例えば,
int main() { euclid xy; polar rth; double a; ... xy.rotate(PI/6.0); a = rth.angle(); ... }のようにして使用する.
しかし,直交座標と極座標という表現形式が異なるが, これらのクラスのオブジェクトに対する操作は 同様なものであることが考えられるため, (実際に同じ名前のメンバ関数を定義している) オブジェクトのクラスを気にせずに操作が行えるとよい.
この目的を実現するために,まず, 仮想関数 について説明する. 継承関係のあるクラス間において, 基本クラスで宣言されるメンバ関数で,派生クラスで再定義される関数を 仮想関数という. また,派生クラスで仮想関数を再定義することを オーバライドするという.
仮想関数を作成するには,基本クラスで,
関数の宣言の前に virtual
と書く.
直交座標と極座標のクラスを継承するように定義し, 仮想関数を用いると,
class euclid { double p, q; public: double getp() {return p;} double getq() {return q;} void setq(double v) { p = v; } /* 仮想関数の定義 */ virtual double absolute() { return sqrt(p * p + q * q); } virtual double angle() { double atan2(q, p); } virtual void rotate(double th); }; class polar: public euclid { public: /* 仮想関数の再定義 */ double absolute() { return getp(); } /* 絶対値 */ double angle() { return getq(); } /* 偏角 */ void rotate(double th) { setq(getq() + th); } /* 回転 */ };となる.
このとき,これらのクラスのオブジェクトを使用する操作は, 上のメインプログラムと同じである.
仮想関数に加えて,継承関係にあるクラスのオブジェクト へのポインタを使用する.
(復習)
クラス euclid
のオブジェクト xy
に対して
メンバ関数 absolute
を呼び出して絶対値を求めるとき,
int main() { euclid xy; double abs; ... abs = xy.absolute(); ... }とするのと同様のことを, クラス
euclid
のオブジェクトへのポインタを使って,
int main() { euclid xy; euclid *p; /* クラス C のオブジェクトへのポインタを格納する変数 */ double abs; p = &xy; /* p にオブジェクト xy へのポインタ (xy の番地) を格納 */ abs = p->absolute(); /* 絶対値を求める */ ... }と書ける.
さて,クラスが継承関係にあるとき, 基本クラスのオブジェクトへのポインタを格納する変数は, 派生クラスのオブジェクトへのポインタを格納することもできる.
よって,上のように仮想関数を用いてメンバ関数が宣言してあり,
基本クラスである euclid
のオブジェクトへの
ポインタ変数があれば,基本クラスのオブジェクトでも,
派生クラスのオブジェクトでも関係なく,
同じ名前のメンバ関数が使用できることになる.
以下にまとめてプログラムを示す.
class euclid { /* 基本クラス */ double p, q; public: double getp() {return p;} double getq() {return q;} void setq(double v) { p = v; } /* 仮想関数の定義 */ virtual double absolute() /* 絶対値 */ { return sqrt(p * p + q * q); } virtual double angle() /* 偏角 */ { double atan2(q, p); } virtual void rotate(double th); /* 回転 */ /* これらのメンバ関数は,派生クラスで再定義するため, virtual を指定している */ }; class polar: public euclid { /* 派生クラス */ public: double absolute() { return p; } /* 絶対値 */ double angle() { return q; } /* 偏角 */ void rotate(double th) { setq(getq() + th); } /* 回転 */ }; int main() { euclid xy; polar rth; euclid *p; /* euclid クラスのオブジェクトへのポインタ */ double sum; ... p = &xy; /* euclid クラスのオブジェクト xy へのポインタ */ sum = p->absolute(); /* xy の絶対値を求める */ p = &rth; /* euclid クラスの派生クラスである polar クラス のオブジェクト rth へのポインタを, euclid クラスのオブジェクトへのポインタ変数に格納 */ sum += p->absolute(); /* rth の絶対値を加える */ ... }上の例のように,仮想関数を定義していれば, ポインタ変数が基本クラスのオブジェクトを指していても, 派生クラスのオブジェクトを指していても, 同じ記述
p->absolute()
で表現できる.
どのクラスのメンバ関数を実行するかは, 実行時に, その時点でポインタ変数が指しているオブジェクトのクラスによって 決まる.
このように記述できることが, C++ が多態の性質を実現している代表的なものである.
問題 3-4
上の直交座標と極座標のクラスの定義に,
定数倍を行うメンバ関数 void magnify(double factor)
を
追加せよ.
magnify
は,
直交座標の場合には,x, y それぞれの値を factor
倍し,
極座標の場合は,r を factor
倍することとする.
必要であればコンストラクタを適切に定義し,
実行してみよ.