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

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

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

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


当前回答

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

#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.

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

其他回答

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

假设您有以下两个类:

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."

完成。

如果没有“虚拟”,您将获得“早期绑定”。在编译时,根据您调用的指针的类型来决定使用该方法的哪个实现。

使用“虚拟”,您将获得“后期绑定”。使用方法的哪种实现在运行时根据指向对象的类型来决定,即它最初的构造形式。这不一定是根据指向该对象的指针的类型来考虑的。

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* basePtr = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

basePtr->Method1 ();  //  Prints "Base::Method1"
basePtr->Method2 ();  //  Prints "Derived::Method2"

编辑-请参阅此问题。

此外,本教程还介绍了C++中的早期和后期绑定。

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

下面是前两个答案的C++代码的合并版本。

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

两种不同的结果是:

如果没有#define virtual,它将在编译时绑定。Animal*ad和func(Animal*)都指向Animal的says()方法。

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

使用#define virtual,它在运行时绑定。Dog*d、Animal*ad和func(Animal*)指向/引用Dog的says()方法,因为Dog是它们的对象类型。除非未定义[Dog's says()“woof”]方法,否则它将是在类树中首先搜索的方法,即派生类可能会覆盖其基类的方法[Eanimal's says)]。

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

有趣的是,Python中的所有类属性(数据和方法)都是虚拟的。由于所有对象都是在运行时动态创建的,因此不需要类型声明或关键字virtual。下面是Python的代码版本:

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __name__ == "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

输出为:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

这与C++的虚拟定义相同。注意,d和ad是两个不同的指针变量,引用/指向同一个Dog实例。表达式(ad is d)返回True,其值与0xb79f72cc>处的<main.Dog对象相同。

关于效率,虚拟函数的效率略低于早期绑定函数。

“这种虚拟调用机制的效率几乎可以与“正常函数调用”机制一样高(25%以内)。它的空间开销是一个具有虚拟函数的类的每个对象中的一个指针加上每个此类类的一个vtbl”[Bjarne Stroustrup的C++教程]