虚函数、纯虚函数、多态

news/2024/11/18 2:41:16/

一.虚函数

        在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据所指对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。

(一)虚表和虚基表指针

要理解这个问题,我们要引出虚表和虚基表

虚表:虚函数表的缩写,类中含有 virtual 关键字修饰的方法时,编译器会自动生成虚表,它是在编译器确定的

虚表指针:在含有虚函数的类实例化对象时,对象地址的前四个字节存储的指向虚表的指针,它是在构造函数中被初始化的

(二)纯虚函数

纯虚函数(Pure Virtual Function)是C++中的一个特殊类型的虚拟函数,它在基类中声明但没有定义。纯虚函数的声明使用virtual关键字,并在函数声明的末尾添加= 0来表示它是一个纯虚函数。子类(派生类)必须提供纯虚函数的实际实现,否则子类也会被标记为抽象类,无法创建对象。

class Shape {
public:// 声明纯虚函数virtual void draw() = 0;// 普通成员函数void displayInfo() {// 这里可以包含一些通用的代码std::cout << "This is a shape." << std::endl;}
};class Circle : public Shape {
public:// 子类必须提供纯虚函数的实现void draw() override {std::cout << "Drawing a circle." << std::endl;}
};class Square : public Shape {
public:// 子类必须提供纯虚函数的实现void draw() override {std::cout << "Drawing a square." << std::endl;}
};int main() {Circle circle;Square square;circle.displayInfo(); // 调用基类函数circle.draw();        // 调用派生类函数square.displayInfo(); // 调用基类函数square.draw();        // 调用派生类函数return 0;
}

在上述示例中,Shape类包含一个纯虚函数draw(),因此Shape类本身是一个抽象类,不能创建它的对象。然后,CircleSquare类都继承自Shape类,并必须提供对draw()的实际实现。这种机制允许多态性(Polymorphism)的实现,允许不同的派生类以不同的方式实现相同的虚拟函数。

二.多态的实现

根据上图举例分析:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:virtual void prints() {cout << "A::prints" << endl;}A() {cout << "A:构造函数" << endl;}
};
class B:public A {
public:virtual void prints() {cout << "B::prints" << endl;}B() {cout << "B:构造函数" << endl;}
};
class C :public A {
public:};
int main() {A *b = new B();b->prints();b = new C();b->prints();return 0;
}

 子类B重写了基类A的虚函数,子类C并没有重写,从结果分析,依然是体现了多态性???

下面阐述实现多态的过程:

1.编译器在发现基类中含有虚函数时,会自动为每个含有虚函数的类生成一份虚表,该表是一个一维数组,虚表里保存了虚函数的入口地址

2.编译器会在每个对象的前四个字节中保存一个虚表指针,即vptr,指向对象所属类的虚表

3.所谓的合适时机,在派生类定义对象时,程序运行会自动调用构造函数,在构造函数中创建虚表并对虚表指针进行初始化。在构造子类对象时,会先调用父类的构造函数,此时,编译器只“看到了”父类,并为父类对象初始化虚表指针,令它指向父类的虚表;当调用子类的构造函数时,为子类对象初始化虚表指针,令它指向子类的虚表

4.当派生类对基类的虚函数没有重写时,派生类的虚表指针指向的是基类的虚表;当派生类对基类的虚函数重写时,派生类的虚表指针指向的是自身的虚表;当派生类中有自己的虚函数时,在自己的虚表中将此虚函数地址添加在后面。

所以,指向派生类的基类指针在运行时,就可以根据派生类对虚函数的重写情况动态的进行调用,从而实现多态性。 

三.为什么析构函数一般写成虚函数?

        由于类的多态性,通常通过父类指针或引用来操作子类对象。因为多套允许我们以统一的方式处理不同的派生类对象,并且在运行时确定要调用的方法。

        如果析构函数不被声明为虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样会造成派生类析构不完全,造成内存泄漏。

        这种行为是为了确保资源的正确释放。由于我们只知道父类的类型,编译器无法确定指针指向的是哪个子类对象,因此只能调用父类的析构函数来释放资源。

