c++ 多态与虚函数

news2025/1/23 21:22:52

c++中多态分为静态多态和动态多态,静态多态是函数重载,在编译阶段就能确定调用哪个函数。动态多态是由继承产生的,指不同的对象根据所接收的消息(成员函数)做出不同的反应。例如,动物都能发出叫声,但不同的动物能发出不同的叫声,这就是多态。

虚函数声明格式:

class A{
权限控制符:
    virtual 函数返回值类型 函数名 (参数列表);
};

c++多态满足条件

(1)基类声明虚函数

class Animal {
public:
	virtual void Sound();
};

(2)派生类重写基类的虚函数

class Dog:public Animal {
public:
	virtual void Sound();
	//void Sound();等价于virtual void Sound();
};

注意:派生类中重写的虚函数前是否添加virtual,均被视为虚函数

(3)将派生类对象赋值给基类指针或引用,通过基类指针或引用访问虚函数

    Dog dog;
	Animal* animal =  &dog;//通过指针
	animal->Sound();
	Animal& animal_1 = dog;//通过引用
	dog.Sound();

声明虚函数注意以下几点:

(1)构造函数不能声明为虚函数,因为构造函数执行时,对象还没有创建,但析构函数可以声明为虚函数

(2)虚函数不能是静态成员函数。因为静态成员函数是对象共享的

(3)友元函数不能声明为虚函数,但虚函数可以作为另一个类的友元函数


 c++11 final关键字

(1)修饰类,表示该类不可以被继承

class A final{};

(2)修饰虚函数,表示该虚函数不能在派生类中重写

virtual void f() final;

c++虚函数实现多态的原理

虚函数是通过动态绑定实现多态的,当编译器在编译过程中遇到virtual关键字时,他不会对函数进行绑定,而是为包含虚函数的类建立一张虚函数表Vftable.编译器按照虚函数的声明依次保存虚函数地址。同时,编译器会在类中添加一个隐藏的虚函数指针Vfptr,指向虚函数表。在创建对象时,将虚函数指针Vfptr放置在对象的起始位置,为其分配内存空间,而虚函数表不占用对象内存空间。

派生类继承基类时,也继承了基类的虚函数指针。当创建了派生类对象时,派生类对象的虚函数指向自己的虚函数表。如果,派生类重写了基类的虚函数,则派生类虚函数会覆盖基类的同名函数。当通过基类指针操作派生类对象时,已派生类对象内存为准,从对象中获取Vfptr,通过Vfptr找到Vftable,从而调用相应的虚函数,实现了动态绑定。

示例介绍:

class Cattle {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat1();
};
class Horse {
public:
	virtual void walk();
	virtual void sound2();
	virtual void eat2();
};
class CattleHorse :public Cattle,public Horse {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat2();
};

其中声明了Cattle类,Horse类,CattleHorse类。CattleHorse重写了walk()方法,Cattle的sound1()方法,Horse的eat2()方法

我们来看一下vftable和vfptr

当创建派生类对象ch赋值给Cattle类时

    CattleHorse ch;
	Cattle* cattle = &ch;

 基类指针cattle从对象ch中获得虚函数指针Vfptr从而获得虚函数表

  调用虚函数时

	cattle->walk();
	cattle->sound1();
	cattle->eat1();

 我们要理解虚函数被重写之后,派生类虚函数会覆盖基类的同名虚函数的原理


c++纯虚函数和抽象类

有时基类并不需要实现函数,只需声明即可,实现交由派生类即可,这样的函数成为纯虚函数

声明格式:

virtual 返回值类型 函数名(参数列表) = 0;

注意:纯虚函数后面"=0",并不是函数的返回值为0,它只是告诉编译器这是一个纯虚函数

纯虚函数的几个说明

(1)如果一个类中包含了纯虚函数,这样的类称为抽象类。抽象类特点是:不能实例化对象,但可以定义抽象类的指针或引用。

如声明了抽象类Animal

class Animal {
public:
	virtual void walk() = 0;
	virtual void eat();
};
	//Animal animal;不允许
    //Animal* animal = new Animal;不允许
	Animal* animal;

其中不能写Animal animal,也即不能实例化对象,但可以定义抽象类的指针Animal *animal;这时如果不用派生类对象为其赋值,尽管实现了eat()方法,也是不可以调用eat()方法的

	animal->eat();//错误,未初始化animal局部变量

 (2)派生类都应该实现基类的纯虚函数,如果不实现,则该函数在派生类中仍然是纯虚函数,该派生类也是抽象类,也不能实例化对象。


c++虚析构函数与纯虚析构函数

若派生类中有开辟到堆区的数据,而基类没有声明虚析构函数,在析构派生类对象时,编译器只会调用基类的析构函数,不会调用派生类析构函数,导致派生类对象申请的资源不能正确的释放。所以需要声明虚析构函数

