我目前正在研究来自" C ++到游戏编程的示例"我来到这个证明多态性的例子
#include <iostream>
using namespace std;
class Enemy
{
public:
Enemy(int damage = 10);
virtual ~Enemy();
void virtual Attack() const;
protected:
int* m_pDamage;
};
Enemy::Enemy(int damage)
{
m_pDamage = new int(damage);
}
Enemy::~Enemy()
{
cout << "In Enemy destructor, deleting m_pDamage.\n";
delete m_pDamage;
m_pDamage = 0;
}
void Enemy::Attack() const
{
cout << "An enemy attacks and inflicts " << *m_pDamage << " damage points.";
}
class Boss : public Enemy
{
public:
Boss(int multiplier = 3);
virtual ~Boss();
void virtual Attack() const;
protected:
int* m_pMultiplier;
};
Boss::Boss(int multiplier)
{
m_pMultiplier = new int(multiplier);
}
Boss::~Boss()
{
cout << "In Boss destructor, deleting m_pMultiplier.\n";
delete m_pMultiplier;
m_pMultiplier = 0;
}
void Boss::Attack() const
{
cout << "A boss attacks and inflicts " << (*m_pDamage) * (*m_pMultiplier)
<< " damage points.";
}
int main()
{
cout << "Calling Attack() on Boss object through pointer to Enemy:\n";
Enemy* pBadGuy = new Boss();
pBadGuy->Attack();
cout << "\n\nDeleting pointer to Enemy:\n";
delete pBadGuy;
pBadGuy = 0;
return 0;
}
我的问题是,为什么使用这一行:
Enemy* pBadGuy = new Boss()
而不是
Boss badGuy;
badGuy.Attack();
作者称之为&#34;将宝贝类指针用于派生类对象&#34;。 经常使用吗?对于&#34;正常&#34;它有任何优势吗?实例化方法?
谢谢!
答案 0 :(得分:2)
这给出了虚拟方法如何工作的示例。即使你正在调用基类的方法,它也是被调用的子类的方法。
您提出的替代方案并未明确证明这一关键概念。
两种替代方案都完成了相同的事情,但这是为了给出虚拟方法调度的一个例子。
答案 1 :(得分:0)
想想以下内容:你有锤子和锯子。他们都是工具。它们是袋子(如矢量),如果你想使用它们,你从袋子中取出一个,然后你使用它(调用对象的方法)。你不能把锤子放在锯子里。袋子,反之亦然,但你可以把它们放在一个叫做工具的包里。拥有一个袋子而不是两个袋子并不容易。如果要使用它们,请以适当的方式使用它(调用虚方法)。 所以你的敌人可以通过多态而更轻松地处理。此外,您可以创建方法,如
// in your class...
virtual void attackedByOther(const Enemy& other);
在这种情况下,您只需要执行一次更多次的功能,即进行损伤计算等。
所以回答你的问题:如果你使用指针你可以做多态,但是你可以使用实例。
我希望你理解!
答案 2 :(得分:0)
只是添加一点其他人说的话......
使用抽象类和虚拟的主要原因之一是,您可以在同一个数组中拥有多个对象类型。
这是我在学校做的一个项目的例子:
char buffer = '\0';
int itemindex = 0;
Item* myitem;//.................................. I used a polymorphic pointer Item
while (!myfile.get(buffer).fail()){//............ Check for failure each loop iteration
if (buffer == 'N' || buffer == 'P') {
if (_noOfItems - 1 >= itemindex) {//.... -1 because of index 0 and >= to account for the first set of entries
delete _items[itemindex];//.......... if it is >= than there has already been allocation at that index, so it must be freed
}//...................................... to avoid memory leak
if (buffer == 'P') {
myitem = new Perishable();//......... polymorphic pointer static type is now perishable (dynamic will always be item)
} else if (buffer == 'N') {//............... else is extra safe
myitem = new NonPerishable();//....... polymorphic pointer static type is now nonPerishable (dynamic is item)
}
myfile.ignore();//....................... ignore comma
myitem->load(myfile);
_items[itemindex] = myitem;//............ This line assigns myitem to the item index, since its polymorphic, only have to write
//............. it once, within the 'N' || 'P' scope.
itemindex++;//........................... This must incriment every time 'N' || 'P' is encountered, cause each represents an
}//.......................................... item entry.
}
我们要做的是创建易腐烂和不易腐烂的物品。如果你按照注释,你会看到我创建了一个Item(基类)指针,然后根据正在读取的文件,如果char是'P'我创建一个Perishable对象(派生)或NonPerishable对象(也派生)
所以重点是,_items[itemindex] = myitem;
只被调用一次,而不是在每个条件分支buffer = P/N
在这个例子中有一些有趣的事情,但正如我所提到的,Perishable(child)和NonPerishable(child)都在Item(父)数组中。
因此,如果我们处理的是老板(子)和字符(子),并且它们都是实体(父级),则可以遍历包含两种派生类型的实体(父)数组,并调用相同的函数...像这样的东西。
for(int i = 0; i < entityList.length; i++){
entityList[i].attack
}
现在是/真的/很酷。您可以告诉所有实体在一行中执行相同的操作,因为它们具有相同的父类型。
您真正需要知道的是,已经提到过有关动态调度的内容。向我解释的方法很简单:一个对象可以有一个动态类型和一个静态类型。
动态类型是创建引用的类型,如下所示:
Item* myitem //Dynamic type is Item
静态类型是指针/当前/指向的内容。所以现在静态类型的myitem也是类型Item。
如果我这样做:
myitem = new NonPerishable();
myitem指针现在指向子类型'NonPerishable'。动态类型不会更改,因为它是作为Item创建的。所以动态类型是/ still / type Item。然而,静态类型现在是NonPerishable,因为Item指针(myitem)现在指向NonPerishable对象。
注意:动态是它被创建的(这在名称中可能是反直觉的,但情况确实如此)
最后,如果您有一个父类和子类,它们都具有相同名称但实现不同的函数,您将获得早期绑定或后期绑定(也称为动态调度)。
早期绑定意味着触发的函数将是父函数,动态调度意味着触发的函数将是子函数。早期绑定是C ++的默认设置,要获得动态调度,必须将该函数声明为“虚拟”,然后默认为子函数。
您可以覆盖绑定的一种方法是明确声明命名空间。这是一个例子:
class Parent; //pseudo code
class Child : public Parent
object.Child::myfunction()
注意:通常,如果Parent和Child具有相同的功能(myfunction),则它将是早期绑定(父版本)。在这种情况下,您使用child :: namespace,因此它将调用myfunction的子版本。
如果您有任何问题我可以详细说明,这是很多信息。
答案 3 :(得分:0)
我将详细阐述@SamVarshavchik所说的内容,并按顺序回答您的问题。
此特定声明已被使用,因为这有助于我们利用 动态绑定和使用虚函数的概念。该 每个细节都可以在网上找到。简而言之, 动态绑定是对象绑定到的概念 在运行时而不是在编译期间运行,并且是虚拟的 函数是那些支持动态绑定的函数。而且动态 绑定只能用指针和引用来执行而不能 使用对象本身,因为可以使用指向基类的指针 作为派生类的指针,我们使用给定的声明。
“经常使用吗?”是的,它经常被使用,当涉及多态性和继承的概念时更是如此,因为它不是 每次都可以预先知道用户所在的课程 打算召集相应的职能。
我想现在我已经回答了你的上一个问题,因为动态绑定既可以用指针执行,也可以用基类引用而不是对象,我们需要使用给定的方法。