惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
V
V2EX
S
Security Affairs
T
Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
J
Java Code Geeks
The Register - Security
The Register - Security
U
Unit 42
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
Project Zero
Project Zero
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
D
DataBreaches.Net
博客园 - 司徒正美
V
Vulnerabilities – Threatpost
T
Tor Project blog
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
小众软件
小众软件
L
LangChain Blog
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
A
Arctic Wolf
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 叶小钗
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog
H
Hacker News: Front Page
Know Your Adversary
Know Your Adversary
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta

博客园 - 鹅要长大

【c++编程基础】理解lambda表达式 【c++编程基础】字节对齐pack的理解 【linux基础知识】理解nohup 【linux基础】如何理解python train_dtld.py 2>&1 | tee my_error_log.txt 【工具使用】docker的使用 【基础算法】银行家舍入法 【ubuntu工具】ubuntu好用工具总结 【ubuntu基础工具使用】ubuntu中screen的安装与使用 【linux操作基础】多目录文件复制且同名不覆盖 【linux基础操作】windows系统生成的txt文件在ubuntu打开乱码 【opencv编程基础】fillpoly和polylines函数的理解 【c++编程基础】std::unique的理解 【编程基础】计算三个顶点之间的夹角 【计算几何算法】道格拉斯普克(Douglas-Peuker)算法 [c++]c++ 工程代码中的debug时条件编译隐去的代码会影响程序运行的性能和耗时吗 【CV基础】语义分割任务计算类别权重 【opencv基础】resize使用的问题 git stash的用法 gflags的使用 opencv 判断某个坐标点是否在多边形内cv::pointPolygonTest 判断多边形的顶点内外角点以及对approxPolyDP函数的理解 用Python从零实现贝叶斯分类器 【CV数据集】智慧城市之CCPD车牌数据集
【c++编程基础】虚函数virtual的理解与使用
鹅要长大 · 2026-07-14 · via 博客园 - 鹅要长大

前言

本文主要记录一下对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:

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:

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:

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:

参考

1. virtual百科