谈谈C++多态的基本使用和总结

2023-05-16

前言

文章目录

  • 前言
  • 1. 多态的理解
  • 2. 构成多态的条件
  • 3. 虚函数
  • 4. 虚函数的重写(覆盖)
  • 5.多态调用虚函数的规则
  • 6. 析构函数的重写(基类与派生类析构函数的名字不同)
  • 7. 设计一个不能被继承的基类
  • 8. final关键字和override 关键字
  • 9. 抽象类
  • 10. 接口继承和实现继承区别

1. 多态的理解

简单的说:多态就是根据你传入的不同的对象,去做同一样的事情,会有不同的结果和行为的表现产生;

在C++中,传入不同的对象意思:给父类传入不同的子类;

去做同一样的事意思是:父类指针或者引用调用了同一样的虚函数;

产生不同的结果意思是:调用同样的虚函数,但是结果却不一样;


2. 构成多态的条件

继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数;
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写;


3. 虚函数

虚函数:即被virtual修饰的类非静态成员函数称为虚函数。(静态成员不可以作为虚函数,全局函数也不行)
虚函数的重写;

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

4. 虚函数的重写(覆盖)

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

class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后
基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/ 
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }

5.多态调用虚函数的规则

构成多态,跟父类的指针或者引用的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 – 跟对象有关;
不构成多态,调用就是父类指针或者引用类型的函数 – 跟类型有关;


#include<iostream>
using namespace std;
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void test1(Person& p)
{
	p.BuyTicket();
}
void test2(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	//父类引用调用虚函数,子类重写虚函数,构成多态;那么调用函数就传入的对象有关
	//这里传入的对象ps的类型为 Person 所以调用Person的虚函数;
	Person& p1 = ps;
	p1.BuyTicket();
	//父类引用调用虚函数,且子类重写父类虚函数,构成多态,那么调用函数就和传入的对象有关;
	//这里传入的对象是 Student,所以调用的是Student的虚函数;
	Person& p2 = st;
	p2.BuyTicket();

	//指针的分析和引用一致
	Person* p3 = &ps; //构成多态
	p3->BuyTicket();

	Person* p4 = &st; //构成多态
	p4->BuyTicket();

	getchar();
	return 0;
}

符合预期:
在这里插入图片描述


测试:假如不是父类指针或者引用调用虚函数会发生什么;

去除条件1,不是父类指针或引用而是父类对象去调用虚函数,是否会发生多态?

  1. 假如 子类对象赋值给父类对象会不会发生多态?肯定不会?所以父类对象调用虚函数,只看父类的类型;
	//子类对象赋值给父类对象,没有父类指针或引用指向子类对象,这不会构成多态,所以调用虚函数
	//只看p5的类型;
	Person p5 = st;
	p5.BuyTicket(); //调用的是父类的虚函数,不构成多态
  1. 即使有父类的指针或引用调用了虚函数,假如子类没有重写虚函数,依旧不会发生多态,没有多态那么,父类指针或引用去调用虚函数时候,都是看父类的类型的;
    (ps:我们可以修改父类和子类虚函数,只要不构成重写,就可以验证我上面的结论)

6. 析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成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; // 编译器默认转化了调用虚构函数的形式 p1->destructor()      
	delete p2; // 编译器默认转化了调用虚构函数的形式 p2->destructor(),
				//由于构成多态,所以会正确的嗲用传入对象是子类的虚析构函数
	return 0;
}

构成多态,正确的调用了子类的析构函数;
这也是唯一的场景:是析构函数,需要写成虚析构的形式;
在这里插入图片描述


没有发生多态时候,不管你的父类还是子类是否为虚析构,都可以正确的调用析构函数
在这里插入图片描述


非虚析构函数,依旧可以正确释放
在这里插入图片描述


上面两种原因很简单:继承体系中,子类对象指向析构时候,本身就会现执行子类再执行父类,和你的析构函数是否为虚函数没关系;


所以说析构函数是否需要写成虚析构函数,首先得发生多态行为,才需要父类析构写成虚析构,这也才可以保证delete 父类指针时候,能够去调用子类的析构函数;


本质我们 delete 父类指针,就是为了析构子类对象的,因为父类指针指向了子类对象,我们希望的就是做这样的事;delete父类指针,为了析构子类对象,那么就需要发生多态,这个行为才会产生;有多态,就必须,子类重写父类的虚析构函数;


7. 设计一个不能被继承的基类

在C++98时候,有一种方式可以设计不被继承:那就是设计基类的构造函数为私有的

#include<iostream>
using namespace std;


class Base{
private:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
private:
	int _b;
};
class Derive :Base{
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}

这种方式:当我屏蔽了:Derive son这句代码,也就是,不创建子类对象时候,上面的代码编译是可以通过的;

