C++技能进阶指南——多态语法剖析

        前言:多态是面向对象的三大特性之一。顾名思义, 多态就是多种状态。 那么是什么的多种状态呢? 这里的可能有很多。比如我们去买火车票, 有普通票, 学生票; 又比如我们去旅游, 有儿童票, 有成人票等等。 这些都是多态的例子。 具体转化为我们的编程思想就是:让不同类型的对象去完成相同的事, 这就是多态

        本篇内容主要讲述多态, 多为语法方面的知识点。 适合已经学完继承的友友们观看。

        

目录

一、多态的相关概念

1.1虚函数

1.2虚函数的重写

1.3虚函数重写的两个例外

 1.4override 和 final 的使用

二、重载、重写、隐藏(重定义)的区别

三、如何构成多态

四、抽象类

五、普通继承和接口继承

六、静态绑定和动态绑定


具体什么是多态在前言中已经提到, 正文部分不做赘述。

一、多态的相关概念

1.1虚函数

        被virtual关键字修饰的成员函数叫做虚函数。 例如:

//A位基类
class A 
{
public:

	virtual void func()    //定义一个虚函数
	{
		cout << "Afunc()" << endl;
	}
};

         需要注意的是, 对于构造函数和析构函数来说。 析构函数可以是虚函数, 但是构造函数不可以是虚函数。 

具体原因如下:(建议看完整篇文章和总结虚函数表机制——c++多态底层原理-CSDN博客​​​​​​ 之后再来看下面这段解释):

        首先:通过之前的学习, 我们知道了, 虚函数的地址是存在虚函数表里面的。 想要调用对应的虚函数, 我们需要先去虚函数表中寻找对应虚函数的地址。 但是虚函数表是在构造函数的初始化列表初始化的。如果构造函数是虚函数, 那么调用构造函数的时候就找不到。 所以构造函数没办法是虚函数。

1.2虚函数的重写

        虚函数的重写就是: 在派生类当中, 有一个和基类中某一个虚函数函数头的虚函数(函数头就是:函数的返回值, 函数名, 函数的参数列表)。 这个时候就会构成虚函数的重写, 即 子类重写了基类的虚函数

//A位基类
class A 
{
public:

	virtual void func() 
	{
		cout << "Afunc()" << endl;
	}
};

//B类继承A类
class B : public A
{
public:
	
	//重写A类的func函数
	virtual void func()  //注意, 这里的virtual可以不写, 因为编译器默认这里是加了virtual的
	{
		cout << "Bfunc()" << endl;
	}
};

        需要注意的是, 上图中派生类的func可以不加virtual, 因为基类的func是虚函数, 编译器会默认派生类中和他函数头相同的函数也是虚函数。 

1.3虚函数重写的两个例外

        协变:派生类在重写基类的虚函数的时候, 与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用, 派生类虚函数返回派生类对象的指针或者引用的时候, 成为协变。


//A位基类
class A
{
public:

	virtual A* func()
	{
		cout << "Afunc()" << endl;
	}
};

//B类继承A类
class B : public A
{
public:

	//重写A类的func函数
	virtual B* func()
	{
		cout << "Bfunc()" << endl;
	}
};

//C类继承A类
class C : public A
{
	virtual C* func()
	{
		cout << "Cfunc" << endl;
	}
};

    

        析构函数的重写: 基类析构函数如果加了virtual, 那么说明基类的析构函数为虚函数。 这个时候如果派生类的析构函数也就变成了虚函数。 那么成不成为虚函数对于析构函数来说有什么不同呢?

        首先我们需要知道的是, 在一个普通的类之中, 编译器其实将析构函数统一处理成为了destructor。

        然后, 对于一个派生类来说, 如果它的析构函数不是虚函数。 当我们使用父类的指针构成多态时, 只会析构派生类的一部分:

//A位基类
class A
{
public:

	virtual A* func()
	{
		cout << "Afunc()" << endl;
	}

    //其他动态内存分配的空间
    //int* ...
    //double* ...
};

//B类继承A类
class B : public A
{
public:

	//重写A类的func函数
	virtual B* func()
	{
		cout << "Bfunc()" << endl;
	}

    //其他动态内存分配的空间
    //int* ...
    //double* ...
};


void test_func(A* p)
{
	p->func();
}


