多态的使用和原理(c++详解)

server/2024/9/24 10:55:12/

一、多态的概念

        多态顾名思义就是多种形态,它分为编译时的多态(静态多态运行时的多态(动态多态,编译时多态(静态多态)就是函数重载,模板等,通过不同的参数来完成对不同的函数的调用(即生成多种形态)并且这个过程在编译阶段就已经完成

        动态多态是在运行时根据对象的实际类型来确定调用函数的哪个版本,完成不同的⾏为。

二、多态构成条件

1.虚函数

        在类成员函数的返回类型前面添加virtual关键字即为虚函数,注意:虚函数只能定义于普通成员函数,构造函数以及类外函数不能定义虚函数。

2.虚函数的重写

        虚函数重写指的是子类(派生类)对父类(基类)的重写。重写的要求是子类虚函数的返回值,函数名,参数类型必须和父类一模一样。但函数的实现逻辑不用相同。

        这里如果虚函数的重写没有加virtual,但是父类加了virtual那么子类依旧保持virtual的性质,也可构成重写。

        注意:对虚函数重写并没有要求缺省参数要相同,但在这里强烈建议把缺省参数设为相同值,要不然会给你带来很大的弊端和误导性。接下来我会讲到。

3.调用方式

        要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向派⽣类对象又能指向基类;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到。

⽐如火车买票这个操作,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票;军⼈买票时是优先买票,我们就可以用多态来实现,如下:

#include<iostream>
using namespace std;
class ticket
{
public:virtual void func(){cout << "普通票" << endl;}
private:
};
class student:public ticket
{
public:virtual void func(){cout << "学生票" << endl;}
private:
};
void fm(ticket& pu)
{pu.func();
}
int main()
{ticket tk;student stu;fm(tk);fm(stu);return 0;
}

4.override和final的修饰

        override关键字:因为多态的实现细节要求太多了特别是对虚函数的重写,因此C++11提供了override,可以帮助⽤⼾来检查虚函数的重写是否正确,需要放在重写的函数参数列表后面。

        final关键字:如果不想子类对该虚函数进行重写的话就可以使用final关键字,放在函数名后面。

5.协变 

        刚才我们说了虚函数的重写一定要满足子类虚函数的返回值,函数名,参数类型必须和父类相同。协变是个例外情况。当子类重写父类虚函数时,若与父类虚函数返回值类型不同,即父类虚函数返回父类对象的指针或引用,子类虚函数返回子类对象的指针或引用,此时称为协变。协变的实际意义并不⼤,所以我们了解⼀下即可。

代码示例:

class A{};
class B :public A{};
class ticket
{
public:virtual ticket* func()//ticket也可以是A{cout << "普通票" << endl;return this;}
private:
};
class student:public ticket
{
public:virtual student* func() override//student也可以是B{cout << "学生票" << endl;return this;}
private:
};

析构函数的重写

        父类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写

A* p1 = new A;
A* p2 = new B;
delete p1;
delete p2;

        假设B是A的子类上⾯的代码如果~A(),不加virtual,那么delete p2时只调⽤A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题。

三、纯虚函数和抽象类

        在虚函数的参数列表后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被子类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象如果子类继承后不重写纯虚函数,那么子类也是抽象类。纯虚函数某种程度上强制了子类重写虚函数,因为不重写实例化不出对象。

四、多态原理

        在分析对象的储存空间时,我们讲过对于同一个类实例化出的不同对象,这些对象使用的函数都是相同的,不同的是它们成员变量。所以每个对象只需要对成员变量进行储存,不用对成员函数进行储存,每一个对象使用的都是这个类的公共成员函数。

        c++为虚函数单独设立了一块区域来储存虚函数的地址,叫做虚表,而这块区域其实 就是一个函数指针数组。即用来储存函数指针的一个数组。那么父类和子类就各自有一个虚表,在对象实例化的时候就会隐含(隐含:类似于成员函数里面看不见的this指针一样)着一个指针——虚表指针,来指向虚表。

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

而虚表指针也是需要占用空间的大家可以自行地去运行一下以上代码,输出结果为:

a:4(或8,即32位与64位机器的区别)

b:1

        所以在调用对象的虚函数时就跟以什么类型的形式调用无关,而是跟这个对象实例化时具体类型有关。

#include<iostream>
using namespace std;
class ticket
{
public:virtual void func(){cout << "普通票" << endl;}
};
class student :public ticket
{
public:virtual void func() {cout << "学生票" << endl;}
};
int main()
{ticket* tk = new student;tk->func();return 0;
}

以上的输出结果是“学生票”。

注意:根据切片原理,子类可强制类型转化为父类,父类不能强制类型转化为子类。

五、练习

以下程序输出结果是什么()

  • A:A->0
  • B:B->1
  • C:A->1
  • D:B->0
  • E:编译出错
  • F:以上都不正确

        这里虽然B类的func成员没有写virtual关键字,但它是由A继承下来的依旧保留virtual的性质,然后因为重写并为要求参数的缺省值相同,所以这里构成函数的重写。再来看主函数main,p调用了test,而test是A的成员函数隐含了一个const   A* (this指针)的参数类型,p传到test函数满足多态,所以这里调用的是B的func。但是这里有个坑,该题的输出结果并不是“B->0”,而是“B->1”。

        要注意重写只是重写了函数的实现,也就是说实现多态的时候相当于调用的是父类的接口声明和子类的函数实现,而并不关心子类的函数接口声明。

        所以在我们自己写虚函数的时候,最好把缺省参数设为相同值,要不然会给你带来很大的误导性。


http://www.ppmy.cn/server/121322.html

相关文章

C#常用数据结构栈的介绍

定义 在C#中&#xff0c;Stack<T> 是一个后进先出&#xff08;LIFO&#xff0c;Last-In-First-Out&#xff09;集合类&#xff0c;位于System.Collections.Generic 命名空间中。Stack<T> 允许你将元素压入栈顶&#xff0c;并从栈顶弹出元素。 不难看出&#xff0c;…

Ansible-Playbook使用角色

在Ansible中使用角色是一种模块化和重用配置的方法。角色允许你定义一系列的任务、文件、模板和变量&#xff0c;这些可以在不同的主机和项目中重用。下面是一个简单的示例&#xff0c;展示如何在Ansible playbook中使用角色。 首先&#xff0c;确保你已经创建了角色目录结构。…

基于BeagleBone Black的网页LED控制功能(Flask+gpiod)

目录 项目介绍硬件介绍项目设计开发环境功能实现控制LED外设构建Webserver 功能展示项目总结 &#x1f449; 【Funpack3-5】基于BeagleBone Black的网页LED控制功能 &#x1f449; Github: EmbeddedCamerata/BBB_led_flask_web_control 项目介绍 基于 BeagleBoard Black 开发板…

鸿蒙​​​​​​保障应用开发安全的技术措施

应用开发安全是指在开发过程中嵌入安全能力&#xff0c;使应用程序从源头上安全可靠。 开发者是应用程序的创作者&#xff0c;合法的开发者是创作出安全、可靠应用的前提条件;为了保证应用开发者身份真实可信&#xff0c;鸿蒙通过开发者证书对应用进行签名&#xff0c;保证应用…

Element Plus如何使用在工程中,如何覆盖主题及常用主题(详细记录,值得一看)

Element Ui 2.x 升级到 Element Plus。 一、安装Element Plus 首先&#xff0c;你需要在你的 Vue 项目中安装 Element Plus。打开你的终端或命令提示符&#xff0c;并导航到你的项目目录&#xff0c;然后运行以下 npm 命令来安装 Element Plus&#xff1a; 输入&#xff1a;…

鸿蒙应用生态构建的核心目标

保护开发者和用户利益的同时维护整体系统的安全性&#xff0c;对生态构建者是至关重要的。以开发者为中心&#xff0c;构建端到端应用安全能力&#xff0c;保护应用自身安全、运行时安全&#xff0c;保障开发者权益&#xff0c;是鸿蒙应用生态构建的核心目标。 应用生命周期主要…

大数据产业核心环节有哪些?哪里可以找到完整的大数据产业分析?

▶大数据产业前景开阔 大数据产业正站在数字化时代前沿&#xff0c;预计在未来几年将实现显著增长和扩展。目前&#xff0c;中国大数据产业规模在2021年已达到1.3万亿元&#xff0c;并在2022年增长至1.6万亿元&#xff0c;预计到2025年将突破3万亿元大关&#xff0c;年均复合增…

使用Conda配置python环境到Pycharm------Window小白版

使用Conda配置python环境到Pycharm 一、Conda安装和环境配置1.1 安装Conda软件1.2 判断是否安装成功1.3 创建Conda虚拟环境 二、 pycharm的安装2.1 Pycharm使用手册2.2 安装pycharm 三、 pycharm导入Conda环境 一、Conda安装和环境配置 anaconda官网 1.1 安装Conda软件 运行…