但是当我写上了Derive son也就是创建子类对象时候,那么就会编译报错:原因就是基类的构造函数为私有,派生类创建对象时候,也是执行Derive son代码时候,需要先调用父类的构造函数,但是由于父类的构造函数时私有的,不可以被调用,所以就会编译报错了;

设计基类的构造函数是私有的:这也就解决了一个类不可以被继承的情况;


8. final关键字和override 关键字

C++11通过新增一个关键字final:解决了基类不可以被继承问题, 也就是不需要设计基类的构造函数为私有的;
具体操作就是:在声明基类的后面加多一个关键字 final就可以达到效果;

class Base final{//final关键字修饰,使得该类不可以被继承
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
private:
	int _b;
};
class Derive :public Base{
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}

在这里插入图片描述


final关键还可以修饰基类的虚函数,目的就是为了使得基类的虚函数不被子类重写;

class Base {
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
	virtual void fun() final{ //使得该虚函数不能够被重写
		cout << "Base::fun()被调用" << endl;
	}
private:
	int _b;
};

class Derive :public Base{
	public
	virtual void fun(){ //由于基类这个函数fun被final修饰,所以派生类不可以重写
		cout << "Derive::fun()被调用" << endl;
	}
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}

在这里插入图片描述


c++11还有一个关键字override,它是修饰派生类的虚函数,目的为的是:派生类必须重写基类虚函数,如果基类没有该虚函数,那么就会报错,或者派生类重写错乱基类虚函数,那么就会报错;

反正就是在基类中找不到你要重写的虚函数,那么就会直接报错;

所以说:override 有检查机制,帮你检查你的派生类是否正确的重写了基类的虚函数;


class Base {
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
	virtual void fun() { 
		cout << "Base::fun()被调用" << endl;
	}
private:
	int _b;
};

class Derive :public Base{
public:
	virtual void fun1() override{ 
		cout << "Derive::fun()被调用" << endl;
	}
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}

在这里插入图片描述


9. 抽象类

3.1 概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。


由于抽象类不能实例化对象,所以虚函数实现也是没有任何意义的,所以不会有人实现纯虚函数;
纯虚函数是可以实现的,但是就是没有意义罢了;


在这里插入图片描述

class Car
{
public:
	virtual void Drive() = 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();
}

有纯虚函数,只能用父类指针或引用指向子类对象了,不可能再指向父类对象,因为父类对象是不可以被创建的;

10. 接口继承和实现继承区别

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


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

谈谈C++多态的基本使用和总结 的相关文章

