为什么我们必须声明虚拟方法

时间:2016-05-03 16:13:53

标签: c++ oop

假设我们有一个类,“动物”和子类,“猫”和“狗”。

让我们说当我们将对象传递给任何“动物”的中间函数时,我们希望允许“猫”和“狗”发出噪音(猫:“喵” - 狗:“woof”)。 / p>

为什么我们必须使用虚拟方法来执行此操作?难道我们不能在没有在“动物”中定义虚拟方法的情况下执行Animal-> makeNoise()吗?由于“猫”和“狗”都是动物,难道不清楚“makeNoise()”是指传递给该函数的动物吗?

这仅仅是语法问题还是更多内容?我很确定在Java中我们不必这样做。

6 个答案:

答案 0 :(得分:10)

在Java中,默认情况下所有成员函数都是virtualstaticprivatefinal除外。

在C ++中,默认情况下所有成员函数都是非virtual。使函数virtual增加了开销 - 包括运行时和对象大小 - 并且C ++哲学不会为你不使用的东西付出代价。我的大部分对象都不是多态的,所以除非我需要,否则我不应该为多态性付费。由于您需要Animal::makeNoise()virtual,因此您必须明确指定它。

答案 1 :(得分:1)

如果你想推断出Animal的类型,然后调用make_sound(),那么你必须对每个动物的孩子对动物对象进行dynamic_cast。这将包括任何直接或间接成为Animal类子代的类。

这对于任何改变都很难维持并且非常痛苦,例如。将新类作为子类添加到Animal类。

由于c ++哲学是效率的,你必须要求编译器为你提供运行时多态性,因为它很昂贵。你会怎么做?通过将make_sound()函数声明为虚拟。这将创建一个vtable(函数指针表),它引用make_sound()的地址,该地址根据对象的类型而不同。

无需向下转发,因为间接为您处理所有事情。可能有数百行代码只是一行代码。这就是间接的力量!!

答案 2 :(得分:0)

在java中,您也使用虚拟方法。 它改善了软件的松散耦合。

例如,您可以使用图书馆并且不知道他们在内部使用哪种动物。有一种你不知道的动物实施,你可以使用它,因为它是一种动物。你使用library.getAnimal方法获得动物。现在你可以使用它们的动物而不知道它产生了什么噪音,因为它们必须实现makeNoise方法。

编辑:所以为了回答你的问题,c ++想要一个明确的声明,而在java中它是隐含的。所以是的,它是一种特定于语言的特质。

答案 3 :(得分:0)

你可以说你必须这样做,因为这是该语言的规则之一。

有一个原因,它有用。

当尝试验证使用Animal的代码时,编译器知道Animal上存在哪些函数。可以在不检查从动物派生的所有类的情况下判断代码是否正确。因此代码不需要依赖所有派生类。如果从Animal派生一个新类但忘记实现makeNoise函数,该函数在新类中是错误的,而不是使用Animal基类的代码,编译器可以指向您的错误。如果没有在Animal中声明的虚函数,就无法判断它是调用代码还是错误的新类。

这里的关键点是,由于其静态类型,这些错误将在编译时被C ++捕获。其他语言可以允许动态类型化,这可以使一些事情变得更容易,但错误只会在运行时发现。

答案 4 :(得分:0)

在Java中,默认情况下所有功能都是虚拟的。在C ++中它们不是,因此当您在给定类型的指针上调用非虚函数时,将使用对象的地址this调用该类型的该函数的实现。

class Animal {
public:
    void sound() { std::cout << "splat\n"; }
    virtual void appearance() { std::cout << "animaly\n"; }
};

class Cat {
public:
    void sound() { std::cout << "meow\n"; }
    virtual void appearance() { std::cout << "furry\n"; }
};

int main() {
    Animal a;
    Cat c;
    Animal* ac = new Cat;

    a.sound();  // splat
    a.appearance();  // animaly

    c.sound();  // meow
    c.appearance();  // furry

    ac->sound();  // splat
    ac->appearance();  // furry
}

当你想编写一个在“Animal”上推广而不需要特定派生类指针的函数时会发生这种情况。

答案 5 :(得分:0)

C ++旨在以尽可能少的开销运行,信任程序员进行正确的调用。基本上,正如我的一个朋友喜欢经常说的那样,它“为你提供枪支和选择射击自己”。速度和灵活性是至关重要的。

要正确地引起真正的多态行为,C ++需要指定它。然而!它只需要在基类中指定,因为所有派生类都将继承虚拟成员函数。如果成员继承了虚拟成员函数,则最好在声明中放置“virtual”,但不是必需的。

ADT通常实现纯虚函数,以指示派生类必须实现该函数。如:

animal makeNoise() = 0; /*Indicates this function contains no implementation.
and must be implemented by derived classes in order to function.*/

同样,只要基类包含它,派生类就不需要在其继承的成员中包含'virtual'。