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

C++多态介绍

C++多态

简介

多态是C++面向对象的核心特征之一,核心思想是一个接口,多种实现,即同一操作作用于不同对象时,会产生不同的行为。多态性极大提升了代码的灵活性和可扩展性,时设计可维护系统的关键工具

向上转型

在继续了解多态之前我们先引入一个新概念转型,转型我们主要了解一下向上转型

#include <iostream>using namespace std;​class A{public:A(){cout << "A" << endl;}​void print(){cout << "print A" << endl;}};​class B : public A{public:B(){cout << "B" << endl;}​void print(){cout << "print B" << endl;}};​int main(){A a;B b;​//向上转型,将派生类赋值给基类a = b;//向下转型,引发报错//b = a;​return 0;}

输出结果

至于为什么只能向上转型而不能向下转型,大致可以这样解释

如图所示,派生类中包含了基类,但是基类中并不一定包含派生类,所以可以直接使用派生类类元素为基类元素赋值,这里就引出了C++中的类型兼容规则

类型兼容规则

类型兼容规则是指在需要基类对象的任何地方,都可以使用共有派生类对象来代替。通过公有继承,派生类得到了基类中除构造函数外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,共有派生类都可以解决。

类型兼容规则中所指的替代包括一下情况:

  • 子类对象可以直接赋值给父类对象使用

  • 子类对象可以直接赋值给父类对象

  • 子类对象可以直接初始化父类对象

  • 父类指针可以直接指向子类对象

  • 父类引用可以直接引用子类对象

在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员

类型兼容规则是多态性的重要基础之一

虚函数

#include <iostream>using namespace std;​class A{public:A(){cout << "A" << endl;}​void print(){cout << "print A" << endl;}};​class B : public A{public:B(){cout << "B" << endl;}​void print(){cout << "print B" << endl;}};​int main(){A a;B b;​A * pa = new A();B * pb = new B();​a.print();//类型赋值之后调用的是基类的方法a = b;a.print();//指针赋值之后仍然调用的是基类的方法pa->print();pa = pb;pa->print();​return 0;}

输出结果

上述代码中我们发现当使用派生类为基类赋值之后调用方法时编译器默认使用的还是基类的方法,此时当我们在A类中的print函数前加上virtual关键字之后再次运行程序,结果如下

此时我们发现当修改后使用指针赋值时编译器调用的就是派生类内的方法了,使用virtual关键字修饰的函数我们称之为虚函数,虚函数允许派生类重写基类函数,并在运行时根据对象实际类型调用对应版本,而非编译时的类型,因此虚函数可以通过基类的指针调用派生类的方法

函数遮蔽
#include <iostream>using namespace std;​class A{public:A(){cout << "A" << endl;}​virtual void print(){cout << "print A" << endl;}};​class B : public A{public:B(){cout << "B" << endl;}​//修改函数参数列表,遮蔽被破坏void print(int num){cout << "print B" << endl;}};​int main(){A a;B b;​A * pa = new A();B * pb = new B();pa->print();pa = pb;pa->print();​return 0;}

输出结果

此时我们观察程序运行结果发现当函数遮蔽改变之后虚函数就无法生效

函数重载

多态的意义为使用同一接口,传递不同实例,执行不同操作,这种特性运用在函数中也能发挥重要作用,此时程序中含有多个类,恰巧每个类中都有一个重名函数,每个重名函数的功能又各不相同,因此我们的目标就是编写一个函数,希望能够通过这一个函数来调用不同类内的重名函数,以简化操作

#include <iostream>using namespace std;​class A{public:virtual void print(){cout << "print A" << endl;}};​class B : public A{public:void print(){cout << "print B" << endl;}};​void test(A a){a.print();}​int main(){A a;B b;​test(a);test(b);​return 0;}

运行结果:

在上述代码中我们尝试通过test函数来实现向函数内传递不同参数时调用不同类内的函数的目标,但是根据输出结果我们发现结果并不理想,在此基础上对函数进行改进之后程序如下

#include <iostream>using namespace std;​class A{public:virtual void print(){cout << "print A" << endl;}};​class B : public A{public:void print(){cout << "print B" << endl;}};​void test(A *a){a->print();}​int main(){A a;B b;​test(&a);test(&b);​return 0;}

