当前位置: 首页 > news >正文

C++——多态

目录

多态

什么是多态

多态的两种形式

静态的多态(编译时多态)

动态的多态(运行时多态)

实现条件:

虚函数

虚函数重写

重载、覆盖(重写)、隐藏(重定义)的对比

纯虚函数(抽象类)

多态的原理

多态常考题目


多态

什么是多态

概念:多态简单来说就是指多种形态,具体点就是不同的对象做一件事会产生不同的结果,更加专业一点的说是指:同一操作作用于不同对象时,会产生不同的执行结果。简单来说,就是 "同一个接口,不同实现"。

多态的两种形式

静态的多态(编译时多态)

编译时多态在编译阶段就确定调用哪个函数,主要通过函数重载模板实现。

(1)函数重载(Function Overloading)

在同一作用域内,允许定义多个同名函数,但它们的参数列表(参数类型、数量、顺序)必须不同。编译器会根据实参类型在编译时匹配对应的函数。

(2)模板(Templates)

通过模板可以实现通用代码,编译器会根据传入的具体类型在编译时生成对应版本的函数或类,这也是一种静态多态。

动态的多态(运行时多态)

运行时多态在程序运行时才确定调用哪个函数,是 C++ 中更核心的多态形式,通过虚函数(Virtual Function) 和继承实现。

实现条件:
  1. 存在继承关系(子类继承父类);
  2. 父类中声明虚函数(使用virtual关键字);
  3. 子类重写(Override)父类的虚函数;
  4. 通过父类指针或引用指向子类对象,调用虚函数。

例如:

class Animal
{
public:virtual void eat(){cout << "Animal eat" << endl;}};class Dog :public Animal
{
public:void eat(){cout << "dog eat" << endl;}};class Cat :public Animal
{
public:void eat(){cout << "Cat eat" << endl;}
};void Func(Animal& who)
{who.eat();
}int main()
{Animal a;Animal & a1=  a;a1.eat();Func(a);cout << endl;Dog d;Dog& d1 = d;d1.eat();Func(d);cout << endl;Cat c;Cat& c1 = c;c1.eat();Func(c);return 0;
}

虚函数

虚函数概念:即被virtual修饰的类成员函数称为虚函数

虚函数重写

虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

在Dog和Cat中的eat函数都是Animal函数中eat函数的重写

class Animal
{
public:virtual void eat(){cout << "Animal eat" << endl;}};class Dog :public Animal
{
public:void eat(){cout << "dog eat" << endl;}};class Cat :public Animal
{
public:void eat(){cout << "Cat eat" << endl;}
};

虚函数重写的两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
class A{};
class B : public A {};
class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
我们建议析构函数都写成virtual函数,在以下案例中如果析构函数不是虚函数,那么我们在销毁函数时,因为析构的时p1,是Animal类型,所以只会调用Animal的析构函数,但是Dog中空间却未释放,会造成内存泄漏
class Animal
{
public:virtual void eat(){cout << "Animal eat" << endl;}};class Dog :public Animal
{
public:void eat(){cout << "dog eat" << endl;}~Dog(){delete p1;cout << "~Dog" << endl;}
private:int* p1 = new int(10);};
int main()
{Animal* p1 = new Dog;delete p1;
}
C++11 override final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了overridefinal两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,表示该虚函数不能再被重写 

如果重写的编译时会报错

class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒适" << endl;}
};
override: 检查派生类虚函数是否重写了基类某个虚函数,如果不是重写编译报错。

重载、覆盖(重写)、隐藏(重定义)的对比

重载的概念:在同一作用域内,声明多个函数名相同、参数列表不同的函数(与返回值无关)

覆盖的概念:在继承关系中,子类重新实现父类中已声明为虚函数的函数,且函数签名完全一致

隐藏的概念:在继承关系中,子类声明了与父类同名但不满足重写条件的函数,导致父类函数“隐藏”

纯虚函数(抽象类)

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

多态的原理

多态是依靠虚函数来实现,将一个类的一个成员函数写成虚函数,那么这个类对象就会自动生成一个虚表指针,虚表指针指向虚表,用于存放虚函数,一个派生类如果继承了一个含有虚函数的基类,那么这个派生类对象也会产生一个虚表指针,虚表指针指向虚表,如果派生类中重写了虚函数,那么我们可以认为,他将原来基类的虚表中的虚函数地址换成了派生类中重写的虚函数地址,在一个函数中调用时,我们就可以通过对象中的虚表来具体调用那个函数。

如果一个基类中有虚函数也有普通成员函数,那么普通成员函数是不会放在虚表中的,而是放在代码段中。

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

多态常考题目

1. 什么是多态?

2. 什么是重载、重写(覆盖)、重定义(隐藏)

3. 多态的实现原理?

4. inline函数可以是虚函数吗?
5. 静态成员可以是虚函数吗?
6. 构造函数可以是虚函数吗?
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
8. 对象访问普通函数快还是虚函数更快?
9. 虚函数表是在什么阶段生成的,存在哪的?
10. C++菱形继承的问题?虚继承的原理?
11. 什么是抽象类?抽象类的作用?
http://www.dtcms.com/a/348823.html

相关文章:

  • 【ABAP4】基本语法1
  • 第4章栈和队列:队列基础知识
  • pom.xml 标签整理各个标签的用途和含义
  • 蓝凌EKP产品:从 XML 到 JSON ——表单存储的性能优化实践
  • 前端漏洞(上)- CSRF漏洞
  • 强光干扰下误检率↓79%!陌讯动态决策算法在安全带检测的实战优化
  • Redis详解--基本篇
  • Linux 的 TCP 网络编程常用API
  • 网络流量分析——使用捕获和显示过滤器查询网络流量
  • 每天自动备份oracle
  • 关于熵减 - 力学单位和来源
  • 安装gitlab
  • C++ AOV 拓扑排序
  • pyecharts可视化图表-scatter:从入门到精通
  • 2020/12 JLPT听力原文 问题二 5番
  • 【网络运维】Shell 脚本编程:case 条件语句
  • 【大语言模型 18】Vision Transformer革命解析:图像理解的范式突破与架构创新
  • VsCode使用SFTP连接Linux
  • 油雾干扰下误报率↓76%!陌讯动态感知算法在卸油作业安全识别中的实战突破
  • Java:HashSet的使用
  • 【MySQL】CRUD基础详解
  • 基于 Redis + JWT 的跨系统身份共享方案
  • HTTP数据之旅:一个网络请求的完整冒险
  • Unity的Cursor.lockState
  • 油雾环境下漏检率↓79%!陌讯多模态检测算法在加油站智能巡检的落地实践
  • VMware Workstation 不可恢复错误:(vcpu-0)
  • 强反射场景漏检率↓89%!陌讯动态感知算法在护目镜合规检测的实战解析
  • 二叉树学习笔记
  • MyBatis 和 MyBatis-Plus对比
  • 【GEE+Python 实战】用 Sentinel-2 监测 2024 年研究区 NDVI 变化(附完整源码与避坑指南)