没有虚析构:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:virtual void prints() {cout << "A::prints" << endl;}A() {cout << "A:构造函数" << endl;}virtual ~A() {cout << "A:析构函数 " << endl;}
};
class B:public A {
public:virtual void prints() {cout << "B::prints" << endl;}B() {cout << "B:构造函数" << endl;}~B() {cout << "B:析构函数 " << endl;}
};
int main() {A *b = new B();b->prints();delete b;b = NULL;return 0;
}

虚析构:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:virtual void prints() {cout << "A::prints" << endl;}A() {cout << "A:构造函数" << endl;}virtual ~A() {cout << "A:析构函数 " << endl;}
};
class B:public A {
public:virtual void prints() {cout << "B::prints" << endl;}B() {cout << "B:构造函数" << endl;}~B() {cout << "B:析构函数 " << endl;}
};
int main() {A *b = new B();b->prints();delete b;b = NULL;return 0;
}

 

分析:可以看到析构函数是,先从子类析构,再到父类析构 


http://www.ppmy.cn/news/1096627.html

相关文章

国内外大模型收录列表

时间截止&#xff1a;2023-09-08&#xff0c;数据来源&#xff1a;https://github.com/wgwang/LLMs-In-China。 从收录可以看出开源大模型基座选用&#xff1a;LLaMA>BLOOM​>ChatGLM2​ 国内大模型列表 序号公司大模型省市类别官网说明1百度文心一言,灵医Bot北京通用…

linux系统服务管理systemctl 和systemd

文章目录 Linux中Systemd的Unit文件Systemd 所支持的12种 Unit 文件类型Unit 文件位置优先级 相关工具对比systemctl和systemdsysctl配置内核参数 Linux中Systemd的Unit文件 参考&#xff1a;Linux中Systemd的Unit文件详细介绍 Unit 文件统一了过去各种不同的系统资源配置格式…

JAVA学习-IDEA创建父子项目

JAVA培训-创建父子项目 一、创建父模块 1、new一个新项目&#xff0c;如下图所示&#xff1a; 2、由于这里是父级Maven项目&#xff0c;所以什么都不用选&#xff0c;只需要将SpringBoot版本选成稳定的版本即可。后面带&#xff08;SNAPSHOT&#xff09;&#xff0c;代表版本…

Kubernetes----基于kubeadm工具在CentOS7.9虚拟机上部署一主两从类型的Kubernetes集群环境

【原文链接】Kubernetes----基于kubeadm工具在CentOS7.9虚拟机上部署一主两从类型的Kubernetes集群环境 文章目录 一、虚拟机环境准备1.1 准备三台CentOS操作系统的虚拟机1.2 修改主机名1.3 确认CentOS的版本符合要求1.4 配置地址解析1.5 配置时间同步1.6 关闭防火墙1.7 禁用se…

Laravel系列开源Dcat admin礼盒商城后台管理项目

前言: 在最近能在与某位前段大佬,合作开发一款项目,这宽项目是由laravel框架搭建使用的Dcat admin框架所制作的一个后台的管理系统,前段制作的是一款小程序,虽说后台管理系统无论是前段还是后端都是千篇一律,但内容也是非常丰富。但本项目仅作为开源学习和技术交流&#xff0c…

MySQL学习之——多表查询

MySQL学习之——多表查询 上节课我们不是学习了外键吗&#xff1f;外键是在两张表之间建立联系。其实就能够引申到多表查询的范畴。 如果两张表没有通过外键建立连接的时候&#xff0c;这个时候能不能联合查询呢&#xff1f; 一、多表关系 1.1 一对多 案例&#xff1a;部门…

Android经典蓝牙与低功耗蓝牙开发相关知识

目录 1、需要知道的几个关键关键词1.1、蓝牙通信中是使用的UUID是什么&#xff1f;1.2、经典蓝牙1.3、低功耗蓝牙 2、如何获取不同设备类型的UUID2.1、手机类设备2.2、串口设备2.3、&#xff08;BLE&#xff09;低功耗蓝牙设备 3、&#xff08;BLE&#xff09;低功耗蓝牙与经典…

nodeJS连接mysql数据库的增删改查接口

首先&#xff0c;确保已在项目中安装了 mysql 模块&#xff08;可以使用 npm install mysql 命令进行安装&#xff09;。 const mysql require(mysql);// 创建数据库连接池 const pool mysql.createPool({host: 数据库主机名,user: 数据库用户名,password: 数据库密码,datab…