class C{
public:void x1(){};void x2(){};};C c;
cout << sizeof(c) <<"\n";1字节
class D{
public:void x1(){};void x2(){};virtual void x3(){};//void *vptr看不见的虚函数表指针
};
D d;
cout << sizeof(d) <<"\n";8字节
类A中,定义了一个虚函数,编译器就会生成一个看不见的成员变量(虚函数表指针)占用一定字节。类中至少存在一个虚函数时,编译时,就会为这个类生成一个虚函数表vtbl,经过编译、链接,类和虚函数表都会保存到可执行文件中,执行时会被装载到内存中。
在编译期间,带有虚函数的类会在编译期间安插一个赋值语句(看不到)
vptr=&A::vftable;
这样就使vptr指向vtbl。
class A{
public://void *vptr看不见的虚函数表指针void func1(){};void func2(){};virtual void vfunc1(){};virtual void vfunc2(){};virtual ~A(){};
private://vptr=&A::vftable;int m_a;int m_b;
};A a;
cout << sizeof(a) <<"\n";16字节 //虚函数一共8字节,m_a和m_b一共8字节
多态必须存在虚函数,没有虚函数绝不可能存在多态
判断有没有多态:从代码实现上来看,查看调用调用路线是不是利用从vptr找到vtbl,然后通过查询vtbl来找到虚函数表的入口地址并去执行虚函数,如果是这个流程,则就是多态,否则就不是多态。
多态发生的核心条件(必须同时满足):
- 通过基类指针或基类引用调用函数
- 调用的是虚函数
- 调用形式:
指针->虚函数
或引用.虚函数
class Animal {
public:virtual void speak() { cout << "Animal sound" << endl; }
};class Cat : public Animal {
public:void speak() override { cout << "Meow" << endl; }
};// 场景1:通过基类指针调用虚函数
Animal* animal = new Cat();
animal->speak(); // ✔️ 多态:输出"Meow"// 场景2:通过基类引用调用虚函数
Cat kitty;
Animal& ref = kitty;
ref.speak(); // ✔️ 多态:输出"Meow"
案例:
class Base
{
public:virtual void myvirfunc() {}
};
Base* pa = new Base();
pa->myvirfunc();//多态Base base;
base.myvirfunc();//不是多态Base* ybase = &base;
ybase->myvirfunc();//多态
1.程序中存在继承关系,且父类至少有一个虚函数,但子类不强制重写虚函数(除非是纯虚函数 = 0
),要触发运行时多态(动态多态),派生类必须重写基类的虚函数。
2.必须通过父类指针或父类引用指向子类对象,才能触发多态。
3.当通过父类指针/引用调用被重写的虚函数时,才会表现出多态行为(动态绑定)。
// 基类(父类)必须包含虚函数
class Base {
public:virtual void myvirfunc() {} // 虚函数声明(virtual 关键字)
};
// 派生类非必须重写普通虚函数(基类已有默认实现)。仅当基类声明为纯虚函数(virtual void func() = 0;)时,派生类必须重写
class Derive : public Base {
public:virtual void myvirfunc() {} // 重写虚函数(virtual 可省略,但建议保留)//C++11 后可用 override 关键字显式标记重写(如 void myvirfunc() override {})
};
//父类指针指向子类对象
Derive derive;
Base* pbase = &derive;
pbase->myvirfunc(); //Derive::myvirfunc()
//或者
Base* pbase2 = new Derive(); //释放内存请自行释放,在这里没演示
pbase2->myvirfunc(); //Derive::myvirfunc()
//父类引用绑定(指向)子类对象
Derive derive2;
Base& yinbase = derive2;
yinbase.myvirfunc(); //Derive::myvirfunc()
虚析构函数
class Base {
public:virtual ~Base() {} // 必须声明为虚析构函数!
};class Derive : public Base {
public://~Derived() {} // 自动成为虚函数,即使不写virtual~Derive() override {} // 但建议显式使用 override(C++11)
};Base* pb = new Derive();
delete pb; // 正确调用 Derive::~Derive() → Base::~Base()
- 若不声明虚析构函数:
delete pb
仅调用Base::~Base()
,导致派生类资源泄漏!
-
虚函数表的共享机制
- 同一类的所有对象共享同一个 vtbl(节省内存)
vptr
在对象构造时被初始化指向该类的 vtbl
-
多继承下的 vptr
class Derived : public Base1, public Base2 {virtual void new_func() {} };
- 派生类会包含多个 vptr(每个基类一个)
- vtbl 可能包含多个子表(Thunk技术处理指针偏移)
class Base1 { public:virtual void func1() { cout << "Base1::func1\n"; }virtual ~Base1() {} };class Base2 { public:virtual void func2() { cout << "Base2::func2\n"; }virtual ~Base2() {} };class Derived : public Base1, public Base2 { public:virtual void new_func() { cout << "Derived::new_func\n"; }virtual void func1() override { cout << "Derived::func1\n"; }virtual ~Derived() {} };
1. Derived类的Base1虚函数表
索引 | 函数类型 | 实际函数地址 -----|--------------------|----------------- 0 | 析构函数 | Derived::~Derived 1 | func1() | Derived::func1 2 | new_func() | Derived::new_func
2. Derived类的Base2虚函数表
索引 | 函数类型 | 实际函数地址 -----|--------------------|----------------- 0 | 析构函数 | Thunk to Derived::~Derived 1 | func2() | Base2::func2
- 每个有虚函数的基类都会有自己的vptr
- Derived类包含两个vptr:一个来自Base1,一个来自Base2
- 派生类新增的虚函数会添加到第一个基类的虚函数表中
总结:多态性的核心逻辑
步骤 | 关键操作 | 作用 |
---|---|---|
必要条件 | 基类声明虚函数,派生类重写 | 建立动态绑定基础 |
桥梁搭建 | 基类指针/引用指向派生类对象 | 提供统一接口 |
多态触发 | 通过基类接口调用虚函数 | 运行时动态解析实际函数地址 |
底层支持 | 虚函数表(vtable)+ 虚表指针(vptr) | C++ 实现动态绑定的核心机制 |
📌 核心结论:多态性 = 虚函数重写 + 基类访问派生类对象 + 通过基类接口调用函数