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

【C++】12.多态(超详解)

一、多态的概念

 1.什么是多态?        

通俗点说就是一个事物有多种形态,就是“—个接口,多种实现”,也就是说调用同一个接口,而产生不同的行为。

2.多态的分类

多态分为编译时多态(也叫静态多态)运行时多态(也叫动态多态)

编译时多态(静态多态)主要是函数重载函数模板。

他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。

运⾏时多态

就是去完成某个⾏为(函数),传不同的对象就会完成不同的⾏为,就达到多种形态。

比如:买车票这件事,有普通票,有学生票半价军人优先买票等等。

二、多态的实现

1.两个必要条件(★)

  • 必须是基类指针或者引用调⽤虚函数
  • 被调⽤的函数必须是虚函数,并且虚函数完成了重写/覆盖

2.虚函数

虚函数,就是在成员函数前面加上virtual关键字 。

class A
{
public://虚函数virtual void fun(){//...}
};

注意:

  1. 只能是成员函数,不是函数!不能在类外声明和定义
  2. 静态成员函数不能设置为虚函数,因为没有 this 指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

3.虚函数的重写/覆盖

子类中有⼀个跟父类完全相同的虚函数(返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了父类的虚函数。
子类的虚函数在不加virtual关键字时,虽然也可以构成重写,但为了更规范最好还是加上。
class A
{
public://虚函数virtual void fun(){//...}
};
class B : public A
{
public://虚函数重写virtual void fun(){//...}
};

4.一个选择题目

正确答案是B
可能很多人选的是D,取val=0,正确的流程是这样的
p->test() → 执行 A::test 的函数体 → A::test 中调用 func() → func 动态绑定到 B::func,且默认参数取 A::func 的 val = 1 → 执行 B::func(1) → 输出 B->1
这里val很可能出错,默认参数是 “编译期静态绑定”—— 默认参数的值,由调用该函数的指针决定。

在 A::test 中调用 func() 时,this 的类型是 A*(因为 test 是 A 的成员函数),所以默认参数由 A 类的 func 决定,即 val = 1

5.虚函数重写的两个例外

1) 协变(了解)

协变,指的是派生类重写基类虚函数时,与基类虚函数返回值类型不同,此时需要满足:基类虚函数的返回值基类对象的指针或引用派生类虚函数的返回值派生类对象的指针或引用

2)析构函数的重写

如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加 virtual 关键字,都与父类的析构函数构成重写,虽然函数名不同。看似违背重写规则,其实不然,因为实际上编译器统一将析构函数的名称处理为 destructor。这样函数名实际就是相同的。构成重写。
class A
{
public:~A(){cout << "~A()" << endl;}
};
class B : public A {
public:~B(){cout << "~B()->delete:"<<_p<< endl;delete _p;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;}

这个程序父类析构函数没有加virtual,运行结果是两个“ ~A() ”

我们创建了分别为A、B类型的两个对象,将它们的地址赋值给两个A*指针p1和p2。指针p1,它所指向的对象是A类型,对象销毁时直接调用A类型的析构函数;指针p2,它所指向的对象是B类型,B是A的子类,其在销毁时首先要调用子类析构,然后调用父类析构,但是程序并没有调用子类析构,导致内存泄漏。这是由于指向它的指针是A*类型,所以只会调用A的析构函数。

所以我们父类的析构函数最好设计成虚函数。

6.override 和 final关键字

C++对虚函数重写的要求较为严格,为防止因为函数名写错、参数不匹配等疏忽导致无法构成重写,这类错误在编译阶段不会报出,直到程序运行未达预期结果时才来调试。

C++11 提供了 override 关键字来帮助检测是否成功重写

class B : public A
{
public:virtual void Buy() override{//...}
};