虚析构函数声明格式:

virtual ~析构函数();

纯虚析构函数声明格式:

virtual ~析构函数() = 0;

简单示例:

class Animal {
public:
	Animal();
	virtual ~Animal();
	virtual void sound()=0;
};
class Dog :public Animal {
public:
	string *name;
public:
	Dog(string name);
	virtual void sound();
	virtual ~Dog();
};
Dog::Dog(string name) {
	cout << "调用Dog子类的构造函数:" << endl;
	 this->name=new string(name);
}
Dog::~Dog() {
	if (this->name != NULL) {
		cout << "调用派生类Dog析构函数" << endl;
		delete name;
		this->name = NULL;
	}
}
int main() {
	Animal* animal = new Dog("小黄");
	animal->sound();
	delete animal;
	return 0;
}

 上图部分代码声明了基类Animal以及构造函数和析构函数,派生类Dog以及构造函数与析构函数,其中派生类为小狗起名声明了name堆区数据,如果不声明基类析构函数为虚析构函数,则派生类堆区数据name就无法释放而造成内存泄漏。

关于虚析构函数的注意事项

(1)在基类声明虚析构函数之后,基类的所有派生类析构函数都自动成为虚析构函数

(2)在析构派生类对象时,先调用派生类析构函数,在调用基类析构函数(栈)

(3)虚析构函数和纯虚析构函数都要在基类中实现,因为假若基类中有堆区开辟的数据,也是需要实现虚析构函数释放资源的

(4)虚析构函数和纯虚析构函数区别:声明了纯虚析构函数后,该类为抽象类(只要该类中有虚函数,该类就是抽象类),不能实例化对象。而声明虚析构函数不会成为抽象类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/521759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

esp32之解析json

文章目录 前言一、json的作用二、json结构三、esp32 json解析安装库解析StaticJsonDocumentDynamicJsonDocument 四、解析今天的北京天气总结 前言 在现代Web开发中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;已成为常用的数据传输格式。ESP32是一款…

Netty 爱好者必看!一文详解 ChannelHandler 家族,助你快速掌握 Netty 开发技巧!

1 Channel 接口的生命周期 Channel 定义了一组和 ChannelInboundHandler API 密切相关的简单但功能强大的状态模型 1.1 Channel 的状态 状 态描 述ChannelUnregisteredChannel 已经被创建&#xff0c;但还未注册到 EventLoopChannelRegisteredChannel 已经被注册到了 EventL…

Wealth 开源的账本响应式网站系统免费部署

演示网站&#xff1a; https://wealth.willin.wang 前置准备 首先需要注册一个 Github 账号&#xff0c;Fork 这个开源项目&#xff1a; https://github.com/willin/wealth &#xff08;欢迎 Star&#xff09; 然后使用 Github 账号分别注册 Vercel 和 Planetscale&#xf…

【Linux 】 ps命令详解,查看进程pid

文章目录 ps概述ps语法指定pid进行查看 ps概述 ps 命令是最常用的监控进程的命令&#xff0c;通过此命令可以查看系统中所有运行进程的详细信息。 ps 命令有多种不同的使用方法&#xff0c;这常常给初学者带来困惑。在各种 Linux 论坛上&#xff0c;询问 ps 命令语法的帖子屡…

双向链表--C语言实现数据结构

本期带大家一起用C语言实现双向链表&#x1f308;&#x1f308;&#x1f308; 文章目录 一、链表的概念&#x1f30e;二、链表中数据元素的构成&#x1f30e; &#x1f30d;三、链表的结构&#x1f30e; &#x1f30d; &#x1f30f;四、 双向带哨兵位循环链表的实现&#x1f3…

ROS2 中 使用奥比中光 Orbbec Astra Pro 深度相机

本文将以 Ubuntu 20.04 和 ROS2 foxy 环境为例&#xff0c;详细介绍如何在 ROS2 中使用奥比中光 Orbbec Astra Pro 深度相机。在这一篇文章中&#xff0c;你会学到如何创建工作空间&#xff0c;使用 usb_cam 功能包&#xff0c;编译安装使用 ros_astra_camera 等。 文章目录 1.…

年薪50万的程序员和一般的中学教师相比,被亲戚看不起

我是一名程序员&#xff0c;已经工作五年&#xff0c;年薪大概有50万左右。然后&#xff0c;亲戚家的孩子是博士生&#xff0c;在一所中学教书&#xff0c;自己一年的工资可以抵达五六年的薪资&#xff0c;不过还是被亲戚给鄙视了。 很多人都持有不同的观点。我自己是一名程序…

vue-事件修饰符+键盘事件

