C++講座最終回は
ポリモーフィズム(多様性)についての説明です。
オブジェクト自体を入れ替えて
第7回のオーバライド関数(仮想関数)をいかに呼び出すかという話です。
といってもいまいち何を言っているのかピンと来ないと思います
オブジェクトというのは構造体やクラスの変数のことで
たとえばオブジェクトの代入について
構造体(struct)の変数やクラス(class)の変数は
次のように同じ型ならデータの代入ができます
Obj obj1,obj2; obj1 = obj2;
これはobj2のデータ(メモリ)をそのままobj1にコピーするということです
(このとき、obj2がポインタなどを内部変数に持っていたら
obj1の内部変数のポインタにも同じアドレスが入ってしまうことには注意)
さて、C++では基本クラスのオブジェクトに
継承クラスのオブジェクトを代入するということができます
どうゆうことかというと
#include <iostream> using namespace std; class Parent { public: void Show() { cout << "親クラスShow" << endl; } }; class Child: public Parent { public: // オーバーライド void Show() { cout << "子クラスShow、オーバーライド" << endl; } }; // main関数(クラスの外部) int main() { Parent pt; Child ch; pt = ch; pt.Show(); return 0; }
ここで注目してほしいのは
pt = ch;という代入部分です
C++では基本クラスに継承クラスのオブジェクトを代入することができます
しかし、これで呼び出されるには基本クラスのShow関数です
では、継承クラスのShow関数を呼び出すにはどうしたらよいでしょうか?
基本クラスのほうのShow関数消してもダメです(コンパイルエラーになる)
C++では
「基本クラスへのポインタとして宣言されたポインタは、その基本クラスから
派生したどのクラスを指し示すのにも使用できる」という機能をもっています
#include <iostream> using namespace std; class Parent { public: virtual void Show() { cout << "親クラスShow" << endl; } }; class Child: public Parent { public: // オーバーライド void Show() { cout << "子クラスShow、オーバーライド" << endl; } }; // main関数(クラスの外部) int main() { Parent* pt; Child ch; pt = &ch; pt->Show(); return 0; }
Parent型(基本クラス)のポインタを用意し、
ch(継承クラス)のオブジェクトのアドレスを代入します。
ただし、継承クラスのオーバライドしたShow関数を呼び出すには
親クラスのオーバライドする関数にvirtualというキーワードをつけていることに注意してください。
(この例だと、ParentクラスのShow関数)
これが仮想関数と呼ばれる由来です
これだけでは、「何これ?おいしいの?」状態です
なので実践的な例を挙げたいと思います
このような仕様が効果を発揮するのは基本となる仕様が決まっているけど、
派生で特殊な機能を持たせたい場合などです
たとえば、ゲームの職業とかの仕様です
職業ごとにHP補正、MP補正、特殊スキルを持つことがわかっていますが
HP補正、MP補正などの割合や特殊スキルは職業ごとに違います
(建前上講座の)最後なので、少し長めです
この例では、キャラクターに職業を持たせ
JOBチェンジさせています
#define _CRT_SECURE_NO_WARNINGS #include <string.h> #include <stdlib.h> #include <time.h> #include <iostream> using namespace std; //プレイヤーデータ struct PLAYERDATA { char name[256]; char jobname[256]; int Level; int HP; int MP; int power; int speed; int inteligence; }; //ジョブデータ struct JOBDATA { char jobname[256]; float HP; float MP; float power; float speed; float inteligence; }; class Person { protected: PLAYERDATA status; PLAYERDATA base; public: Person(){} Person(PLAYERDATA pd):base(pd) { strcpy(status.name,base.name); } virtual ~Person(){} void ShowStatus() { cout << "Name = " << status.name << endl; cout << "Job = " << status.jobname << endl; cout << "Level = " << status.Level << endl; cout << "HP = " << status.HP << endl; cout << "MP = " << status.MP << endl; cout << "power = " << status.power << endl; cout << "speed = " << status.speed << endl; cout << "inteligence = " << status.inteligence << endl; } void LevelUP() { srand((unsigned int)time(NULL)); base.Level++; base.HP += rand() % 20; base.MP += rand() % 10; base.power += rand() % 7; base.speed += rand() % 5; base.inteligence += rand() % 6; cout << "Level UP !!" << endl; UpDateStatus(); } virtual void UpDateStatus() = 0; }; class Knight : public Person { private: JOBDATA jobknight; public: Knight(PLAYERDATA pd,JOBDATA jd):Person(pd),jobknight(jd) { UpDateStatus(); } virtual ~Knight() { } virtual void UpDateStatus() { strcpy(status.jobname,jobknight.jobname); status.Level = base.Level; status.HP = static_cast<int>(base.HP * jobknight.HP); status.MP = static_cast<int>(base.MP * jobknight.MP); status.power = static_cast<int>(base.power * jobknight.power); status.speed = static_cast<int>(base.speed * jobknight.speed); status.inteligence = static_cast<int>(base.inteligence * jobknight.inteligence); } }; class Magician : public Person { private: JOBDATA jobmagician; public: Magician(PLAYERDATA pd,JOBDATA jd):Person(pd),jobmagician(jd) { UpDateStatus(); } virtual ~Magician() { } virtual void UpDateStatus() { strcpy(status.jobname,jobmagician.jobname); status.Level = base.Level; status.HP = static_cast<int>(base.HP * jobmagician.HP); status.MP = static_cast<int>(base.MP * jobmagician.MP); status.power = static_cast<int>(base.power * jobmagician.power); status.speed = static_cast<int>(base.speed * jobmagician.speed); status.inteligence = static_cast<int>(base.inteligence * jobmagician.inteligence); } }; int main() { PLAYERDATA playerdata; JOBDATA knight ,magician ; // 外部データ(本当はファイル読み込みが望ましい) strcpy(playerdata.name,"player"); playerdata.Level = 1; playerdata.HP = 100; playerdata.MP = 80; playerdata.power = 50; playerdata.speed = 30; playerdata.inteligence = 50; strcpy(knight.jobname,"knight"); knight.HP = 1.4f; knight.MP = 0.4f; knight.power = 1.2f; knight.speed = 1.2f; knight.inteligence = 0.3f; strcpy(magician.jobname,"magician"); magician.HP = 0.8f; magician.MP = 2.0f; magician.power = 0.3f; magician.speed = 0.8f; magician.inteligence = 1.5f; // ポリモーフィズム Person *player; Knight knightplayer(playerdata,knight ); Magician magicianplayer(playerdata,magician ); player = &knightplayer; player->ShowStatus(); cout << endl; player->LevelUP(); cout << endl; player->ShowStatus(); cout << endl; cout << "Job Change !!" << endl; cout << endl; player = &magicianplayer; player->ShowStatus(); return 0; }
いくつか説明してない箇所があります
まずPersonクラスの
virtual void UpDate() = 0;
という関数です
これは純粋仮想関数とよばれ、継承したクラスでは
必ずオーバライドして仕様をかかなければなりません
これにより継承クラスには必ずその関数があることを保証します
1つでも純粋仮想関数が存在すると、そのクラスは抽象クラスとよばれ、インスタンス化できません
インスタンス化というのは
Person pd; Person pd = new Person();
などとやって変数用メモリを用意することです
ただし、インスタンスを生成せず、ポインタを用意することは許されます
Person *player;
もうひとつ説明してないのは
static_cast<int>(なんとか)
という部分です、これはただ単にint型にキャストしているだけです
(int)(なんとか)
とキャストしても同じです
ただ厳密にはキャストにもいくつかタイプがあるのですが
C++ではキャストのタイプを明記することができます
なので、明記したほうがより親切といえるでしょう
(キャストの種類については各自調べてください)
このように基本クラスのポインタに継承クラスのオブジェクトをとっかえひっかえして
柔軟な対応をできるようにすることを
ポリモーフィズムと呼びます。
この講座を通して
C++はカプセル化、オーバロード(+関数のデフォルト引数)、
オーバライド、ポリモーフィズム
などC言語にはない
柔軟かつ堅牢な仕様が設計できることがわかりました
以上のような機能をフルに生かし、オブジェクト指向(クラス)による
設計をしていけば、再利用可能な便利なライブラリを作ることが可能になり
生産性もきわめて向上します
C++基礎講座はこれで終わりです
でも、気まぐれでまたなんか書くかもしれないので乞うご期待(?)
この講座で説明はしてないですが、
演算子のオーバロードとテンプレートという便利な機能があるので
余力があるひとは勉強してみてください