 final,修饰虚函数,表示该虚函数不能继承;

class A
{
public:virtual void Buy() final {// ... }
};

7.重载、重写、隐藏的对比

三、纯虚函数和抽象类

在虚函数的后⾯写上 =0 ,就成了纯虚函数。包含纯虚函数的类叫做抽象类
  • 纯虚函数不需要定义实现
  • 抽象类不能实例化出对象
  • 如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。

class A
{
public://纯虚函数virtual void fun() = 0;
};

四、多态的实现原理

1.函数虚表

先看这段代码,请问类的大小是多少?

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
protected:int _a = 1;char _ch = 'x';
};
int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

按照内存对齐,我们肯定会认为是 8,思路:int 类型_a 偏移量从 0 开始排到 3,然后放的是 char类型的_ch,然后输出的字节应该是成员最大对齐数的整数倍也就是 8,但是事实确实 12,为什么呢?这里就涉及到了虚表的概念。

调试一下可以看到,b里面除了成员变量_a和_ch外还有一个_vfptr_vfptr叫做虚函数表指针是一个指针类型,在x32环境下,它的大小是4个字节,8+4正好等于我们得到的答案12。

虚函数表就是存放虚函数地址的一个表。【0】就是func1的地址,实际上,对于一个有虚函数的类至少都有一个虚函数表指针,该指针指向一个虚函数表,虚函数表简称虚表,虚函数表指针简称虚表指针!

注意:

  • 虚表里面存的不是虚函数,而是虚函数的地址,表里只有虚函数。
  • 虚表的本质就是一个存虚函数指针的指针数组。
  • 虚表是在编译阶段就生成的。
  • 虚函数跟普通函数一样存在代码段。
  • 如果派生类重写了基类的虚函数,那么派生类的虚函数表中对应的虚函数地址就会被覆盖成重写的新虚函数地址。

如果子类重写了父类的虚函数,子类里虚函数表存放的指针也会对应改变。

2. 动态绑定和静态绑定

根据多态的实现原理,编译时多态与运行时多态的主要区别在于绑定时机不同,由此引出静态绑定与动态绑定的概念:

静态绑定发生在程序编译期间,确定程序行为,例如函数重载、函数模板,对应编译时多态

动态绑定则在程序运行期间,根据实际对象类型确定具体行为并调用特定函数,对应运行时多态

http://www.dtcms.com/a/469277.html

相关文章:

  • 【Linux操作系统】进程控制
  • 做实验流程图的网站广州免费核酸采集点时间
  • 网站网页设计公司电子商务公司logo
  • 潮玩盲盒抽赏小程序玩法拆解:不同视角下的增长逻辑分析
  • 使用Milvus和DeepSeek构建RAG demo
  • WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
  • wordpress 新浪微博百度网站优化外包
  • Oracle LOB使用入门和简单使用,提供学习用的测试用例!
  • Java版旅游系统/文旅系统/旅游助手/旅游攻略/公众号/小程序/app全套源码
  • 线程2---javaEE(校招)
  • [创业之路-687]:华为“1+8+N”战略以及其背后的技术栈、商业逻辑。
  • 基于大语言模型(LLM)的城市时间、空间与情感交织分析:面向智能城市的情感动态预测与空间优化
  • 眼控交互:ErgoLAB新一代人机交互方式
  • 数字货币众筹网站开发如何做高网站的浏览量
  • 网站服务器频繁掉线的主要原因是什么
  • codeigniter换服务器之后,会员登录之后又跳回登录页面的解决方法
  • VS Code 的 SSH 密钥,并将其安全地添加到服务器
  • 香港服务器速度快慢受何影响?
  • 服务器相关:什么是 alios. centos. cuda. cuda tookit. gcc. cudann. pytorch.
  • K8S(五)—— K8s中YAML文件全方位解析:语法、案例、Port详解与快速编写技巧
  • 企业网站注销流程做企业网站需要服务器么
  • k8s存储juicefs简介
  • Ansible模块介绍(接上段)
  • 河南省建设厅网站官网重庆seo务
  • 【开题答辩全过程】以 北京房屋租赁数据分析与可视化为例,包含答辩的问题和答案
  • 什么身一什么网站建设网站开发毕设任务书
  • 【八股消消乐】手撕分布式协议和算法(基础篇)
  • Databend 九月月报:自增列 AUTOINCREMENT 与行级安全
  • Zenlayer 推出分布式推理平台,加速 AI 创新全球落地
  • 01-iptables防火墙安全