C++ 多态

embedded/2024/10/22 15:36:40/

C/C++总述:Study C/C++-CSDN博客 

目录

多态概念

多态分类

多态实现 

虚函数&虚函数表

虚函数的重写(覆盖)

多态的构成条件 

虚函数重写的两个特例

协变

析构

关键字final和override(C++11)

抽象类

纯虚函数

实现继承与接口继承


多态概念

对于同一个行为对于不同的对象,有不同的表现

eg:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。 

多态分类

多态在 c++ 中分为 静态多态动态多态,也称 编译期多态 和 运行时多态 。

静态多态:是基于 函数重载 与 泛型编程 实现的。

动态多态:是基于虚函数实现的。

多态实现 

虚函数&虚函数表

  • 虚函数:即被 virtual 修饰的类成员函数称为虚函数。
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
  • 虚函数表本质是一个存虚函数指针 的 指针数组,一般情况这个数组最后面放了一个nullptr。
  • 虚函数表:虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚函数表中。
  • 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
  • 注意:对象的前四个字节就是虚表的地址,虚表存放在常量区(虚表是不能人为更改的)

虚函数的重写(覆盖)

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

多态的构成条件 

  1. 必须通过 基类的指针 来 引用 调用虚函数
  2. 被调用的函数 必须是虚函数,且 派生类必须对基类的虚函数进行重写
class Person {               //多态条件2:被调用的函数 必须是虚函数
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:                      //多态条件2:派生类必须对基类的虚函数进行重写virtual void BuyTicket() { cout << "买票-半价" << endl; }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*//*void BuyTicket() { cout << "买票-半价" << endl; }*/
};void Func(Person& p)         //多态条件1:必须通过基类的指针来“引用”调用虚函数
{ p.BuyTicket(); 
}int main()
{Person ps;Student st;Func(ps);     //输出:买票-全价Func(st);     //输出:买票-半价return 0;
}

虚函数重写的两个特例

协变

派生类重写基类虚函数时 ,与基类虚函数 返回值类型不同 。即如下代码所示:【基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时】,称为协变。

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关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。 虽然函数名不相同~Person() ,~Student() ,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor

class Person 
{
public:                  //基类的析构函数为虚函数virtual ~Person() {cout << "~Person()" << endl;}
};class Student : public Person 
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};
//只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}

关键字final和override(C++11)

final:修饰虚函数,使虚函数不能被覆盖。(加在父类中)
           final修饰类时,表示这个类不能被继承。

override:修饰虚函数,检测是否正确覆盖。(加在子类中)

class A
{
public:virtual void test()final   //使该虚函数不能覆盖,若被覆盖就报错{}
}class B: public A
{
public:virtual void test()override //检查虚函数是否正确覆盖,若未覆盖就报错{}
}

抽象类

含纯虚函数的类,称为抽象类

纯虚函数

  • 在虚函数后面加上 =0,则这个函数为纯虚函数。
  • 包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。
  • 子类继承抽象类后也不能实例化出对象,只有覆盖(重写)纯虚函数,子类才能实例化出对象。
  • 纯虚函数强制子类必须覆盖函数,更加体现出接口继承。
  • 纯虚函数不需要实现功能,只需要声明(重写时再实现功能)
class Car       // 抽象类
{
public:virtual void Drive() = 0;         //在虚函数的后面写上 =0,这个函数为 纯虚函数
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;  //只有 重写纯虚函数 ,派生类才能实例化出对象}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

实现继承与接口继承

  • 普通函数的继承是一种 实现继承 ,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
  • 虚函数的继承是一种 接口继承 ,派生类继承的是基类虚函数的接口, 目的是为了重写,达成多态 ,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

http://www.ppmy.cn/embedded/24947.html

相关文章

vue项目,普通js文件添加全局变量

在 Vue 项目中&#xff0c;如果你需要在普通的 JavaScript 文件中使用全局变量&#xff0c;并且没有 this 上下文&#xff08;比如在一个非 Vue 组件的 JavaScript 模块中&#xff09;&#xff0c;你可以通过几种不同的方式来定义和使用这些全局变量。 1. 使用全局变量 你可以…

[docker] 多容器项目 - PHP+MySQL+Nginx+utility containers

[docker] 多容器项目 - PHPMySQLNginxutility containers 这个项目总共会配置 6 个容器&#xff0c;主要还是学习一下 docker 的使用和配置&#xff0c;目标是&#xff1a; 本机不安装 PHP、Nginx 安装部分全都交给 docker 容器实现 可以运行一个 Laravel 网页项目 修改本机…

三维SDMTSP:GWO灰狼优化算法求解三维单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)

一、单仓库多旅行商问题 多旅行商问题&#xff08;Multiple Traveling Salesman Problem, MTSP&#xff09;是著名的旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;的延伸&#xff0c;多旅行商问题定义为&#xff1a;给定一个&#x1d45b;座城市的城市集…

力扣练习4.29-30

86. 分隔链表 解题思路&#xff1a;设置两个链表&#xff0c;分别装小于x和>x的节点&#xff0c;最后将两个链表拼接。 步骤&#xff1a; 1.初始化两个新链表的头结点和指针节点&#xff0c;初始化链表的指针节点 2.遍历变量&#xff0c;如果是小于x&#xff0c;就将第一个…

使用 LooperPrinter 监控 Android 应用的卡顿

在 Android 开发中&#xff0c;主线程&#xff08;UI线程&#xff09;的卡顿直接影响用户体验。LooperPrinter 是一种有效的工具&#xff0c;可以帮助我们监测和识别这些卡顿。下面是如何实现 LooperPrinter 监控的详细步骤和相应的 Kotlin 代码示例。 步骤 1: 创建自定义的 P…

asyncio异步编程(三)

1.异步迭代器 迭代器&#xff1a;内部实现__iter__()和__next__()方法的对象。 可迭代对象&#xff1a;内部实现__iter__()方法&#xff0c;并且可以返回迭代器的对象。 异步迭代器&#xff1a;实现__aiter__()和__anext__()方法的对象。 异步可迭代对象&#xff1a;内部实…

AI多模态平台新生之旅 如何打造好一个AI多模态平台

如何打造好一个AI多模态平台&#xff1f;在这篇文章里&#xff0c;作者结合实际案例&#xff0c;从三个维度分享了AI多模态平台的设计思路&#xff0c;我们不妨来看一下。 AI 多模态平台设计一个具有挑战性但又充满机遇的领域。我们的多模态 AI 平台是一个集成了图片生成、视频…

32个centos常见的命令使用

CentOS 是一个基于 Red Hat Enterprise Linux (RHEL) 的免费企业级操作系统。以下是一些基本的 CentOS 命令&#xff0c;用于日常管理和操作&#xff1a; 这些命令是Linux系统中常用的命令行工具&#xff0c;用于执行各种系统管理任务。下面是对每个命令的详细介绍&#xff0c…