特にオブジェクト指向あたり。動物に当てはめて振る舞いを見てみる。
動物クラスを継承した猫クラス、犬クラス、キリンクラスを作成。
動物は種族がある。
動物は鳴くが、一般的に鳴き声がわからない動物もいる(今回はキリン)。
種族は必ず存在する情報なので、メンバ変数で持たせてもいいんだけど、
今回はオブジェクト指向の振る舞いを見たいので、純粋仮想関数として定義。
鳴き声も同様だが、こちらはデフォルトの鳴き声として"???"を返す仮想関数を定義。
#include <iostream> #include <string> using namespace std; // 動物 class Animal{ private: string m_name; public: Animal(const string &name){ this->m_name = name; } virtual ~Animal(){ } virtual const string kind() = 0; // なんらかの動物を作る場合、必ず何かしら種族を示す文字列を返す関数を定義する inline virtual const string cry(){ return string("???"); } // デフォルトの鳴き声 inline const string & name(){ return m_name; } }; // 猫 class Cat : public Animal{ public: Cat(const string &name) : Animal(name){ } ~Cat(){ } inline const string kind(){ return string("猫"); } inline const string cry(){ return string("にゃー"); } }; // 犬 class Dog : public Animal{ public: Dog(const string &name) : Animal(name){ } ~Dog(){ } inline const string kind(){ return string("犬"); } inline const string cry(){ return string("わん"); } }; // キリン class Giraffe : public Animal{ public: Giraffe(const string &name) : Animal(name){ } ~Giraffe(){ } inline const string kind(){ return string("キリン"); } // 鳴き声を知らない。 }; int main(){ // 動物さん達 Animal *animals[3]={new Dog("ポチ"), new Cat("たま"), new Giraffe("きりんさん")}; // Animal animal("ジョン"); // 動物だけでは作れない for(int i=0; i<3; ++i) cout << animals[i]->kind() << " の '"<< animals[i]->name() << "' は '" << animals[i]->cry() << "' と鳴く" << endl; for(int i=0; i<3; ++i) delete animals[i]; return 0; }
犬 の 'ポチ' は 'わん' と鳴く
猫 の 'たま' は 'にゃー' と鳴く
キリン の 'きりんさん' は '???' と鳴く
まず、各クラスをnewでインスタンスを生成し、Animalのポインタを持つ配列に順番に格納している。
クラスは異なっても基底となっているクラスは全て一緒なので、
今回はAnimalという抽象的なクラスを目印にして異なる種族の動物を動物を管理する一つに配列にまとめているわけです。
Animalのkind()とcry()はそれぞれ仮想関数として定義してあるので、
サブクラス(猫、犬、キリン)でそれぞれ定義していると、サブクラスのkind(),cry()が優先される。
仮想関数でない場合は、Animalのkind(),cry()が使おうとする。
キリンは鳴き声がわからないので、Giraffe::cry()を定義していない。
このときは、Animalで仮想関数として定義されたAnimal::cry()が呼び出される。
ただ動物を作りたいから、とAnimal unknown("何か");として呼び出してインスタンスを作ろうとしても、
純粋仮想関数であるAnimal::kind()が定義されておらず、純粋仮想関数は必ず何かしらの定義しないといけない決まりがある。
そのためコンパイルエラーとなる。
おまけに、メンバ変数で名前を記憶する変数m_nameはなぜ、nameではないのか、というと、
まず、あるクラスAがあったとして、operator () (関数呼び出し演算子)が定義されていた場合、
クラスAのオブジェクトをメンバ変数nameとして持つクラスBを作った場合、
クラスB内で(publicなら外部からでも)name()という呼出が可能となる。
その為、さらにクラスBでメンバ関数name()を定義した場合、
クラスBが持つメンバ関数name()なのか、クラスBが持っているクラスAのメンバ変数nameのoperator()なのか、
区別がつかず、曖昧になってしまう。
こういった事が起きないようにするためにも、メンバ変数名とメンバ関数名が重複するとコンパイルエラーとなる(はず)。
この問題を避けるためや、メンバ変数というのを示すためにも、
"m_変数名"といった形でメンバ変数を定義しているわけです。
最後のdeleteも忘れずに。newで得たらdeleteで後始末!!
スマートポインタでも使って、vectorにpush_backでデータを追加できるようにしてやるような、
メモリ管理を意識しないで済むような実装を出来るようにしてみたい。
例え話が苦手だなぁ。