虚函数,多态,虚函数表

1.多态

父类的一个指针,可以有多种执行状态(父类的指针调用子类的函数),即多态

复制代码
#include <iostream>
using namespace std;
class Base
{
public:
    virtual void func() { cout << "Base func() called." << endl; }
};
class Dervied : public Base
{
public:
    void func() { cout << "Dervied func() called." << endl; }
};

int main()
{
    Base *b = new Dervied();//父类调用了子类的构造函数
    b->func();//父类是虚函数,调用子类的同名函数
    return 0;
}
结果:Dervied func() called.
 
复制代码

 

2.什么是虚函数?

虚函数是一种由virtual关键字修饰的一种类内函数,可分为虚函数和纯虚函数。我们还是直接先上代码看看吧(代码1.1):

 

复制代码
#include <iostream>
using namespace std;
class A
{
public:
    virtual void func() { cout << "A func() called." << endl; }
};
class B : public A
{
public:
    void func() { cout << "B func() called." << endl; }
};
int main()
{
    A a;
    a.func();
    B b;
    b.func();
    return 0;
}
复制代码

1.析构函数可以写成虚的,但是构造函数不行。

为什么呢?其中的原因比较复杂,简单地来说就是虚函数是通过一种特殊的功能来实现的,它储存在类所在的内存空间中,构造函数一般用于申请内存,那连内存都没有,怎么能找到这种特殊的功能呢?所以构造函数不能是虚的。

首先我们把基类的析构函数写成虚的,有代码1.2:

 
复制代码
#include <iostream>
using namespace std;
class A
{
public:
    A() { cout << "A() called." << endl; }
    virtual ~A() { cout << "~A() called." << endl; }
};

class B : public A
{
public:
    B() { cout << "B() called." << endl; }
    ~B() { cout << "~B() called." << endl; }
};
int main()
{
    B b;
    return 0;
}
复制代码

纯虚函数呢?还是代码1.1那个例子,把:

virtual void func() { cout << "A func() called." << endl; }

修改为:

virtual void func()=0;
 

纯虚函数是不能被调用的,因为它根本就没有实现,只有声明。

所以a.func();这样的代码是会报错的。

  • 在单一 class 中实现虚函数意义并不大,虚函数主要是为了实现子类函数重写父类函数的作用
  • 要实现多态,通常父类中的虚函数与子类中的函数的返回值类型、函数名和参数列表必须都相同的,但是在协变的情况下返回值类型可以不一样,协变即虚函数的返回值类型为所在类的指针或引用
  • 子类重写的函数默认是虚函数,也可以显式的加上 virtual,也可以不加
  • 虚函数不能是内联函数,加上 inline 是没有效果的
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数(在多态中应写虚析构)

3.虚表

这个void *的数组,就是我们的虚函数表。这个void *其实就存放着所有被virtual关键字修饰的函数的实际存放地址。

 父类调用子类时,指针指向的是子类的虚函数表。
  • 若对象有虚函数,对象空间最开始 4Byte(32Bit目标平台)或 8Byte(64bit目标平台)内容是虚表(虚函数列表)的首地址,叫虚指针
  • 在实例化对象时,编译器检测到虚函数(virtual修饰的成员函数)时,会将虚函数的地址放到虚表(类似于一个存放函数指针的数组)中
  • 当实例化子类时,检测到有虚函数的重写,编译器会用子类重写的虚函数地址覆盖掉之前父类的虚函数地址,当调用虚函数时,检测到函数是虚函数就会从虚表中找对应的位置调用,若子类没有重写,虚表中的虚函数地址就还是父类的,若子类中有重写,虚表记录的就是子类重写的虚函数地址,即实现了父类的指针调用子类的函数
  • 虚表中先记录父类中的虚函数地址,接着记录子类中虚函数地址(若子类重写父类的虚函数则是覆盖)
  • 最后虚表还有一个尾值是 0

4.虚析构

  • 在多态中,如果释放父类指针(指向子类的父类指针),只会调用父类的析构函数,将父类的析构函数声明为虚函数(虚析构,加 virtual 修饰的析构函数),就会先调用子类的析构函数再调用父类的析构函数,所以在多态中,要用虚析构
  • 父类的析构函数加了 virtual 修饰,delete 会调用子类和父类的析构函数,子类可以显式的加 virtual ,也可以不加, 默认是有的 virtual
  • 还有一点需要注意的,delete 谁的指针就会调用谁的析构函数
 
 
 
posted @   开锁球  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示