前回の最後で「基底クラス」の参照やポインタ変数に「派生クラス」を代入しました。
これがどのように使えるのか説明したいと思います。
まずは色々と準備するところから始めましょう。
プログラムは前回までの続きとなります。
では、Monsterクラスからの「派生クラス」をもう1つ作ります。
以下の2つのファイルを追加してください。
Minotaur.h Minotaur.cpp
書いてある通りミノタウロスのクラスを作ります。
中身はドラゴンと同じく、独自の攻撃関数が1つ、共通の説明文が1つにします。
<sample program cpp075-01>
/* Minotaur.h */
#pragma once #include "Monster.h" class Minotaur : public Monster { public: void ShoulderCharge(); void Explanation() const; }; |
続いて、Minotaur.cppを開き本体を作ります。
<sample program cpp075-02>
/* Minotaur.cpp */
#include "Minotaur.h" #include <iostream> void Minotaur::ShoulderCharge() { std::cout << "体当たり!" << std::endl; m_mp -= 10; } void Minotaur::Explanation() const { std::cout << "ギリシア神話に登場する牛頭人身の怪物である。" << std::endl; } |
これでドラゴンと同じような感じになりました。
統一感を考え、Dragon.cppの以下の行を削除しておいてください。
Monster::Explanation(); |
動作テストをするため、Main.hを開きます。
<sample program cpp075-03>
/* Main.h */
#pragma once
#include "Dragon.h"
#include "Minotaur.h"
|
最後にMain.cppを書き換えます。
<sample program cpp075-04>
/* Main.cpp */
#include "Main.h"
int main()
{
Dragon dragon;
dragon.Initialize(1000, 500);
dragon.ShowStatus();
dragon.Explanation();
dragon.FireBreath();
Minotaur minotaur;
minotaur.Initialize(500, 100);
minotaur.ShowStatus();
minotaur.Explanation();
minotaur.ShoulderCharge();
return 0;
}
|
<実行結果>
HP = 1000 : MP = 500 ヨーロッパの文化で共有されている伝承や神話における伝説上の生物。 その姿はトカゲあるいはヘビに似ている。 ファイアブレス! HP = 500 : MP = 100 ギリシア神話に登場する牛頭人身の怪物である。 体当たり! 続行するには何かキーを押してください・・・
大丈夫そうです。
ドラゴンの「FireBreath関数」、ミノタウロスの「ShoulderCharge関数」以外は同じ関数名である事を確認してください。
まずは1つ試してみます。
↓のようにmain関数を書き換えてください。
<sample program cpp075-05>
/* Main.cpp */
#include "Main.h"
int main()
{
Dragon dragon;
dragon.Initialize(1000, 500);
Minotaur minotaur;
minotaur = dragon;
return 0;
}
|
<コンパイル結果>
error C2679: 二項演算子 '=': 型 'Dragon' の右オペランドを扱う演算子が見つかりません (または変換できません)。 note: 'Minotaur &Minotaur::operator =(Minotaur &&)' の可能性があります note: または 'Minotaur &Minotaur::operator =(const Minotaur &)' note: 引数リスト '(Minotaur, Dragon)' を一致させようとしているとき
コンパイルエラーになりました。
いくら同じ「基底クラス」を持つクラスでも、型が違えば代入は出来ません。
ただ、前回見たように「基底クラス」の参照やポインタ変数であれば代入出来ます。
そこで「ベクタ」を用意して次のようなプログラムに変えてみます。
参照の「ベクタ」は作れませんので、Monsterクラスのポインタ変数で「ベクタ」を作ります。
<sample program cpp075-06>
/* Main.cpp */
#include "Main.h"
#include <vector>
int main()
{
Dragon dragon;
dragon.Initialize(1000, 500);
Minotaur minotaur;
minotaur.Initialize(500, 100);
std::vector<Monster*> vecMonster;
vecMonster.push_back(&dragon);
vecMonster.push_back(&minotaur);
return 0;
}
|
コンパイルエラーは出ていません。
「基底クラス」のポインタであれば、DragonクラスのアドレスもMinotaurクラスのアドレスも受け取る事が出来ました。
今の状態は「異なるクラスが同じ配列の中に入っている」状態です。
「ベクタ」は配列ベースですから、添え字が指定できます。
DragonクラスもMinotaurクラスもMonsterクラスから派生していますから、共通の関数もありますよね。
そこで、次のようにプログラムを追加して動かしてみましょう。
<sample program cpp075-07>
/* Main.cpp */
#include "Main.h"
#include <vector>
int main()
{
Dragon dragon;
dragon.Initialize(1000, 500);
Minotaur minotaur;
minotaur.Initialize(500, 100);
std::vector<Monster*> vecMonster;
vecMonster.push_back(&dragon);
vecMonster.push_back(&minotaur);
for (unsigned int i = 0; i < vecMonster.size(); i++) {
vecMonster[i]->ShowStatus();
vecMonster[i]->Explanation();
}
return 0;
}
|
<実行結果>
HP = 1000 : MP = 500 ヨーロッパの文化で共有されている伝承や神話における伝説上の生物。 その姿はトカゲあるいはヘビに似ている。 HP = 500 : MP = 100 ギリシア神話に登場する牛頭人身の怪物である。 続行するには何かキーを押してください・・・
仮想関数のおかげで、「基底クラス」のポインタからExplanation関数にアクセスした時に「派生クラス」の関数が「優先的に」実行されています。
異なるクラスであっても、「基底クラス」のポインタを介する事で同じ配列などに格納する事が出来る上、異なる動作をさせる事も出来るのです。
これも、オブジェクト指向プログラミングの特徴の1つである「多態性」と言えます。
これを使うと色々な事が出来そうですが、「派生クラス」独自の関数は呼ぶ事が出来ませんよね。
ドラゴンの「FireBreath関数」、ミノタウロスの「ShoulderCharge関数」は「基底クラス」にありませんから呼ぶ事が出来ません。
しかし、どちらも「特殊攻撃」のようなイメージで考えると同列に扱う事が出来ないでしょうか。
特殊攻撃 ---+--- ファイアブレス | | +--- 体当たり |
このようなイメージです。
このように、同じようなイメージを集めてひとくくりにする事を「汎化」と言います。
※乱暴な説明ですが、高尚な説明は他のサイトに任せます。
「犬」と「猫」を汎化すると「動物」とか、「ジェット機」と「戦闘機」で「飛行機」とかですね。
では、「FireBreath関数」と「ShoulderCharge関数」を「特殊攻撃(SpecialAttack)」として「汎化」させてみます。
まずは、Monster.hを開いて「SpecilAttack関数」を追加しましょう。
<sample program cpp075-08>
/* Monster.h */
#pragma once
class Monster {
public:
Monster();
void Initialize(const int hp, const int mp);
void ShowStatus() const;
virtual void Explanation() const;
virtual void SpecialAttack();
protected:
int m_hp;
int m_mp;
};
|
「派生クラス」でオーバーライドする予定ですから「virtual」が付いています。
次に、関数本体を作ろうと思いますが「モンスター全般」の「特殊攻撃」って何でしょう???
個別に「特殊攻撃」を持っていると言うのはイメージ出来ますが、全般的となると全くイメージ出来ません。
このような場合も対処方法があります!
先ほどのプロトタイプ宣言を変更しましょう。
<sample program cpp075-09>
/* Monster.h */
#pragma once
class Monster {
public:
Monster();
void Initialize(const int hp, const int mp);
void ShowStatus() const;
virtual void Explanation() const;
virtual void SpecialAttack() = 0;
protected:
int m_hp;
int m_mp;
};
|
プロトタイプ宣言の後ろに「= 0」を付ける事で「純粋仮想関数」と言うものになります。
「純粋仮想関数」は、本体を持ちません。
その代わり「派生クラス」で必ずオーバーライドしなければなりません。
続いて、Dragon.hを開き「FireBreath関数」を「SpecilAttack関数」に書き換えましょう。
<sample program cpp075-10>
/* Dragon.h */
#pragma once
#include "Monster.h"
class Dragon : public Monster {
public:
void SpecialAttack();
void Explanation() const;
};
|
Dragon.cppの関数も名前を書き換えます。
<sample program cpp075-11>
/* Dragon.cpp */
#include "Dragon.h"
#include <iostream>
void Dragon::SpecialAttack()
{
std::cout << "ファイアブレス!" << std::endl;
m_mp -= 10;
}
void Dragon::Explanation() const
{
std::cout << "ヨーロッパの文化で共有されている伝承や神話における伝説上の生物。"
<< std::endl << "その姿はトカゲあるいはヘビに似ている。" << std::endl;
}
|
Minotaur.hの「ShoulderCharge関数」も同じようにします。
<sample program cpp075-12>
/* Minotaur.h */
#pragma once
#include "Monster.h"
class Minotaur : public Monster {
public:
void SpecialAttack();
void Explanation() const;
};
|
続いて、Minotaur.cppを開き修正してください。。
<sample program cpp075-13>
/* Minotaur.cpp */
#include "Minotaur.h"
#include <iostream>
void Minotaur::SpecialAttack()
{
std::cout << "体当たり!" << std::endl;
m_mp -= 10;
}
void Minotaur::Explanation() const
{
std::cout << "ギリシア神話に登場する牛頭人身の怪物である。" << std::endl;
}
|
準備が出来ましたので、main関数に戻りコードを追加しましょう。
<sample program cpp075-14>
/* Main.cpp */
#include "Main.h"
#include <vector>
int main()
{
Dragon dragon;
dragon.Initialize(1000, 500);
Minotaur minotaur;
minotaur.Initialize(500, 100);
std::vector<Monster*> vecMonster;
vecMonster.push_back(&dragon);
vecMonster.push_back(&minotaur);
for (unsigned int i = 0; i < vecMonster.size(); i++) {
vecMonster[i]->ShowStatus();
vecMonster[i]->Explanation();
vecMonster[i]->SpecialAttack();
}
return 0;
}
|
<実行結果>
HP = 1000 : MP = 500 ヨーロッパの文化で共有されている伝承や神話における伝説上の生物。 その姿はトカゲあるいはヘビに似ている。 ファイアブレス! HP = 500 : MP = 100 ギリシア神話に登場する牛頭人身の怪物である。 体当たり! 続行するには何かキーを押してください・・・
これで「特殊攻撃」も「基底クラス」のポインタから呼び出す事が出来るようになりました。
「純粋仮想関数」を持つクラス(ここではMonsterクラス)は実体を持つことが出来ません。
関数の本体が無いため「抽象クラス」と呼ばれ、実体が作れなくなります。
main関数で試してみます。
<sample program cpp075-15>
/* Main.cpp */
#include "Main.h"
#include <vector>
int main()
{
Monster monster;
return 0;
}
|
<コンパイル結果>
error C2259: 'Monster': 抽象クラスをインスタンス化できません。 note: 次のメンバーが原因です: note: 'void Monster::SpecialAttack(void)': は抽象型です note: 'Monster::SpecialAttack' の宣言を確認してください
コンパイルエラーのメッセージに書いてある通り、抽象クラスはインスタンス(実体)化出来ません。
しかし、参照やポインタ変数は作れますので「派生クラス」を代入する事が出来ます。
結構長くなりましたが、「基底クラス」の参照やポインタを上手く使う事でプログラミングの幅が広がります。
皆さん自身でも色々試してみてください。