覆盖vs虚拟

时间:2010-05-28 22:29:08

标签: c++ function virtual override

在功能面前使用虚拟词保留字的目的是什么?如果我希望子类覆盖父函数,我只需声明相同的函数,例如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.

再说一遍,为什么在virtual的标题中需要保留字say()

非常感谢。

8 个答案:

答案 0 :(得分:37)

如果该功能是虚拟的,那么你可以这样做并仍然得到输出“2”:

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

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

答案 1 :(得分:25)

尝试使用:

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

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

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

答案 2 :(得分: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指针的函数仍然可以工作。为了使其像其他人一样提及,您需要将该方法声明为虚拟。

答案 3 :(得分:15)

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

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

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

答案 4 :(得分: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 ++被标记为“慢”的主要原因。早期:事实上,它很强大,但就像一切一样,如果你知道如何使用它会很强大,否则你只是“把你的整条腿吹掉”。

答案 5 :(得分:2)

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

答案 6 :(得分:0)

假设我们有两个类,如下所示:-

class Fruit {
    protected:
    int sweetness;
    char* colour;
    //...
    public:
    void printSweetness() const {
         cout<<"Sweetness : "<<sweetness<<"\n";
         return;
    }

    void printColour() const {
         cout<<"Colour : "<<colour<<"\n";
         return;
    }

    virtual void printInfo() const {
         printSweetness();
         printColour();
         return;
    }
};

class Apple : public Fruit {
      private:
      char* genus;
      //...
      public:
      Apple() {
          genus = "Malus";
      }
   
      void printInfo() const {
           Fruit::printInfo();
           cout<<"Genus : "<<genus<<"\n";
           return;
      }  
};

现在假设我们具有以下功能……

void f() {
    Fruit* fruitList[100];
    for(int i = 0; i<100 ; i++) {
        fruitList[i]->printInfo();
    }
    return;
}

在上述情况下,我们可以调用相同的函数,并依赖动态分配机制及其提供的抽象,而无需知道该数组中存储了哪种水果。这极大地简化了代码并提高了可读性。而且比使用类型字段要好得多,这会使代码变得丑陋!

在重写方法中,我们必须知道我们正在处理哪种对象,否则将面临可能导致意外结果的对象切片问题。

注意 -我写这个答案只是为了明确显示其好处。

答案 7 :(得分:-1)

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

如果您将主要内容更改为:

会发生什么
int main() { Parent* a = new Child(); a->say(); return 0; }

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