【c++编程基础】虚函数virtual的理解与使用

前言

本文主要记录一下对c++编程中虚函数virtual的理解。

虚函数virtual理解

virtual允许子类重新实现,并通过基类指针调用时能够动态选择正确函数,即运行时多态。虚函数或虚方法可以被子类继承和覆盖,编译期无法确定目标函数的具体指向。

如果基类的函数是“虚”的,则调用到的都是最终派生类(英语:most-derived class)中的函数实现,与指针或引用的类型无关。反之,如果函数非“虚”,调用到的函数就在编译期根据指针或者引用所指向的类型决定。
在C++中,在基类的成员函数声明前加上关键字virtual即可让该函数成为虚函数,派生类中对此函数的不同实现都会继承这一修饰符,允许后续派生类覆盖,达到迟绑定的效果。即便是基类中的成员函数调用虚函数,也会调用到派生类中的版本,即虚函数调用时,不看“调用代码写在哪个类”,而看“当前对象实际属于哪个类”。。
内部机制
c++实现虚函数通常依靠虚函数表vtable,vptr指针指向虚函数表vtable,每个类都有自己的vtable,vtable就是保存自己类中虚函数的地址。如果明确指定调用基类版本,则不会动态绑定。构造函数与析构函数不会向下调用,接口决定调用方式,对象决定执行版本。
定义虚函数的限制
(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
(2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
(3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
(4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。

性能影响

虚函数的性能开销主要源于其实现动态多态的机制,包括间接函数调用、内存开销、编译器优化受限、影响分支预测。调用虚函数需要通过虚表指针间接查表跳转,这带来了额外的指令开销和内存占用(每个包含虚函数的类需要维护虚函数表,每个对象需要存储虚表指针)。然而,更主要的性能影响在于,由于编译器在编译时通常无法确定具体调用哪个函数,导致许多优化(如函数内联)无法进行,并且可能影响处理器的分支预测效率。

纯虚函数/纯虚方法

纯虚函数和抽象类在接口设计中大量使用。如果一个类包含至少一个纯虚函数,则这个类为抽象类。抽象类不能实例化,可以定义指针,因为指针只是一个地址。子类必须实现纯虚函数。因为软件设计中,经常需要定义规范,故需要抽象类。

纯虚函数 = 父类只规定“必须有什么功能”,但不告诉你“具体怎么实现”。
抽象类 = 包含纯虚函数的“接口模板”,不能直接创建对象,只能被继承。

纯虚函数

class Animal
{
public:
    virtual void Speak() = 0;  // 接口,表示这个函数没有实现,必须由子类实现。
};

纯虚函数通常只有声明没有定义,但是虚析构函数必须有实现,因为派生类析构时一定会调用析构函数。

image

 接口基类必须有virtual析构函数,

class A
{
public:
    ~A() = default; // c++11引入,使用编译器自动生成的默认实现。
};

抽象接口类只是接口,本身没有资源,故其西沟函数使用default足够。

 

代码示例

virtual_1.cc      Show() 不是虚函数,因此调用基类版本。

#include <iostream>
using namespace std;

class Base
{
public:
    void Func()
    {
        cout<<"Base::Func"<<endl;
        Show();
    }

    void Show()
    {
        cout<<"Base::Show"<<endl;
    }
};

class Derived:public Base
{
public:
    void Show()
    {
        cout<<"Derived::Show"<<endl;
    }
};

int main()
{
    Derived d;
    d.Func();
    return 0;
}
View Code

output:

Base::Func
Base::Show
virtual_2.cc   Show() 是虚函数,发生动态绑定,调用派生类版本。
#include <iostream>
using namespace std;

class Base
{
public:
    void Func()
    {
        cout<<"Base::Func"<<endl;
        Show();
    }

    virtual void Show()
    {
        cout<<"Base::Show"<<endl;
    }
};

class Derived:public Base
{
public:
    void Show() override
    {
        cout<<"Derived::Show"<<endl;
    }
};

int main()
{
    Derived d;
    d.Func();
    return 0;
}
View Code

output:

Base::Func
Derived::Show
virtual_3.cc    代码通过 Base::Show() 明确指定调用基类版本,绕过虚函数动态绑定。
如果明确指定Base::Show();,即强制调用基类版本,则不会动态绑定。
#include <iostream>
using namespace std;

class Base
{
public:
    void Func()
    {
        cout<<"Base::Func"<<endl;
        Base::Show(); // 强制调用基类版本。
    }

    virtual void Show()
    {
        cout<<"Base::Show"<<endl;
    }
};

class Derived:public Base
{
public:
    void Show() override
    {
        cout<<"Derived::Show"<<endl;
    }
};

int main()
{
    Derived d;
    d.Func();
    return 0;
}
View Code

output:

Base::Func
Base::Show
virtual_4.cc    基类构造期间调用虚函数时,不会分派到尚未构造完成的派生类,因此调用 Base::Init()。
构造函数/析构函数,构造期间,对象还没有构造完成,故虚函数不会向下调用。
#include <iostream>
using namespace std;

class Base
{
public:
    Base() 
    {     
        Init(); 
    }  
    virtual void Init() 
    {     
        cout<<"Base::Init"; 
    }
    void Func()
    {
        cout<<"Base::Func"<<endl;
        Show();
    }

    virtual void Show()
    {
        cout<<"Base::Show"<<endl;
    }
};

class Derived:public Base
{
public:
    void Init() override 
    {
         cout<<"Derived::Init"; 
    }
    void Show() override
    {
        cout<<"Derived::Show"<<endl;
    }
};

int main()
{
    Derived d;
    return 0;
}
View Code

output:

Base::Init

参考

1. virtual百科

 

 

  

 
 
 
 
 
 
 
 
 
 
 
 

 

posted on 2026-07-14 18:20  鹅要长大  阅读(9)  评论(0)    收藏  举报

导航