C++虚函数多态

发布于:2025-06-27 ⋅ 阅读:(15) ⋅ 点赞:(0)
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字节

image-20250620213428990

多态必须存在虚函数,没有虚函数绝不可能存在多态

判断有没有多态:从代码实现上来看,查看调用调用路线是不是利用从vptr找到vtbl,然后通过查询vtbl来找到虚函数表的入口地址并去执行虚函数,如果是这个流程,则就是多态,否则就不是多态。

多态发生的核心条件(必须同时满足):

  1. 通过基类指针或基类引用调用函数
  2. 调用的是虚函数
  • 调用形式:指针->虚函数引用.虚函数
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.当通过父类指针/引用调用被重写的虚函数时,才会表现出多态行为(动态绑定)。

image-20250623091821551

// 基类(父类)必须包含虚函数
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(),导致派生类资源泄漏!
  1. 虚函数表的共享机制

    • 同一类的所有对象共享同一个 vtbl(节省内存)
    • vptr 在对象构造时被初始化指向该类的 vtbl
  2. 多继承下的 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
    
    1. 每个有虚函数的基类都会有自己的vptr
    2. Derived类包含两个vptr:一个来自Base1,一个来自Base2
    3. 派生类新增的虚函数会添加到第一个基类的虚函数表中

总结:多态性的核心逻辑

步骤 关键操作 作用
必要条件 基类声明虚函数,派生类重写 建立动态绑定基础
桥梁搭建 基类指针/引用指向派生类对象 提供统一接口
多态触发 通过基类接口调用虚函数 运行时动态解析实际函数地址
底层支持 虚函数表(vtable)+ 虚表指针(vptr) C++ 实现动态绑定的核心机制

📌 核心结论:多态性 = 虚函数重写 + 基类访问派生类对象 + 通过基类接口调用函数


网站公告

今日签到

点亮在社区的每一天
去签到