int main() 
{
	C c;

	A* p = nullptr;
	p = &c;

	delete p;
	return 0;
}

         如上图, 假如delete p, 那么就只能释放属于C类自己的那一部分。那么属于A类的那一部分将得不到释放。 

        但是, 如果我们对A类的析构函数使用虚函数。 那么派生类的析构函数也变成了虚函数, 这个时候如果再形成多态。delete p就能将A类和C类都释放掉。


//A位基类
class A
{
public:

	virtual A* func()
	{
		cout << "Afunc()" << endl;
	}

	virtual ~A() 
	{}
};

//B类继承A类
class B : public A
{
public:
	//重写A类的func函数
	virtual B* func()
	{
		cout << "Bfunc()" << endl;
	}

	virtual ~B()
	{}
};

 1.4override 和 final 的使用

先谈override, override是用来检验某个虚函数是否构成了重写。如果没有构成重写, 那么编译器就会报错。

        如下为构成重写:

//A位基类
class A
{
public:

	virtual void func()
	{
		cout << "Afunc()" << endl;
	}

};
//B类继承A类
class B : public A
{
public:
	//重写A类的func函数
	virtual void func() override
	{
		cout << "Bfunc()" << endl;
	}

};

如下为没有构成重写:


//A位基类
class A
{
public:

	void func()
	{
		cout << "Afunc()" << endl;
	}
};
//B类继承A类
class B : public A
{
public:
	//重写A类的func函数
	virtual void func() override
	{
		cout << "Bfunc()" << endl;
	}

};

二、重载、重写、隐藏(重定义)的区别

  • 重载: 函数处于相同作用域内, 并且函数的函数名相同, 参数不同。
  • 重写: 函数分别处于基类和派生类中,并且都是虚函数, 并且有相同的函数头
  • 隐藏: 继承体系中函数分别处在基类和派生类的作用与之中, 不是虚函数,并且都具有相同的函数头

三、如何构成多态

        要形成多态有两个条件:

  • 一、虚函数的重写。
  • 二、父类的指针指向子类,或者父类的引用引用子类对象。

        如下为一个多态的实例:

//A位基类
class A 
{
public:

	virtual void func() 
	{
		cout << "Afunc()" << endl;
	}
};

//B类继承A类
class B : public A
{
public:
	
	virtual void func() 
	{
		cout << "Bfunc()" << endl;
	}
};

//C类继承A类
class C : public A
{
	virtual void func() 
	{
		cout << "Cfunc" << endl;
	}
};

int main() 
{
	C c;
	B b;
	A* p = nullptr;
	p = &c;
	p->func();
	p = &b;
	p->func();
	return 0;
}

         在这串代码中, B类和C类都是A类的派生类。 他们都有对A类中的虚函数func进行重写, 满足条件一。 

        然后基类的指针p先是指向了C类的对象。 又指向了B类的对象。 构成了父类的指针指向子类, 满足条件二。

        所以, 这就是一个多态。

其实, 多态的应用场景多为这样:


//A位基类
class A 
{
public:

	virtual void func() 
	{
		cout << "Afunc()" << endl;
	}
};

//B类继承A类
class B : public A
{
public:
	
	virtual void func() 
	{
		cout << "Bfunc()" << endl;
	}
};

//C类继承A类
class C : public A
{
	virtual void func() 
	{
		cout << "Cfunc" << endl;
	}
};


void test_func(A* p) 
{
	p->func();
}


int main() 
{
	C c;
	B b;
	test_func(&b);
	test_func(&c);
	return 0;
}

        这样, 通过传送不同类型的对象给test_func函数, 就能构成多态。

四、抽象类

        如果一个虚函数后面加上 =0, 那么这个虚函数就是纯虚函数, 并且包含这个纯虚函数的类叫做抽象类。

        抽象类不能实例化对象。


//A位基类
class A
{
public:

	virtual void func() = 0;
};



int main() 
{
	A a;

	return 0;
}

但是A的派生类如果重写了纯虚函数, 那么就可以这个派生类就可以实例化处对象。

但是如果A的派生类没有重写纯虚函数, 那么这个派生类同样不能实例化处对象。


//A位基类
class A
{
public:
	virtual void func() = 0;
};

//B类继承A类
class B : public A
{
public:
	//重写A类的func函数
};

int main() 
{
	B b;
	return 0;
}

