我正在学习C++,我刚刚开始学习虚拟函数。

从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。

但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。

那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。


当前回答

OOP答案:亚型多态性

在C++中,需要虚拟方法来实现多态性,如果应用维基百科中的定义,则更准确地说是子类型或子类型多态性。

维基百科,分类,2019-01-09:在编程语言理论中,子类型化(也称为子类型多态性或包含多态性)是类型多态性的一种形式,其中子类型是通过某种可替代性概念与另一个数据类型(父类型)相关的数据类型,这意味着程序元素(通常是子例程或函数),编写为对父类型的元素进行操作也可以对子类型的元素执行操作。

注意:子类型表示基类,子类型表示继承类。

关于亚型多态性的进一步阅读

https://en.wikipedia.org/wiki/Subtypinghttps://en.wikipedia.org/wiki/Polymorphism_(computer_science)#子类型

技术答案:动态调度

如果您有一个指向基类的指针,那么方法的调用(声明为虚拟)将被分派到所创建对象的实际类的方法。这就是亚型多态性是如何在C++中实现的。

进一步阅读C++中的多态性与动态调度

http://www.cplusplus.com/doc/tutorial/polymorphism/https://en.cppreference.com/w/cpp/language/virtual

实现答案:创建vtable条目

对于方法上的每个修饰符“virtual”,C++编译器通常会在声明方法的类的vtable中创建一个条目。这就是常见的C++编译器实现动态调度的方式。

进一步阅读vtables

https://en.wikipedia.org/wiki/Virtual_method_table


示例代码

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

示例代码输出

Meow!
Woof!
Woo, woo, woow! ... Woof!

代码示例的UML类图

其他回答

关键字virtual告诉编译器它不应该执行早期绑定。相反,它应该自动安装执行后期绑定所需的所有机制。为了实现这一点,典型的编译器1为每个包含虚拟函数的类创建一个表(称为VTABLE)。编译器将该特定类的虚拟函数的地址放在VTABLE中。在每个具有虚拟函数的类中,它都会秘密地放置一个指针,称为vpointer(缩写为VPTR),该指针指向该对象的VTABLE。当您通过基类指针进行虚拟函数调用时,编译器会悄悄地插入代码以获取VPTR并在VTABLE中查找函数地址,从而调用正确的函数并导致延迟绑定。

此链接中的详细信息http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html

解释了虚拟功能的需求[易于理解]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

输出将为:

Hello from Class A.

但具有虚拟功能:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

输出将为:

Hello from Class B.

因此,使用虚拟函数可以实现运行时多态性。

我认为您所指的是这样一个事实:一旦方法被声明为virtual,您就不需要在重写中使用“virtual”关键字。

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

如果在Base的foo声明中不使用“virtual”,那么Derived的foo将只是隐藏它。

当基类中有函数时,可以在派生类中重新定义或重写它。

重新定义方法:派生类中给出了基类方法的新实现。不便于动态绑定。

重写方法:在派生类中重新定义基类的虚拟方法。虚拟方法有助于动态绑定。

所以当你说:

但在书的早些时候,当我了解基本遗传时能够重写派生类中的基方法,而不使用“虚拟”。

因为基类中的方法不是虚拟的,所以您不是在重写它,而是在重新定义它

你熟悉函数指针吗?虚拟函数也是一个类似的想法,只是您可以轻松地将数据绑定到虚拟函数(作为类成员)。将数据绑定到函数指针并不容易。对我来说,这是主要的概念区别。这里的许多其他答案只是说“因为…多态性!”