当我使用父类指针将它们添加到向量时,我无法使用特定于子类的功能



我正在尝试创建一个;外来入侵者";我自己玩游戏。为了制造敌人和玩家,我创建了一个名为"实体";并将其划分为子类,如Player、shootingEnemy、IdleEnemy。当编码时,我意识到将它们收集在vector<Entity>中会使我的碰撞检测功能变得更容易。

在互联网上搜索后,我了解到这被称为";对象切片";并复制对象的基础部分。

所以最终的版本变成了这个。

int main()
{
int BoardWidth = 50;
int BoardLength = 30;
vector<Bullet> bullets;
vector<Entity*> SpaceShips;

setup(SpaceShips, BoardWidth, BoardLength); 
double ElapsedTime = 0;
int PreviousRoundSec = 0;
int PreviousRoundQSec = 0;
DrawGame(BoardWidth, BoardLength, SpaceShips, bullets);
int IsGameOver = 0;
auto start = chrono::steady_clock::now();
while(!IsGameOver)
{
// Updates EverySecond
if ((int)(ElapsedTime / 1000) > PreviousRoundSec)
{
PreviousRoundSec = (int)(ElapsedTime / 1000);

}
// Updates every quarter of a second
if ((int)(ElapsedTime / 250) > PreviousRoundQSec)
{
PreviousRoundQSec = (int)(ElapsedTime / 250);

}
// To keep time
auto end = chrono::steady_clock::now();
ElapsedTime = chrono::duration_cast<chrono::milliseconds>(end - start).count();
}
if (IsGameOver == 1)
{
// conjualations
}
else if (IsGameOver == 2)
{
// GameOver
}
return 0;
}

但是当我尝试使用一些特定于子类的函数时,我会得到一个编译器错误,说"CLASS";实体";没有任何名为";射击"。

我正在尝试练习类和多态性,所以我甚至不知道这有解决方案,因为编译器无法知道这个向量的哪个元素属于哪个子类。

这也是我的类标题页,以备不时之需。

class Entity
{
public:
int x;
int y;
int width;
int length;
int hp;
bool shooting;
public:
Entity(int x, int y, int width, int length, int hp, bool shooting): x(x), y(y), width(width), length(length), hp(hp), shooting(shooting) {}
};
class Bullet : public Entity
{
private:
char dir;
int ID;
public:
Bullet(int x, int y, char GivenDir, int GivenID) : Entity(x, y, 1, 1, 1, false) { dir = GivenDir; ID = GivenID; }
void Move();
void IfHit(vector<Entity>& SpaceShips);
void IfOut();
};
class Player : public Entity
{
private:
char action = 'a';
public:
Player(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) {}
void GetAction();
void Move();
void Shoot(vector<Bullet>& bullets);
bool IfHit(vector<Entity>& SpaceShips, vector<Bullet>& bullets);

};
class IdleEnemy : public Entity
{
public:

IdleEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, false){}

bool IfHit(Player* player, vector<Bullet> &bullets);
void Move(char HordDir);

};
class ShootingEnemy : public Entity
{

public:
ShootingEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) {}
void Shoot(vector<Bullet> &bullets);
bool IfHit(Player* player, vector<Bullet> &bullets);
void Move(char HordDir);
};

您需要检查C++中的运行时多态性。让我们看看你是怎么做到的。首先,您需要更改Entity类接口。您需要添加虚拟或纯虚拟函数。我添加了纯虚拟功能;

class Entity
{
public:
int x;
int y;
int width;
int length;
int hp;
bool shooting;
public:
Entity(int x, int y, int width, int length, int hp, bool shooting) : x(x), y(y), width(width), length(length), hp(hp), shooting(shooting) {}
void virtual Move() = 0; // pure virtual function
void virtual IfHit() = 0; // pure virtual function 
};

虚拟函数是可重写的函数。此外,它们有实现,但当我们谈论纯虚拟函数时,它们只为类提供了一个接口。您需要在派生类中重写该函数。当你实现你的派生类时,你需要这样做,

class Bullet : public Entity
{
private:
char dir;
int ID;
public:
Bullet(int x, int y, char GivenDir, int GivenID) : Entity(x, y, 1, 1, 1, false) { dir = GivenDir; ID = GivenID; }
void Move()override;
void IfHit();
void IfOut();
};
class Player : public Entity
{
private:
char action = 'a';
public:
Player(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) {}
void GetAction();
void Move();
void Shoot(vector<Bullet>& bullets);
void IfHit()override {//code};

};
class IdleEnemy : public Entity
{
public:
IdleEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, false) {}
void IfHit()override;
void Move()override;
};
class ShootingEnemy : public Entity
{

public:
ShootingEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) {}
void Shoot(vector<Bullet>& bullets);
void IfHit()override;
void Move()override;
};

这些函数可以内联实现,也可以在源文件中实现。此外,这些函数的一个重要点是,除非您不使用协变返回类型,否则返回值、函数签名和名称必须相同。

从派生类中可以看出,有些函数并不常见。我知道你的问题,我该如何使用它:(正如评论中提到的例子,你需要使用dynamic_cast运算符。

int main()
{
Entity* ptr = new ShootingEnemy{ 1,2,4 };
ptr->Move();
ptr->IfHit();
if (auto SE = dynamic_cast<ShootingEnemy*>(ptr))
SE->Shoot(...);
}

dynamic_cast运算符是一个运行时转换运算符。它将基类指针的类型转换为派生类。它被称为下行广播。此外,它还检查基类指针是否指向目标派生类。如果dynamic_cast操作失败,则返回nullif statement变为失败。通过这种方式,您可以使用运行时多态性和类成员函数。

顺便说一句,尽可能避免对象切片。您正在丢失派生类属性。

为了更好地理解,请参考dynamic_cast 类

编译器告诉你真相。你有一个指向Entity的指针,它的接口中显然没有Shoot方法,所以你怎么可能在没有任何强制转换的情况下调用它呢?

您在这里试图实现的动态多态性背后的思想是关于拥有一个公共接口(您的基类Entity(,在每个子类中都有特定的实现。因此,公开可用的方法签名将在所有子类中都是通用的,但在实现中则不然。

从设计的角度来看,最干净的方法是将Entity重命名为ShootableEntity,并在其中声明一个纯虚拟的Shoot方法。然后,所有子类都应提供一些实现。

如果不是所有的容器都实现了Shoot,但您正试图以这种方式通用它们,也许您应该重新考虑这种方法,例如创建两个容器——用于可射击实体和不可射击实体。然后,当迭代可射击实体(实际上是ShootableEntity子类的类的实例,其中包含Shoot声明(时,可以在基类的指针上调用Shoot,而不会出现任何问题。

但是,您的Entity并不代表任何通用接口。所以,如果你试图利用多态性(所以,你有一个指向基类的指针,但在这个指针后面有一些具体的实例(,这样的类对你没有任何好处。

事实上,文档本身有一个很好的解释:http://www.cplusplus.com/doc/tutorial/polymorphism/

最新更新