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

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

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

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


当前回答

以下是我如何理解虚拟函数的含义,以及为什么需要它们:

假设您有以下两个类:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

在主功能中:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

到目前为止还不错,对吧?动物吃普通食物,猫吃老鼠,都没有虚拟食物。

现在让我们稍微改变一下,以便通过一个中间函数调用eat()(本例中的一个普通函数):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

现在我们的主要功能是:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

哦哦。。。我们把一只猫传给func(),但它不会吃老鼠。是否应该重载func()以使其使用Cat*?如果你必须从Animal派生出更多的动物,它们都需要自己的func()。

解决方案是使Animal类中的eat()成为一个虚拟函数:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

主要内容:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

完成。

其他回答

如果你知道潜在的机制,这会有所帮助。C++将C程序员使用的一些编码技术形式化,用“覆盖”代替“类”-具有公共头段的结构将用于处理不同类型的对象,但具有一些公共数据或操作。通常,覆盖的基本结构(公共部分)具有指向函数表的指针,该函数表指向每个对象类型的不同例程集。C++做了同样的事情,但隐藏了机制,即C++ptr->func(…),其中func是C的虚拟(*ptr->func_table[func_num])(ptr,…),派生类之间的变化是func_table内容。[非虚拟方法ptr->func()仅转换为mangled_func(ptr,..)。]

这样做的结果是,您只需要了解基类就可以调用派生类的方法,即,如果例程了解类a,您可以向它传递派生类B指针,那么所调用的虚拟方法将是B的虚拟方法,而不是a的虚拟方法。

为什么我们需要虚拟功能?

虚拟函数避免了不必要的类型转换问题,我们中的一些人会争论,当我们可以使用派生类指针来调用派生类中特定的函数时,为什么需要虚拟函数!答案是,它否定了在大型系统开发中继承的全部思想,因为在大型系统中,非常需要使用单指针基类对象。

让我们比较以下两个简单的程序,以了解虚拟函数的重要性:

无虚拟功能的程序:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

输出:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

具有虚拟功能的程序:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

输出:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

通过仔细分析这两个输出,可以理解虚拟函数的重要性。

下面是一个完整的示例,说明了为什么使用虚拟方法。

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}

如果基类是base,派生类是Der,则可以有一个base*p指针,它实际上指向Der的实例。当您调用p->foo();时;,如果foo不是虚拟的,则执行Base版本的foo,忽略p实际上指向Der的事实。如果foo是虚拟的,则p->foo()执行foo的“最叶”覆盖,充分考虑指向项的实际类。因此,虚拟和非虚拟之间的区别实际上非常关键:前者允许运行时多态性,这是OO编程的核心概念,而后者则不允许。

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

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

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

所以当你说:

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

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