五、普通继承和接口继承

        普通继承:在继承体系中, 派生类继承了基类的函数, 能够直接使用的是普通继承, 这类继承继承的是基类函数的实现。 

        接口继承:如果继承了基类的虚函数, 并且重写实现了多态。 那么就是一种接口继承, 多态的体系是一种接口的继承, 具体的函数实现是由派生类自己实现的。

六、静态绑定和动态绑定

        静态绑定: 静态绑定又被成为前期绑定,  当程序在编译的时候确定的要调用的函数, 确定了程序要执行的行为, 这个过程成为静态多态。 比如我们使用的函数重载就是静态的多态。

        动态绑定: 动态绑定又被成为后期绑定, 当程序在编译之后也就是运行期间根据不同的对象调用不同的函数。 这个过程叫做动态多态, 也就是多态。

------------------------------------------------------

ps: 本篇内容没有讲解多态的原理, 因为多态的原理其实就是虚函数表。 而虚函数表的详细讲解博主之前已经写过一篇: 总结虚函数表机制——c++多态底层原理-CSDN博客 。

        在这篇文章中, 博主用自己的理解讲解的虚函数表的机制与实现。 写的不甚严谨, 但是里面的结论却是博主通过调试一步一步验证的来的。感兴趣的友友们可以看一下。

后续补带有虚函数的类的内存大小的计算(暂时有点模糊, 先不写, 而且最近考试比较多。可能要等暑假才能补上这一板块)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

10款免费黑科技软件,强烈推荐!

1.AI视频生成——巨日禄 网页版https://aitools.jurilu.com/ "巨日禄 "是一款功能强大的文本视频生成器&#xff0c;可以快速将文本内容转换成极具吸引力的视频。操作简单&#xff0c;用户只需输入文字&#xff0c;选择喜欢的样式和模板&#xff0c; “巨日禄”就会…

Nginx - 安全基线配置与操作指南

文章目录 概述中间件安全基线配置手册1. 概述1.1 目的1.2 适用范围 2. Nginx基线配置2.1 版本说明2.2 安装目录2.3 用户创建2.4 二进制文件权限2.5 关闭服务器标记2.6 设置 timeout2.7 设置 NGINX 缓冲区2.8 日志配置2.9 日志切割2.10 限制访问 IP2.11 限制仅允许域名访问2.12 …

【408真题】2009-16

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

qemu+gdb调试linux内核

打开CONFIG_DEBUG_INFO,编译内核 通过图形菜单配置该宏,执行make menuconfig。 kernel hacking —> compile-time checks and compiler options —> compile the kernel with debug info 验证是否打开成功,grep -nr “CONFIG_DEBUG_INFO” .config。 打开成功,然后…

AcWing 3466. 清点代码库(STL:map,vector)

3466. 清点代码库 需要求有几种不同数列&#xff0c;每种有多少个&#xff0c;可以想到用map。它的键是一个数列&#xff0c;可以把它放在vector里。也就是map<vector<int>,int> 要满足要求的输出序列&#xff0c;就要想把它放在其他容器&#xff0c;或数组里&…

【Linux】信号之信号的保存和处理详解

&#x1f916;个人主页&#xff1a;晚风相伴-CSDN博客 &#x1f496;如果觉得内容对你有帮助的话&#xff0c;还请给博主一键三连&#xff08;点赞&#x1f49c;、收藏&#x1f9e1;、关注&#x1f49a;&#xff09;吧 &#x1f64f;如果内容有误或者有写的不好的地方的话&…

仿《Q极速体育》NBACBA体育直播吧足球直播综合体育直播源码

码名称&#xff1a;仿《Q极速体育》NBACBA体育直播吧足球直播综合体育直播源码 开发环境&#xff1a;帝国cms7.5 空间支持&#xff1a;phpmysql 仿《Q极速体育》NBACBA体育直播吧足球直播综合体育直播源码自动采集 - 我爱模板网源码名称&#xff1a;仿《Q极速体育》NBACBA体育直…

编程实战:自己编写HTTP服务器(系列3:处理框架)

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 系列入口&#xff1a;编程实战…

需求分析部分图形工具

描述复杂的事物时,图形远比文字叙述优越得多,它形象直观容易理解。前面已经介绍了用于建立功能模型的数据流图、用于建立数据模型的实体-联系图和用于建立行为模型的状态图,本节再简要地介绍在需求分析阶段可能用到的另外3种图形工具。 1 层次方框图 层次方框图用树形结…

