派生类中不同方法的多态性

Polymorphism with different methods in derived classes

本文关键字:方法 多态性 派生      更新时间:2023-10-16

在尝试学习多态性时,我对一件事感到困惑。请考虑以下情况:

存在一个基类Shape和派生类CircleSquare。通过多态性,我可以在基类中将方法get_area实现为虚函数,并在派生类CircleSquare中实现此功能的各个版本。

class Shape
{
public:
Shape(){}
virtual int get_area() = 0;
};
class Square: public Shape
{
public:
Square(int width, int height)
{
// stuff
}
int get_area()
{ 
return width*height; 
}
};
class Circle: public Shape
{
public:
Circle(int radius)
{
// stuff
}
int get_area()
{ 
return pi*radius*radius; 
}
};
int main () 
{
Shape *circle= new Circle(3);
Shape *square= new Square(4,5);
return 0;
}

但是,如果我需要在其中一个派生类中实现单个方法,例如get_radius.如果此方法未在基类中作为虚函数进行宣传,我从编译器那里得到错误消息。但是如果我这样做,我还必须实现派生类Squareget_radius的方法,这没有任何意义,因为正方形没有半径。

有没有更好的方法来解决这个问题?谢谢。

如果你写

Shape *circle=new Circle(4,5);

然后尝试使用此引用来调用get_radius()方法

circle->get_radius(); 

如果未在 Shape 类中声明get_radius方法(虚拟或具体),则会发生错误。发生此错误的原因是circle不是对 Circle 的引用,而是对 Shape 的引用,因此无法简单地解决方法的定义,因为 Shape 中没有定义get_radius方法。
另一方面,如果您在 Shape 类中声明了get_radius,则可以(如果它具有具体的实现)为所有类型的 Shapes 调用 get_radius,它没有多大意义。
所以正确的方法是使用对圆的引用,例如

Circle *circle=new Circle(4,5);
double r=circle->get_radius();
... //do something with r 

一般来说,如果没有强制转换,你就无法做你想做的事情,这是因为运行时多态性正是这个意思:通过公共接口控制类层次结构。您的潜在Circle::get_radius()不是界面的一部分。

如果您知道您的对象是Circle,则一种解决方案有效:使用向下投射

Circle* ptrCircle = dynamic_cast<Circle*>(circle);
int radius = 0;
if(ptrCircle) // the cast succeeded
radius = ptrCircle->get_radius();

其中get_radius()是仅由Circle实现的成员函数。

注意:一般来说,对强制转换的绝对需求表明设计不好,所以最好花一些时间思考如何更好地设计你的代码。

您绝对可以将非虚拟成员函数添加到派生类中,而无需在基类中定义它们,但是不能从基类指针访问这些函数。 即:

//won't work
Shape* shapeP = new Circle();
shapeP ->get_radius(); 
//will work
Circle* circP = new Circle();
circP->get_radius();

您还可以将形状指针投射到您知道形状是圆形的情况,如 vsoftco 在他的答案中提到的那样

我有可能误解了你,但听起来你试图用main打电话给circle->get_radius()以测试它。问题是circle实际上不是一个Circle*对象,它是一个Shape*。派生类的工作方式是,您可以将它们分配给类型为它们扩展/实现的任何类或接口的变量(就像您在示例中所做的那样),因为派生类包含这些基类/接口的每个成员的定义。反之则不然;正如你所说,get_radius()对每个Shape都没有意义.您收到错误不是因为它缺少Circle而是因为您试图获取恰好使用Circle函数实现的随机Shape*的半径。

换句话说,您(作为人类)足够聪明,可以查看代码并看到circle是一个Circle*。编译器足够聪明,可以记住"在查找circle函数时,使用Circle定义的函数",但除此之外,它不知道该特定Shape*实例与square有何不同。据它所知,它唯一能对它们中的任何一个做的事情就是你告诉它每个Shape都可以做的事情。

在您不关心对象的特定Shape的情况下,这很方便 - 您所需要的只是可以为您提供区域的东西。如果您需要知道半径、宽度和高度,或者一个Shape具有但其他人没有的任何其他属性,则需要使用具有更具体类型的变量。

看看这是否更适合您:

Circle *circle= new Circle(3);
circle->get_radius();