随机推荐

  • 对uC/OS-III时钟节拍运转机制的一点理解

    目录 如何产生时基信号系统时钟中断管理时基任务时基列表更新写在最后 我在初学uC OS III的时候 xff0c 时基产生后到底是如何去驱动操作系统运转的 xff0c 对于这个问题一直有很多疑问 xff0c 最后读了手册并且仔细推敲源码后终
  • Altium Designer 常见的问题和解决办法,常更!

    本人的Altium Designer 版本是18 1 8 问题1 xff1a AD左上角有一个坐标显示框 xff0c 今天按键盘不小心弄没了 xff0c 然后弄出来后发现又会随着鼠标移动跟着移动 xff0c 不再固定到左上角了 相关解释 x
  • 推荐一款串口调试助手(win10,无广告,功能齐全,操作简单)

    目录 1 安装2 打开软件3 测试功能4 使用脚本功能5 波形显示功能6 升级专业版 作为一个搞嵌入式软件的 xff0c 串口是我们经常需要使用的一个外设资源 xff0c 对应的我们在调试的时候就需要用到一些工具 xff0c 本人也用过目前
  • STM32L1单片机的ADC必须开启HSI才能工作

    之前玩过F1和F4的板子 xff0c 这段时间 xff0c 接个项目需要用低功耗 xff0c 所以就整了STM32L151单片机 xff0c 然后今天在写ADC的时候 xff0c 发现了一个问题 xff0c 就是STM32L151MCU必须
  • 深入理解Linux文件系统与日志文件,快来看!

    目录 一 inode与block1 1 inode和block概述1 2 inode的内容 1 11 3 inode的内容 1 21 4 inode的内容 1 31 5 inode的号码1 6 文件存储小结1 7 inode的大小1 8 i
  • AT 指令集详解

    AT 指令集详解 1 AT的历史与应用1 1 AT的历史1 2 AT的优点1 3 AT命令与ppp协议的关系 2 AT的命令格式3 DCE的状态切换与AT的命令拨号流程3 1 初始化DCE的Modem设备3 2 拨号连接3 3 数据传输及处
  • 幽暗的廊桥,热烈的遗梦:火了25年的中年“杰克苏”童话

    廊桥是一种有遮盖的桥梁 路人经过一段幽暗隐蔽的空间后 xff0c 已身在彼岸 廊桥遗梦 的作者罗伯特 詹姆斯 沃勒于2017年3月10日凌晨 xff0c 在美国德克萨斯州弗雷德里克斯堡的家中去世 xff0c 享年77岁 作者当年如有神助 x
  • STM32L152 实现电源电压采集 基于HAL库实现

    CudeMX中对相关ADC的配置 这里用ADC1的通道17 keil中 自己编写 ADC Getvalue的值的函数 在主函数中调用 在debug模式下 可以在watch窗口中看见相关值 单位为mv
  • 基本共射放大电路的动态分析以及放大电路Q点的稳定

    求解静态 xff1a 直流通路 xff1a VCC 61 Ubeq 43 Ibq Rb gt Ibq gt Ieq 2 动态分析 xff1a 交流通路 xff1a H参数等效 xff1a 在放大过程中 xff1a 动态的Ube秉承动态的Ib
  • FreeRTOS 创建任务

    用已经移植好的工程模板 创建一个简单的任务 xff08 电量LED灯 xff09 1 硬件初始化 需要用到开发板上的LED灯小江LED灯相关的函数初始化 xff0c 在main c中创建一个Init xff08 xff09 函数 xff0c
  • 差分放大电路及动态分析

    由温度引起的零点漂移成为温漂 xff0c 引入负反馈 加入Re电阻 从抑制温度漂移的角度来说 希望Re越大越好 但是会导致放大倍数变小 加一个镜像的电路可以让两边一起浮动 但是两个Re可以共用一个 于是改成下面的 形式 Re电阻现在的作用变
  • FreeRTOS的启动流程

    目前的RTOS中有两种比较流行的启动方式 万事俱备 只欠东风 xff1a 在main函数中将硬件初始化 xff0c rtos系统初始化 xff0c 所有任务的创建都完成 再启动RTOS的调度器 xff08 在刚刚创建的的任务中选择一个优先级
  • FreeRTOS--消息队列

    队列 xff1a 队列又称为消息队列 xff0c 是一种用于任务间通信的数据结构 xff0c 是一种异步通信方式 xff0c 实现接收来自其他任务或者中断的不固定长度的消息 xff0c 任务能够从队列中读取消息 xff0c 当队列中消息是空
  • FreeRTOS--信号量

    信号量的基本概念 xff1a 信号量是一种实现任务间通信的机制 xff0c 可以实现任务之间同步或者临界资源的互斥访问 xff0c 常用于协助一组相互竞争的任务来访问临界资源 信号量是一个非负整数 xff0c 所以获取它的任务都会将整数减一
  • FreeRTOS-互斥信号量

    互斥量又称为互斥信号量 xff0c 本质是一种特殊的二值信号量 xff0c 支持互斥量所有权 xff0c 递归访问 xff0c 以及防止优先翻转的特性 xff0c 用于实现对临界资源的独占式处理 任意时刻互斥量的状态只有两种 xff0c 开
  • FreeRTOS--软件定时器

    定时器 xff1a 从指定的时刻开始 经过一个指定时间 触发一个超时事件 用户自定义定时器的周期和频率 硬件定时器 xff1a 芯片本身提供的定时功能 一般是外部晶振提供给芯片输入时钟 芯片向软件模块一直配置寄存器 接收控制输入 到达设定时
  • FreeRTOS--任务通知

    任务通知 xff1a 每个任务有一个32位的通知值在大多数情况下 xff0c 任务通知可以替代二值信号量 计数信号量 事件组 xff0c 也可以替代长度为 1 的队列 xff08 可以保存一个 32位整数或指针值 xff09 使用任务通知比
  • 模拟实现strchr,strrchr,strstr,strrstr函数

    模拟实现strchr strnchr strstr strrstr函数 strchr 查找字符串s中首次出现字符c的位置 xff0c 返回首次出现c的位置的指针 xff0c 如果s中不存在c则返回NULL include lt stdio
  • FreeRTOS--内存管理

    FreeRTOS操作系统将内核和内存管理分开实现 xff0c 操作系统内核仅规定了必要的内存管理函数原型 不关心内存管理函数是如何实现的 所以在 FreeRTOS 中提供了多种内存分配算法 xff08 分配策略 xff09 xff0c 但是
  • 谈谈C++多态的基本使用和总结

    前言 文章目录 前言1 多态的理解2 构成多态的条件3 虚函数4 虚函数的重写 覆盖 5 多态调用虚函数的规则6 析构函数的重写 基类与派生类析构函数的名字不同 7 设计一个不能被继承的基类8 final关键字和override 关键字9