LaTex 模板 - 东北师范大学申研申博推荐信

文章目录 NENU-Letter-Template项目地址示例特性项目结构如何使用main.texletterContent.tex 如何编译方式 1 &#xff1a;在线编译方式 2 &#xff1a;本地编译 参考 NENU-Letter-Template NENU’s recommendation letter template. 东北师范大学推荐信模板 项目地址 GitHu…

Spring框架学习笔记(五):JdbcTemplate 和 声明式事务

基本介绍&#xff1a;通过 Spring 框架可以配置数据源&#xff0c;从而完成对数据表的操作。JdbcTemplate 是 Spring 提供的访问数据库的技术。将 JDBC 的常用操作封装为模板方法 1 JdbcTemplate 使用前需进行如下配置 1.1 在maven项目的pom文件加入以下依赖 <dependencies…

Windows安装并启动Redis服务端(zip包)

一、Redis简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的基于内存的 Key - Value结构的数据库&#xff0c;遵守 BSD 协议&#xff0c;它提供了一个高性能的键值&#xff08;key-value&#xff09;存储系统&#xff0c;常用于缓存、消息队列、会话存储…

c# sqlite使用

安装包 使用 const string strconn "Data Sourcedata.db"; using (SQLiteConnection conn new SQLiteConnection(strconn)) {conn.Open();var cmd conn.CreateCommand();cmd.CommandText "select 1";var obj cmd.ExecuteScalar();MessageBox.Show(ob…

Python小游戏——打砖块

文章目录 打砖块游戏项目介绍及实现项目介绍环境配置代码设计思路代码设计详细过程 难点分析源代码代码效果 打砖块游戏项目介绍及实现 项目介绍 打砖块游戏是一款经典的街机游戏&#xff0c;通过控制挡板来反弹小球打碎屏幕上的砖块。该项目使用Python语言和Pygame库进行实现…

鲁教版七年级数学上册-笔记

文章目录 第一章 三角形1 认识三角形2 图形的全等3 探索三角形全等的条件4 三角形的尺规作图5 利用三角形全等测距离 第二章 轴对称1 轴对称现象2 探索轴对称的性质4 利用轴对称进行设计 第三章 勾股定理1 探索勾股定理2 一定是直角三角形吗3 勾股定理的应用举例 第四章 实数1 …

【实际项目精选源码】ehr人力资源管理系统实现案例(java,vue)

一、项目介绍 一款全源码可二开&#xff0c;可基于云部署、私有部署的企业级数字化人力资源管理系统&#xff0c;涵盖了招聘、人事、考勤、绩效、社保、酬薪六大模块&#xff0c;解决了从人事招聘到酬薪计算的全周期人力资源管理&#xff0c;符合当下大中小型企业组织架构管理运…

心链2---前端开发(整合路由,搜索页面,用户信息页开发)

心链——伙伴匹配系统 接口调试 说书人&#x1f4d6;&#xff1a;上回书说到用了两种方法查询标签1.SQL查询&#xff0c;2.内存查询&#xff1b;两种查询效率是部分上下&#xff0c;打的是难解难分&#xff0c;是时大地皴裂&#xff0c;天色聚变&#xff0c;老祖斟酌再三最后决…

(十一)统计学基础练习题五(50道选择题)

本文整理了统计学基础知识相关的练习题&#xff0c;共50道&#xff0c;适用于想巩固统计学基础或备考的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-统计学二&#xff09;。序号之前的题请看往期文章。 201&#xff09; 202&#xff09; 203&#xff09; 2…

指纹识别概念解析

目录 1. 指纹是物证之首 1.1 起源于中国 1.2 发展于欧洲 1.3 流行于全世界 2. 指纹图像 3. 指纹特征 4. 指纹注册 5. 指纹验证 6. 指纹辨识 1. 指纹是物证之首 指纹识别技术起源于中国、发展于欧洲、流行于全世界。自20世纪以来&#xff0c;指纹在侦破刑事案件、解决诉…

《图解支付系统设计与实现》电子书_V20240525

相较于上次公开发布的V20240503版本&#xff0c;变更内容如下&#xff1a; 根据掘金网友zz67373&#xff08;李浩铭&#xff09;的勘误建议&#xff0c;优化了部分描述。增加&#xff1a;金额处理规范&#xff0c;低代码报文网关实现完整代码&#xff0c;分布式流控等内容。扩…