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

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

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

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


当前回答

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

#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编程的核心概念,而后者则不允许。

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

“这种虚拟调用机制的效率几乎可以与“正常函数调用”机制一样高(25%以内)。它的空间开销是一个具有虚拟函数的类的每个对象中的一个指针加上每个此类类的一个vtbl”[Bjarne Stroustrup的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.

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

对虚拟函数的解释存在的问题是,它们没有解释如何在实践中使用它,以及它如何有助于维护。我创建了一个虚拟函数教程,人们已经发现它非常有用。此外,它基于战场前提,这让它更令人兴奋:https://nrecursions.blogspot.com/2015/06/so-why-do-we-need-virtual-functions.html.

考虑这个战场应用:

#include "iostream"

//This class is created by Gun1's company
class Gun1 {public: void fire() {std::cout<<"gun1 firing now\n";}};
//This class is created by Gun2's company
class Gun2 {public: void shoot() {std::cout<<"gun2 shooting now\n";}};

//We create an abstract class to interface with WeaponController
class WeaponsInterface {
 public:
 virtual void shootTarget() = 0;
};

//A wrapper class to encapsulate Gun1's shooting function
class WeaponGun1 : public WeaponsInterface {
 private:
 Gun1* g;

 public:
 WeaponGun1(): g(new Gun1()) {}
 ~WeaponGun1() { delete g;}
 virtual void shootTarget() { g->fire(); }
};

//A wrapper class to encapsulate Gun2's shooting function
class WeaponGun2 : public WeaponsInterface {
 private:
 Gun2* g;

 public:
 WeaponGun2(): g(new Gun2()) {}
 ~WeaponGun2() { delete g;}
 virtual void shootTarget() { g->shoot(); }
};

class WeaponController {
 private:
 WeaponsInterface* w;
 WeaponGun1* g1;
 WeaponGun2* g2;
 public:
 WeaponController() {g1 = new WeaponGun1(); g2 = new WeaponGun2(); w = g1;}
 ~WeaponController() {delete g1; delete g2;}
 void shootTarget() { w->shootTarget();}
 void changeGunTo(int gunNumber) {//Virtual functions makes it easy to change guns dynamically
   switch(gunNumber) {
     case 1: w = g1; break;
     case 2: w = g2; break;
   }
 }
};


class BattlefieldSoftware {
 private:
 WeaponController* wc;
 public:
 BattlefieldSoftware() : wc(new WeaponController()) {}
 ~BattlefieldSoftware() { delete wc; }

 void shootTarget() { wc->shootTarget(); }
 void changeGunTo(int gunNumber) {wc->changeGunTo(gunNumber); }
};


int main() {
 BattlefieldSoftware* bf = new BattlefieldSoftware();
 bf->shootTarget();
 for(int i = 2; i > 0; i--) {
     bf->changeGunTo(i);
     bf->shootTarget();
 }
 delete bf;
}

我鼓励您首先阅读博客上的文章,了解包装器类创建的原因。

如图所示,有各种火炮/导弹可以连接到战场软件,并且可以向这些武器发出命令,以进行射击或重新校准等。这里的挑战是能够在不必更改蓝色战场软件的情况下更改/更换火炮/导弹,并且能够在运行时切换武器,而无需更改代码并重新编译。

上面的代码显示了问题是如何解决的,以及具有精心设计的包装类的虚拟函数如何封装函数并帮助在运行时分配派生类指针。WeaponGun1类的创建确保了你将Gun1的处理完全分离到类中。无论你对Gun1做了什么改变,你只需要在WeaponGun1中做出改变,并有信心其他职业不会受到影响。

由于WeaponsInterface类,您现在可以将任何派生类分配给基类指针WeaponsIterface,并且因为它的函数是虚拟的,所以当您调用WeaponsIInterface的shootTarget时,派生类shootTarget将被调用。

最好的部分是,您可以在运行时更改枪(w=g1和w=g2)。这是虚拟函数的主要优势,这也是我们需要虚拟函数的原因。

因此,在更换枪支时,不再需要在不同的地方注释代码。现在,这是一个简单而干净的过程,添加更多的枪类也更容易,因为我们只需要创建一个新的WeaponGun3或WeaponGun 4类,我们可以确信它不会破坏BattlefieldSoftware的代码或WeaponGun1/WeaponGun2的代码。

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

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

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

所以当你说:

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

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