重写还是虚拟

在函数前面使用保留字virtual的目的是什么? 如果我想要一个子类重写父函数,我只是声明相同的function,如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。

那么,为什么在say()的头文件中保留字是virtual呢?

谢谢一堆。

这是我认为多态如何工作的经典问题。 主要想法是,你想抽象每个对象的具体types。 换句话说:您希望能够在不知道它是小孩的情况下调用Child实例!

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

 Parent[3] parents; parents[0] = new Child(); parents[1] = new Child2(); parents[2] = new Child3(); for (int i=0;i<3;++i) parents[i]->say(); 

你可以想像,这是非常强大的。 让我们根据需要多次扩展Parent,带有Parent指针的函数仍然可以工作。 为了让其他人提到,你需要声明这个方法是虚拟的。

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

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

这是有效的,因为virtual函数使用实际types,而非虚函数使用声明types。 阅读关于多态性的更好的讨论,你为什么要这样做。

尝试一下:

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

没有virtual ,两个打印“1”。 添加虚拟,并且这个孩子将会像一个孩子一样,尽pipe它是通过一个Parent指针来引用的。

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

当你使用virtual关键字(在base中是必需的,在派生类中是可选的)时,你告诉编译器从这个base派生的类将能够覆盖该方法。 在这种情况下, Base::sayDerived::say被认为是相同方法的重写。

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

我自己testing了一下,因为我们可以考虑很多事情:

 #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 

我认为一个好的,简单的和简短的答案可能是这样的(因为我认为更多的人可以记住更less的记忆,因此需要简短的解释):

虚方法检查指针指向的实例的DATA,而传统方法不会调用与指定types相对应的方法。

该function的要点如下:假设你有一个A的数组。 数组可以包含B,C(甚至派生types)。 如果你想顺序调用所有这些实例的相同方法,你可以调用每一个你超载的方法。

我觉得这很难理解,显然任何C ++课程都应该解释这是如何实现的,因为大多数时候你只是教给你关于虚函数的东西,但是直到你理解了编译器如何理解它们以及如何执行将处理电话,你是在黑暗中。

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

使用关键字virtual时,会创build一个虚拟函数表以在实例中查找正确的方法。 然后,即使派生实例被一个基类指针指向,它仍然会find该方法的正确实现。

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

如果您将主设备更改为:

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

另外,值得了解一个vtable是什么。