さて、この節では継承について勉強します。継承とは受け継ぐことを意味します。では、C++では何を受け継ぐのでしょうか。それは、クラスを受け継ぐのです。下の例を見てください。
List 2.7 図形クラスと円形クラス |
|
この例では、図形クラス(Shape)が円形クラス(Circle)に継承されています。図形クラスは、図の位置を示すxとy、そして図を描画するDraw関数とそれを回転させるRotate関数を持っています。
さて、この中で今まで出てこなかったものがいくつかあります。一つはアクセス制御子の一つ、protected:
です。これは、クラス外からメンバへのアクセスを禁じるprivate:
と自由にアクセスできるpublic:
の中間にあるキーワードで、自分自身のクラスと継承されたクラスからアクセスでき、それ以外からはできません。因みに、privateは継承されたクラスからもアクセスできないのです。
次に、virtual
というキーワードがあるのに気が付いたでしょう。これは、継承後に再定義される関数であることを表しています。つまり、継承されたクラスで再び同じ名前の関数を定義することを意味します。ここでは、図形クラスのDraw関数とRotate関数が継承先の円形クラスでも使われます。
ところで、クラスを継承させるには、class 派生クラス : public 基底クラス
のように記述します。List 2.7の例では、class Circle : public Shape
として、図形クラスから円形クラスに継承しています[注1]。
継承を行うときはすべて、class 派生クラス : public 基底クラス
としましたが、もちろんpublic以外のprivateやprotectedも指定できます。しかし、ほとんどの場合でそれは無意味なものとなってしまうかもしれません。
では、これらにはどのような意味があるのでしょうか。継承のアクセス制御をpublicにした場合は、基底クラスのprivate、protected、publicはそのまま派生クラスに伝わります。しかし、アクセス制御がprotectedの場合、privateとprotectedは基底クラスと派生クラスで同じですが、基底クラスのpublicは派生クラスでprotectedになってしまいます。アクセス制御をprivateにした場合は基底クラスのメンバすべてが派生クラスでprivateになってしまうのです。
そして、円形クラスにも図形クラスが持つDraw関数とRotate関数を持たせ、それぞれ定義を行います。ところで、このRotate関数はいくら回転しても変化のない円には必要ありません。そこで、void Rotate(int) { }
として、何もしない関数になっています。
円形クラスは、円を描くための半径が必要なので、int radius;
として、半径を持たせます。因みに、ここで出てくる数値はすべて整数で扱っています。
ここまでで、継承のやり方は大体おわかり頂けたと思いますので、次は、前節に出てきたノートクラスを基底クラスとして、日記クラスを作ることにしましょう。
その前に、前節のノートクラスに若干の修正を加えます。日記クラスで再定義できるようにvirtualキーワードを関数に付けています。
List 2.8 ノートクラス virtual版 |
#include <iostream> #include <string> using namespace std; |
では次に、ノートクラスを継承した日記クラスを示します。
List 2.9 日記クラス |
#include <iostream> #include <string> using namespace std; |
日記クラスでは、いくつかの関数が加えられ、コンストラクタにも修正が加わっています。コンストラクタの定義で、ページ数を一年の日数と同じ365ページにしています。この際、閏年は考慮していません。また、ページだけではなく、日付でも日記の読み書きができるようにそのための関数を加えています。日付をページに変換できるようにDateToPageという関数も追加しました。
ところで、コンストラクタでnote = new string[pages];
としているのに、どこにもdeleteが見当たらないのに気が付いたでしょうか。このままだとメモリリークが起こるのではないかと思う方もいるでしょう。しかし、その心配は要りません。というのも、ノートクラスのデストラクタがnoteをdeleteしているからです。派生クラスのオブジェクトが作られるときには、まず基底クラスのコンストラクタが呼ばれ、次に派生クラスのコンストラクタが呼ばれます。そして、オブジェクトが破棄されるときには、まず派生クラスのデストラクタが呼ばれて、次に基底クラスのデストラクタが呼ばれるのです。ですから、日記クラスのデストラクタにdelete [] note;
を入れると、noteに対してdeleteが二度呼ばれるのでエラーとなってしまいます。
次に、日記クラスの関数を以下に示します。
List 2.10 日記クラスのメンバ関数の定義 |
|
指定の日付の日記を読み込む関数と書き込む関数を追加しましたが、どちらも日付からページを求めて、それを指定したページの日記を読み込む関数、書き込む関数に渡しているだけです。そして、指定したページからの読み書きの関数では、ノートクラスの関数をそのまま使っています。その際、ReadとWriteの関数にNotebook::
を付けることで、ノートクラスの関数であることを表しています。
指定した日付からページを求める関数では、月の日数のテーブルを用いて、ページ数を割り出しています。これは、そんなに難しくはないでしょう。
以下に、日記クラスを用いた例を示します。日記クラス全体はタイトルにリンクされています。
List 2.11 日記クラスの例 (全ソースはリンク先を参照) |
int main() { |
This page is 3. Today is the holiday. Today is my birthday :D This page is empty. Error of date: 2/31 Cannot read: out of pages. |
これで、継承についての基本的なことはおわかり頂けたと思います。継承はオブジェクト指向には不可欠なものなので、しっかり身に付けるようにしてください。