输出结果

此时程序输出了我们想要的结果,对函数的改进为从原来的传值变成了传地址。由此可见当程序内想要调用某个类内的函数时只需要在main函数中实例化一个该类的对象,然后再将该对象取值后传入目标函数即可,这样就实现了一个函数解决多个问题

上述操作完成后我们发现用一个基类对象接受子类无法发生多态,只有基类指针接受子类对象的地址或指针,才会发生多态

总结

综合上述一系列操作我们不难推理出实现多态的前提

  1. 父类中有虚函数

  2. 子类覆写父类中的虚函数

  3. 通过已被子类对象赋值的父类指针或引用,调用共用接口

多态的定义:多态是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的相应

多态的作用:能增加程序的灵活性,可以减轻系统升级,维护,调试的工作量和时间复杂度,提高代码扩展性

  • 注意事项

    1. 基类中用virtual声明成员函数为虚函数。类外实现虚函数时,不必再加virtual

    2. 再派生类中重新定义此函数成为覆写,要求函数名,返回值类型,函数参数个数以及类型全部匹配

    3. 为了避免再派生类中写错虚函数,可在派生类的虚函数中添加override修饰,确保该函数并复写来自基类的虚函数

    对于override关键字

    #include <iostream>using namespace std;​class A{public:virtual void print()  {cout << "print A" << endl;}};​class B : public A{public://此处使用override关键字之后,编译会引发报错void print(int num) override{cout << "print B" << endl;}};​void test(A *a){a->print();}​int main(){A a;B b;​test(&a);test(&b);​return 0;}

    override关键字通过编译时检查和显示意图声明,有效减少继承体系中的隐藏错误,同时提高代码的可读性与维护性,建议在所有的虚函数重写场景中强制使用override

  • 限制:

    1. 只有类的成员函数才能声明为虚函数

    2. 静态成员函数不能是虚函数

    3. 内联函数不能是虚函数

    4. 构造函数不能是虚函数

    5. 析构函数可以是虚函数且通常声明为虚函数

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

相关文章:

  • sunset: sunrise
  • 安全多方计算(MPC):技术原理、典型应用与 Python 工程实现详解
  • POLAR 社区交流平台 PRD v1.0
  • DDR5 介绍
  • 关于PXIe工控机的网速问题XH-PXIe7313万兆网卡
  • 【LeetCode每日一题】21. 合并两个有序链表 2. 两数相加
  • Linux三剑客grep-sed-awk
  • # `std::basic_istream`总结
  • 从零到一:使用Flask构建“我的笔记”网站
  • Elasticsearch面试精讲 Day 2:索引、文档与映射机制
  • 如何在 Jenkins Docker 容器中切换到 root 用户并解决权限问题
  • WPF和WinFrom区别
  • WPF中的ref和out
  • 基于Ubuntu本地GitLab 搭建 Git 服务器
  • 小迪安全v2023学习笔记(七十四讲)—— 验证机制篇验证码绕过思路SRC挖掘演示
  • web渗透ASP.NET(Webform)反序列化漏洞
  • SpringBoot整合Actuator实现健康检查
  • windows系统中安装zip版本mysql,配置环境
  • Spring Cloud Gateway 网关(五)
  • 电子战:Maritime SIGINT Architecture Technical Standards Handbook
  • 系统分析师考试大纲新旧版本深度分析与备考策略
  • 拼团小程序源码分享拼团余额提现小程序定制教程开发源码二开
  • 深入理解 RabbitMQ:从底层原理到实战落地的全维度指南
  • (纯新手教学)计算机视觉(opencv)实战十——轮廓特征(轮廓面积、 轮廓周长、外接圆与外接矩形)
  • 在Kotlin中安全的管理资源
  • 突破视界的边界:16公里远距离无人机图传模块全面解析
  • 神经网络激活函数:从ReLU到前沿SwiGLU
  • 华为对“业务对象”是怎样定义与应用的?
  • Linux网络服务发现在VPS云服务器自动化配置的关键技术与实践
  • 运维底线:一场关于原则与妥协的思辨