为什么我们实际上需要运行时多态性?

6

我尝试理解多态性,但不理解为什么我们需要运行时多态性,如果静态多态性可以很好地调用类的成员。

比如说,假设这是一个问题。

#include <bits/stdc++.h>
using namespace std;
class base{
    public:
        virtual void fun(){
            cout<<"base called"<<endl;
        }
};
class derived:public base{
    public:
        void fun(){
            cout<<"derived called"<<endl;
        }
};
int main() {

    base b,*b1;
    derived d;
    b1 = &d;
    b1->fun();
    // b.fun();
    // d.fun();
}

假设这是我的代码,我想访问派生类或基类的函数,只需创建该类的对象即可实现,那么如果没有问题,为什么我们要尝试使用引用来调用对象(运行时多态性)?有人能解释运行时多态性的实际需要吗?如果可能的话,您能否通过使用现实生活场景来解释它?

希望这能帮到你: https://dev59.com/DWUp5IYBdhLWcg3w6q2q#14953419 - Harsh Thakkar
4
为什么我不应该使用 #include <bits/stdc++.h>? - Mike Kinghan
看一下 void f(base* b) { b->fun(); }。它调用哪个函数? - molbdnilo
4个回答

4

多态性被认为是面向对象编程中的重要特性。在 C++ 中,多态性主要分为两种类型:

  • 编译时多态性:通过函数重载或运算符重载实现。

  • 运行时多态性:通过函数重写实现。

现在考虑以下场景。

假设我们有一个名为 Shape 的基类,它具有以下接口。

class Shape {
public:
    Shape(int init_x, int init_y);
    virtual ~Shape() = default;

    virtual void scale(int s) = 0;
protected:
    int x;
    int y;
};

现在我们想要从中继承两个其他类,分别命名为RectangleCircle

class Rectangle : public Shape {
public:
    Rectangle(int init_x, int init_y, int w, int h);
    void scale(int s) override;
private:
    int width;
    int height;
};

class Circle : public Shape {
public:
    Circle(int init_x, int init_y, int r);
    void scale(int s) override;
private:
    int radius;
};

可能你知道,圆形和矩形的实现在它们的scale方法上有所不同,因此我不能在Shape类中实现它。

现在假设我们有一个程序,将所有形状存储在一个容器中,例如vector<Shape*>(例如,它一次从用户那里获取所有形状并将它们存储在这个容器中)。

如果我们想要在其中一个形状上使用scale方法,我们实际上不知道我们正在处理哪种类型的形状,但它会将我们的scale方法调用绑定到其适当的实现。


我基本上明白你的意思,但是你说“我们实际上不知道我们正在处理哪种形状”,那么为什么我们要调用特定派生类的函数呢?base b,*b1; derived d; b1 = &d; // 这一步基本上是... - Shivam Jain
1
假设您有其他类,如“矩形”和“圆形”(例如“正方形”,“三角形”等)。如果您不想将它们全部收集在一个容器中(例如vector<Shape*>),则必须拥有多个容器,每个容器都包含特定类型的形状。实际上,多态性为设计添加了一层抽象级别,这是拥有良好设计的最重要原则之一。 - Mohammad Moridi
1
我建议在派生类中使用void scale(int s) override;代替virtual void scale(int s);。这样,如果它没有按照预期覆盖虚方法,会产生编译时错误,并且我已经对答案进行了更改。我还在基类中添加了一个虚拟析构函数,以便您可以通过基类指针“删除”对象。 - Ted Lyngmo

3

多态性在任何时候都非常有用,当软件无法在编译时准确地知道运行时的所有内容,或者当您需要一个容器来保存实现共同接口的异构组件时。

一个很好的例子是UI工具包。几乎所有的UI工具包都有一个可以容纳其他小部件的容器概念,但UI工具包的作者不知道您将要放入容器的小部件是什么。由于容器需要能够处理任何类型的小部件,因此多态性是实现这一目标最简单的方法。


2
假设您有一个包含类和几个方法的库,这些方法需要指向该类的指针。
class GameObject {
public:
    virtual void paint(void);

public:
    void print_to_display(void);

};

class Game {

public:
    void add_obj(GameObject *obj);
};

由于你只有库(共享库和几个头文件),且不想修改库的源代码(因为这可能会破坏现有方法),所以可以在项目中扩展类 GameObject重写 它的 print 方法,以执行除默认行为之外的其他操作。

class MyObject: public GameObject {

public:
    void paint(void) override;
};

你重写了哪个 paint 函数呢? - xyf

0

@cdhowie已经完美地解释了这个问题。我只是再举一个例子。

如果没有运行时多态性,在C++中重写函数就像是一道不完整的菜谱。在这种情况下,派生类函数将掩盖或隐藏在基类中,而不是覆盖它。

假设,类A实现了function_1和function_2。此外,function_1调用function_2。现在类B继承类A并重新实现function_2。当类B的实例调用function_1时会发生什么?它会调用类A的还是类B的function_2?

考虑以下代码:

#include<bits/stdc++.h>

class A {
public:
  void function_1() {
    std::cout << "This is function_1 from Class A\n";
    this->function_2();
  }

  void function_2() {
    std::cout << "This is function_2 from Class A\n";
  }
};

class B : public A {
public:
  void function_2() {
    std::cout << "This is function_2 from Class B\n";
  }
};

int main() {
  B b;
  b.function_1();
  b.function_2();
  return 0;
}

输出:

This is function_1 from Class A
This is function_2 from Class A
This is function_2 from Class B

唯一能够从类A的函数1调用类B的函数2的方法是使用虚函数。
#include<bits/stdc++.h>
using namespace std;

class A {
public:
  void function_1() {
    std::cout << "This is function_1 from Class A\n";
    this->function_2();
  }

  virtual void function_2() {
    std::cout << "This is function_2 from Class A\n";
  }
};

class B : public A {
public:
  virtual void function_2() {
    std::cout << "This is function_2 from Class B\n";
  }
};

int main() {
  B b;
  b.function_1();
  b.function_2();
  return 0;
}

输出

This is function_1 from Class A
This is function_2 from Class B
This is function_2 from Class B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接