事件修饰符 1、prevent&#xff1a; 阻止默认事件&#xff08;或在方法中使用e.preventDefault()&#xff09; <a hrefhttps://blog.csdn.net/weixin_52993364?typeblog click.preventshowInfo>点我</a> 说明&#xff1a;这样点击后就不会发生地址的跳转 2、s…

Linux查找指令 时间查看

date 我们在windows中想要看一下时间&#xff0c;我们可以直接在显示器上看到&#xff0c;但是如果我们用的是linux远程登录软件我们像查看一下时间&#xff0c;我们应该怎么做&#xff1f; 我们直接输入date&#xff0c;我们就可以看到当前的时间&#xff0c;不过这个是系统按…

蚁群算法ACS处理旅行商问题TSP【Java实现】

1. 介绍 蚁群算法是一种群体智能算法&#xff0c;模拟了蚂蚁寻找食物时的行为&#xff0c;通过蚂蚁之间的信息交流和合作&#xff0c;最终实现全局最优解的寻找【是否找得到和迭代次数有关】。 蚁群算法的基本思想是将搜索空间看作一个由节点组成的图&#xff0c;每个节点代表…

Linux awk [-v] {print} 命令

AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 语法&#xff1a;语法&#xff1a;awk 条件1 {动作 1} 条件 2 {动作 2} … 文件名 awk是处理文本文件的语言&#xff0c;所以要传入文本数据供其处理&#xff08;文件逐行读入&#xff09;&#xff0c;…

合宙Air780e C-SDK开发

Air78e简介 AirXXXE系列模组&#xff0c;是合宙通信基于移芯EC618平台设计研发的新款4G Cat.1模组。 Air780e的资料点击这里打开。 Air78e开发板简介 一代 IPEX 天线连接器&#xff08;选配&#xff09;4G 弹簧天线一个下载/调试串口&#xff0c;两个通用串口IO 口默认电平…

电子电气架构——车辆电子电气架构的网络安全汇总

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本文主要分享关于车辆电子电气架构的网络安全常见隐患和对应现阶段解决办法。 背景信息 不知道是否还记得《速度与激情8》中黑客Cipher通过网络侵入车辆,…

PyTorch2.0向后兼容性和加速效果浅探

前言 在PyTorch2022开发者大会上&#xff0c;PyTorch团队发布了一个新特性——torch.compile&#xff0c;将PyTorch的性能推向了新的高度&#xff0c;称这个新版本为PyTorch2.0。torch.compile的引入不影响之前的功能&#xff0c;其是一个完全附加和可选的功能&#xff0c;因此…

Linux系统调用函数(300多个)

前言&#xff1a;这里只是给出中文描述&#xff0c;方便浏览熟悉&#xff0c;具体情况建议去具体环境&#xff08;Linux系统&#xff09;下执行 1&#xff09;man 2 systemcalls &#xff08;查看所有系统调用函数&#xff09;&#xff1b;2&#xff09;man 2 open &#xff08…

Codeforces Round 872 (Div. 2)

Problem - D2 - Codeforces 思路&#xff1a; 我们设good点到所有k点的距离和为dis。 假设good点不止一个&#xff0c;那么我们good点的dis应该都是相等的&#xff08;废话&#xff09;。设当前点u是good点&#xff0c;如果他往儿子v移动&#xff0c;儿子有w个点属于k&#…

Maven 项目模板学习

目录 Maven 项目模板 什么是 archetype&#xff1f; 使用项目模板 Maven 将询问原型的版本 创建的项目 创建 pom.xml Maven 项目文档 Maven 快照(SNAPSHOT) 什么是快照? 项目快照 vs 版本 app-ui 项目的 pom.xml 文件 Maven 快照(SNAPSHOT)的出现是因为为了如果pom有…

OpenPCDet系列 | 4.4 DataProcessor点云数据处理模块解析

文章目录 DataProcessor模块解析1. mask_points_and_boxes_outside_range2. shuffle_points3. transform_points_to_voxels DataProcessor模块解析 在对batch_data的处理中&#xff0c;经过了point_feature_encoder模块处理后&#xff0c;就轮到了进行data_processor处理。在d…

django路由(多应用配置)

一、配置全局路由 在应用下&#xff0c;定义视图函数views.py from django.http import HttpResponse from django.shortcuts import render# Create your views here.def get_order(request):return HttpResponse("orders应用下的路由") 在项目的urls路由配置中&…

Qt事件传递及相关的性能问题

在使用Qt时&#xff0c;我们都知道能通过mousePressEvent&#xff0c;eventFilter等虚函数的重写来处理事件&#xff0c;那么当我们向一个界面发送事件&#xff0c;控件和它的父控件之间的事件传递过程是什么样的呢&#xff1f; 本文将以下图所示界面为例&#xff0c;结合源码介…