覆盖vs虚拟

问题描述 投票:39回答:7

在函数前使用虚拟词virtual的目的是什么?如果我希望子类覆盖父函数,我只声明相同的函数,如void draw(){}

class Parent { 
public:
    void say() {
        std::cout << "1";
    }
};

class Child : public Parent {
public:
    void say()
    {
        std::cout << "2";
    }
};

int main()
{
    Child* a = new Child();
    a->say();
    return 0;
}

输出为2。

再说一遍,为什么保留字virtualsay()的标题中是必要的?

谢谢一堆。

c++ function virtual override
7个回答
18
投票

这是我认为多态如何工作的经典问题。主要思想是您要抽象每个对象的特定类型。换句话说:你希望能够在不知道它是孩子的​​情况下调用Child实例!

下面是一个示例:假设您有类“Child”和类“Child2”和“Child3”,您希望能够通过它们的基类(Parent)引用它们。

Parent* parents[3];
parents[0] = new Child();
parents[1] = new Child2();
parents[2] = new Child3();

for (int i=0; i<3; ++i)
    parents[i]->say();

你可以想象,这是非常强大的。它允许您根据需要多次扩展Parent,并且使用Parent指针的函数仍然可以工作。为了使其像其他人一样提及,您需要将该方法声明为虚拟方法。


37
投票

如果函数是虚函数,那么你可以这样做并仍然得到输出“2”:

Parent* a = new Child();
a->say();

这是因为virtual函数使用实际类型,而非虚函数使用声明的类型。阅读polymorphism,以便更好地讨论您为什么要这样做。


25
投票

尝试使用:

Parent *a = new Child();
Parent *b = new Parent();

a->say();
b->say();

没有virtual,两者都打印'1'。添加虚拟,孩子将像孩子一样,即使它通过指向Parent的指针被引用。


16
投票

如果不使用virtual关键字,则不会覆盖,但rahter在派生类中定义一个隐藏基类方法的无关方法。也就是说,没有virtualBase::sayDerived::say是无关的 - 这就是名字巧合。

当您使用虚拟关键字(在基础中需要,在派生类中是可选的)时,您告诉编译器从该基础派生的类将能够覆盖该方法。在这种情况下,Base::sayDerived::say被认为是同一方法的覆盖。

当您使用引用或指向基类的指针来调用虚方法时,编译器将添加适当的代码,以便调用最终的覆盖(在大多数派生类中的覆盖,它定义具体实例的层次结构中的方法)正在使用)。请注意,如果不使用引用/指针而是使用局部变量,则编译器可以解析调用,并且不需要使用虚拟调度机制。


12
投票

我亲自测试了它,因为我们可以考虑很多事情:

#include <iostream>
using namespace std;
class A
{
public:
    virtual void v() { cout << "A virtual" << endl; }
    void f() { cout << "A plain" << endl; }
};

class B : public A
{
public:
    virtual void v() { cout << "B virtual" << endl; }
    void f() { cout << "B plain" << endl; }
};

class C : public B
{
public:
    virtual void v() { cout << "C virtual" << endl; }
    void f() { cout << "C plain" << endl; }
};

int main()
{
    A * a = new C;
    a->f();
    a->v();

    ((B*)a)->f();
    ((B*)a)->v();
}

输出:

A plain
C virtual
B plain
C virtual

我认为一个好的,简单的和简短的答案可能看起来像这样(因为我认为能够理解更多的人可以记住更少,因此需要简短而简单的解释):

虚方法检查指针指向的实例的DATA,而经典方法因此不调用与指定类型相对应的方法。

该功能的要点如下:假设您有一个A的数组。该数组可以包含B,C,(甚至派生类型)。如果要按顺序调用所有这些实例的相同方法,则应调用每个重载的方法。

我发现这很难理解,显然任何C ++课程都应该解释如何实现这一点,因为大多数时候你刚刚学习虚函数,你使用它们,但直到你理解编译器如何理解它们以及如何执行它们会处理电话,你是在黑暗中。

关于VFtables的事情是我从来没有解释过它添加了什么样的代码,而且显然这里C ++需要比C更多的经验,这可能是C ++在早期被标记为“慢”的主要原因:事实上,它很强大,但就像所有东西一样,如果你知道如何使用它会很强大,否则你只是“把你的整条腿吹走”。


2
投票

使用关键字virtual时,会创建一个虚拟函数表来定位实例中的正确方法。然后,即使派生实例由基类指针指向,它仍将找到该方法的正确实现。


-1
投票

这是c ++编程的一个非常重要的方面 - 几乎我去过的每次采访都会被问到这个问题。

如果将主要内容更改为:

int main() { Parent* a = new Child(); a->say(); return 0; }

此外,值得了解vtable是什么。

© www.soinside.com 2019 - 2024. All rights reserved.