C++八股文之面向对象(四)——什么是虚函数和虚函数表? 虚函数和纯虚函数的区别? 什么是抽象类和纯虚函数? 简述一下虚析构函数,什么作用?说说为什么要虚析构,为什么不能虚构造?

目录

什么是虚函数和虚函数表?

虚函数和纯虚函数的区别?

什么是抽象类和纯虚函数?

简述一下虚析构函数,什么作用?

说说为什么要虚析构,为什么不能虚构造


什么是虚函数和虚函数表?

1. 虚函数

C++中的虚函数的作⽤主要是实现了多态的机制。虚函数允许在派⽣类中重新定义基类中定义的函数,使得通过基类指针或引⽤调⽤的函数在运⾏时根据实际对象类型来确定。这样的机制被称为动态绑定或运⾏时多态。

在基类中,通过在函数声明前⾯加上 virtual 关键字,可以将其声明为虚函数。派⽣类可以重新定义虚函数,如果派⽣类不重新定义,则会使⽤基类中的实现。

class Base {
public:
    virtual void virtualFunction() {
        // 虚函数的实现
    }
};
class Derived : public Base {
public:
    void virtualFunction() override {
        // 派⽣类中对虚函数的᯿新定义
    }
};

2. 虚函数表

虚函数的实现通常依赖于⼀个被称为虚函数表(虚表)的数据结构。每个类(包括抽象类)都有⼀个虚表,其中包含了该类的虚函数的地址。每个对象都包含⼀个指向其类的虚表的指针,这个指针被称为虚指针(vptr)。

当调⽤⼀个虚函数时,编译器会使⽤对象的虚指针查找虚表,并通过虚表中的函数地址来执⾏相应的虚函数。这就是为什么在运⾏时可以根据实际对象类型来确定调⽤哪个函数的原因。

虚函数和纯虚函数的区别?

1. 虚函数

  • 有实现: 虚函数有函数声明和实现,即在基类中可以提供默认实现。
  • 可选实现: 派⽣类可以选择是否覆盖虚函数。如果派⽣类没有提供实现,将使⽤基类的默认实现。
  • 允许实例化: 虚函数的类可以被实例化。即你可以创建⼀个虚函数的类的对象。
  • 调⽤靠对象类型决定: 在运⾏时,根据对象的实际类型来决定调⽤哪个版本的虚函数。
  • virtual 关键字声明: 虚函数使⽤ virtual 关键字声明,但不包含 = 0
class Base {
public:
    // 虚函数有实现
    virtual void virtualFunction() {
         // 具体实现
    }
};

2. 纯虚函数

  • 没有实现: 纯虚函数没有函数体,只有函数声明,即没有提供默认的实现。
  • 强制覆盖: 派⽣类必须提供纯虚函数的具体实现,否则它们也会成为抽象类。
  • 禁⽌实例化: 包含纯虚函数的类⽆法被实例化,只能⽤于派⽣其他类。
  • ⽤ = 0 声明: 纯虚函数使⽤ = 0 在函数声明末尾进⾏声明。
  • 为接⼝提供规范: 通过纯虚函数,抽象类提供⼀种接⼝规范,要求派⽣类提供相关实现。
class AbstractBase {
public:
    // 纯虚函数,没有具体实现
    virtual void pureVirtualFunction() = 0;
    // 普通成员函数可以有具体实现
    void commonFunction() {
        // 具体实现
    }
};

什么是抽象类和纯虚函数?

抽象类是不能被实例化的类,它存在的主要⽬的是为了提供⼀个接⼝,供派⽣类继承和实现。抽象类中可以包含普通的成员函数、数据成员和构造函数,但它必须包含⾄少⼀个纯虚函数。即在声明中使⽤ virtual 关键字并赋予函数⼀个 = 0 的纯虚函数。

class AbstractShape {
public:
    // 纯虚函数,提供接⼝
    virtual void draw() const = 0;

    // 普通成员函数
    void commonFunction() {
        // 具体实现
    }
};

纯虚函数是在抽象类中声明的虚函数,它没有具体的实现,只有函数的声明。通过在函数声明的末尾使⽤ = 0 , 可以将虚函数声明为纯虚函数。派⽣类必须实现抽象类中的纯虚函数,否则它们也会成为抽象类。

class AbstractShape {
public:
    // 纯虚函数
    virtual void draw() const = 0;
};

简述一下虚析构函数,什么作用?

虚析构函数是⼀个带有 virtual 关键字的析构函数。 主要作⽤是确保在通过基类指针删除派⽣类对象时,能够正确调⽤派⽣类的析构函数,从⽽释放对象所占⽤的资源。

通常,如果⼀个类可能被继承,且在其派⽣类中有可能使⽤ delete 运算符来删除通过基类指针指向的对象,那么该基类的析构函数应该声明为虚析构函数。

class Base {
public:
    // 虚析构函数
    virtual ~Base() {
        // 基类析构函数的实现
    }
};
class Derived : public Base {
public:
    // 派⽣类析构函数,可以覆盖基类的虚析构函数
    ~Derived() override {
        // 派⽣类析构函数的实现
    }
};

说说为什么要虚析构,为什么不能虚构造

为什么需要虚析构函数?

  • 虚析构函数允许在运⾏时根据对象的实际类型调⽤正确的析构函数,从⽽实现多态性。
  • 如果基类的析构函数不是虚的,当通过基类指针删除指向派⽣类对象的对象时,只会调⽤基类的析构函数,⽽不会调⽤派⽣类的析构函数。这可能导致派⽣类的资源未被正确释放,造成内存泄漏。

构造函数在对象的创建阶段被调⽤,对象的类型在构造函数中已经确定。因此,构造函数调⽤不涉及多态性,也就是说,在对象的构造期间⽆法实现动态绑定。虚构造函数没有意义,因为对象的类型在构造过程中就已经确定,不需要动态地选择构造函数。

  1. 从存储空间⻆度:虚函数对应⼀个 vtable ,这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数,就需要到 vtable 中调⽤,可是对象还没有实例化,没有内存空间分配,如何调⽤。(悖论)
  2. 从使⽤⻆度:虚函数主要⽤于在信息不全的情况下,能使重载的函数得到对应的调⽤。构造函数本身就是要初始化实例,那使⽤虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作⽤在于通过⽗类的指针或者引⽤来调⽤它的时候能够变成调⽤⼦类的那个成员函数。⽽构造函数是在创建对象时⾃动调⽤的,不可能通过⽗类的指针或者引⽤去调⽤,因此也就规定构造函数不能是虚函数。
  3. 从实现上看, vtable 在构造函数调⽤后才建⽴,因⽽构造函数不可能成为虚函数。从实际含义上看,在调⽤构造 函数时还不能确定对象的真实类型(因为⼦类会调⽗类的构造函数);⽽且构造函数的作⽤是提供初始化,在 对象⽣命期只执⾏⼀次,不是对象的动态⾏为,也没有太⼤的必要成为虚函数。
class Base {
public:
    // 错误!不能声明虚构造函数
    virtual Base() {
        // 虚构造函数的实现
    }
    virtual ~Base() {
        // 基类析构函数的实现
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J^T

谢谢帅哥/美女

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值