C++>多态,强制多态,参数类型多态,重载多态,包含多态(运行时多态),联编(早期联编,滞后联编),虚函数,虚函数重写(覆盖),函数重载、覆盖、隐藏的比较,抽象类,虚函数表。),)

2023-05-16

多态

多态的概念

多态即多种形态,当不同的对象去完成某一个行为时产生出不同的状态。类的多态体现在面向对象程序设计的许多方面,函数重载,函数的覆盖继承,虚函数以及模板均是多态性的体现。

多态的种类及不同种类介绍

1>强制多态:强制类型转换

强制多态是指将一种类型的值转换为另一种类型的值进行语义操作,从而防止类型错误。类型转换可以是隐式的,在编译时完成;也可以是显式的,可在动态运行时完成。
1.C++使用两种语法进行强制类型转换:(类型说明符)变量名 / 类型说明符(变量名)
2.构造函数也是一种重要的显示类型转换,构造函数进行的类型转换只能将参数类型向类类型转换。如果要把类类型的数据转换为所指定的某种数据类型,就需要使用类型转换函数。类型转换函数又称为类型强制转换成员函数,他是类中的一个非静态成员函数。需要注意的是类型转换函数不能有返回值,不带任何参数,也不可以将类型转换函数定义为友元函数。
类型转换函数定义格式与实例如下:
class<类型说明符1>
{
public:
operator<类型说明符2>();

}

#include<iostream.h>
class Rational
{
public:
	operator double();	//类型强制转换成员函数的声明
	Rational(int d,int n)	//构造函数
	{
		den=d;
		num=n;
	}
private:
	int den;	//分数的分子
	int num;	//分数的分母
};
Rational::operator double()	//类型强制转换成员函数的定义
{
	return	double(den)/double(num);	//返回double类型的分数值
}
void main()
{
	Rational r(5,8);
	double d=4.7;
	d+=r;				//隐式调用类型强制转换成员函数,也可以使用显式类型强制转换:d+=double(r);
	cout<<d<<endl;		//输出结果为5.325
}
2>参数类型多态: 函数模板和类模板

参数多态与类模板相关联,它把功能相似仅数据类型(或类类型)不同的函数或类类设计为通用的函数模板或类模板,从而实现了相同的函数或类的成员函数对多种数据类型的数据处理。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础.
1.函数模板
(1)概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
(2)定义
template<typename T1,typenamename T2,…,typename Tn>
返回值类型 函数名(参数列表){}

template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}      //typename是用来定义模板参数关键字,
	   //也可以用class(不能使用struct).

(3)函数模板的实例化

"1"隐式实例化
让编译器根据实参推演模板参数的实际类型.
"2"显式实例化
在函数名后的<>中指定模板参数的实际类。
template<class T>
T Add(const T& left , const T& right)
{
	return left + right;
}
//隐式实例化
int Test1()
{
	int a1 = 10, a2 = 20;
	doublr d1 = 10.0, d2 = 20.2;
	Add(a1,a2);
	Add(d1,d2);
	Add(a1,d1);//此处出错
	return 0;
}
//显示实例化
int Test2()
{
	int a = 10;
	double b = 20.0;
	Add<int>(a,b);
	return 0;
}

Add(a1,d1);语句不能通过编译,因为在编译期间,编译器看到该实例化时会推演其实参类型通过实参a1将T推演为int,通过实参b1将T推演为double类型,但是模板参数列表只有一个T,编译器无法确定此处到底将T确定为int型或者double型而报错。
修改方式:‘1’用户自己来强制转化 Add(a1,(int)b1); ‘2’显式实例化

2.类模板

(1)定义

template<class T1, class T2, ...,classTn>
class Vector
{
	//类内成员定义
};
	//动态顺序表
template<class T>
class Vector
{
pubilc:
	......
private:
	......
}
//类模板中函数放在类外定义时,
//需要加模板参数列表.
template<class T>
vector<T>::Vector()
{
	if(_pData)
	{
		delete[] _pData;
	}
}

Seqlist(vctor)不是具体的类,而是编译器根据被实例化的类型生成具体类的模具。
(2)类模板的实例化
类模板的实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真的类,而实例化的结果才是真正的类。

	//Vector类名,Vector<>才是类型
	Vector<int> s1;
	s1.PushBack(1);
	s1.PushBack(2);
	s1.PushBack(3);

3.模板参数
(1)类型参数
template
跟在class或typename之后的类的参数类型。
(2)非类型参数
template<size_t N=10>
用一个常量作为类(函数)模板的参数,该参数可以被当做常量来使用。

3>重载多态(函数重载):函数名相同,但参数类型和参数个数顺序不同构成的多态

重载多态是多态性中最简单的形式,它分为函数重载和运算符重载。
1.函数重载
函数重载允许功能相近的函数使用相同的函数名,编译器根据参数列表的不同区别不同函数。
2.运算符重载
运算符重载是对已存在运算符赋予多重含义,同一个运算符作用于不同的类型数据导致不同类型的行为,它的实质是函数重载。

4>包含多态(运行时多态、虚函数重写实现、虚方法的动态联编):

包含多态是通过虚函数来实现的,同样的操作可用于一个类型及其子类型。包含多态就是运行时多态。如果派生类中覆盖了基类对象的函数,根据赋值兼容,用基类类型的指针或引用指向派生类,就可以通过这个指针或引用来使用派生类的成员函数。如果这个函数是普通成员函数,通过基类指针访问到的只能是基类的成员成员。如果将其设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数,这样通过基类类型的指针就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行时过程的多态。
包含多态的及实现包含多态的构成条件:
1.基类中必须包含有虚函数,在派生类中必须对基类中的虚函数进行重写。
2.必须通过基类的指针或者引用来调用虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout<<""全价买票""<<endl;
	}
};
class Student:public Person
{
public:
	virtual void BuyTicket()	//虚函数的覆盖
	{
		cout<<""半价买票""<<endl;
	}
};
void Func(Person & people)
{
	people.BuyTicket();
}
void Test()
{
	Person Mike;
	Func(Mike);		//调用Person里的BuyTicket
	Student Tom;
	Func(Tom);	//调用Student里的BuyTicket
}

总结:
1.包含多态和参数多态是通用多态,重载多态和强制多态是特定多态。
2.编译时的多态性是指系统在编译时就确定了调用同名函数的哪一个,C++语言中函数重载实现的都是编译时的多态性。
3.运行时多态则需要系统在运行器件根据每一个对象指针指向的对象确定,调用父类或是子类的成员函数,虚函数标识了采用滞后联编实现运行时的多态性。

运行时多态(包含多态详细)

C++语言是利用虚函数实现运行时的多态性的。对于存在虚函数的类层次中,系统将按照滞后联编的方式考虑调用调用对象的成员函数。

1>联编

1.概念及定义
联编是指一个计算机程序自身彼此关联(使一个源程序经过编译、连接,成为一个可执行程序)的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系。
2.分类
按照联编所进行的阶段不同,可分为静态联编(早期联编)和动态联编(滞后联编)。
(1)早期联编:在对于同名函数的调用时,在编译时决定执行哪个同名函数。
(2)滞后联编:在执行阶段依据要处理的对象的类型来决定执行哪个类的成员函数。
(3)备注:一些面向对象的程序设计语言(Java等)完全使用滞后联编,而C++需要兼容C,所以采用了两种联编方式。C++中虚函数是滞后联编的标识。
3.两种联编的对比
(1)动态联编的功能明显比静态联编强大,但是从效率的执行来看,静态联编在编译连接阶段就完成决策,而动态联编为了使程序能够在运行阶段惊醒决策,必须采取一些方法来跟踪基类指针或引用指向对象类型,这增加了额外的处理开销。基于C++知道原则:不要为不适用的特性付出代价,所以程序默认使用静态联编。
(2)在 C++ 中动态联编需要虚函数的支持,这是因为虚函数的工作原理决定的,而正是因为使用了虚函数来实现动态联编,也让动态联编的效率略低于静态联编。通常,编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员,隐藏成员保存了一个指向函数地址数组的指针 ,这个数组就是虚函数表(virtual function table, vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址,调用虚函数时,程序将查看存储在对象中的 vtbl 地址,然后转向相应的函数地址表,如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。

2>虚函数

1.概念及作用及条件
概念:在类成员函数的前面加virtual关键字就构成了虚函数。
定义:virtual<成员函数原型>
作用:在C++中使用虚函数标志滞后联编。要使用虚函数是,在外部程序中定义一个指向基类对象的指针。根据赋值兼容准则(派生类对象是基类对象,反之不成立),该指针也可指向基类对象,如果该对象指针指向基类对象则调用基类的成员函数,如果该对象指针指向派生类对象则调用派生类的成员函数。
条件:
(1)只有在类层次中才能声明虚函数,虚函数存在于类继承中,只有一个类中存在虚函数是无意义的。
(2)派生类中虚函数的函数名、参数列表和返回值必须与基类中的虚函数完全一样。
(3)派生类必须公有继承基类。
(4)只有成员函数才可定义为虚函数,友元/全局/static函数都不可以。
2.实现

#include<iostream.h>
class A
{
public:
	A(void){	//构造函数
	}
	virtual void f1()const	//定义虚函数
	{
		cout<<"基类的f1()函数"<<endl;
	}
	void f2()const		//定义一般成员函数
	{
		cont<<"基类的f2函数"<<endl;
	}
};
class AA:public A
{
public:
	AA(void){		//构造函数
	}
	void f1()const		//定义虚函数,继承覆盖即虚函数的重写(函数名,参数列表,返回值相同)
	{
		cout<<"派生类的f1()函数"<<endl;
	}
	void f2()const		//定义一般成员函数
	{
		cont<<"派生类的f2函数"<<endl;
	}
};
void main()
{
	A *pa,myA;	
	AA myAA;
	pa=&myA;	//虚函数重写(覆盖)//基类指针指向基类对象//滞后联编,虚方法的动态联编
	pa->f1();					//调用基类的f1()虚函数
	pa->f2();					//调用基类的成员函数f2()
	pa=&myAA;				//基类指针若是指向派生类对象
	pa->f1();					//调用派生类的f1()函数
	pa->f2();					//调用基类的成员函数f2()
	myA.f2();	//覆盖继承函数//基类对象调用基类的成员函数f2()		//早期联编,非虚方法的静态联编,
	myAA.f2();				//派生类对象调用派生类的成员函数f2()	//若使用指针访问成员,则只会根据指针类型调用基类成员,因为没使用虚函数
}

小结
(1)派生类中f1()函数前未加virtual关键字仍是虚函数(建议加上),因为在派生类中声明某个成员函数的函数名、参数列表和返回类型和基类中声明的虚函数完全一样,那么系统自动将其视为虚函数。
(2)基类指针根据指针指向的是基类对象还是派生类对象调用对应的虚函数。而覆盖继承在编译时就依据对象指针的类型决定了事中调用某一类对象的成员函数,如f2()函数是一般覆盖继承的函数,那么不管基类指针类pa指向派生类对象或是基类对象,利用pa调用f2()函数始终执行的是基类的f2()函数。
3.关于虚函数的关键字override与final
(1)final修饰虚函数标识虚函数不能被继承。
(2)override修饰虚函数,检查派生类中的虚函数是否重写了基类的虚函数。
4.不能声明为虚函数的有:
友元函数,静态成员函数,类外普通函数,inline函数。

3>虚函数的重写(覆盖)

1.虚函数的重写(函数名,参数列表,返回值相同)
2.虚函数重写的两个例外
(1)协变(基类与派生类的返回值不同),派生类重写基类的虚函数时虚函数的返回值类型不同,基类返回基类的指针或引用,派生类返回派生类的指针或引用。
(2)析构函数重写(基类与派生类的析构函数名不同),如果基类的析构函数为虚函数,此时派生类析构函数只要定义,虽然名字不同但仍是虚函数的重写,因为编译器会将析构函数的名称统一处理为destructor。

4>函数重载、隐藏(重定义)和覆盖(虚函数重写)的对比
语法重载隐藏覆盖(虚函数重写)
两函数作用域相同基类与派生类基类与派生类
函数名相同相同相同(析构函数除外)
参数列表不同无要求相同
返回值相同无要求相同(协变除外)
虚函数/无要求两个函数必须是虚函数
调用要求指针或者引用调用
重载:参数列表不同
隐藏:子类重写父类中的代码,将父类中的内容隐藏
覆盖:重写父类虚函数,包含多态。

抽象类

1>概念及定义

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

2>实现继承和接口继承

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

多态的原理

1>虚函数表(虚函数工作原理)

通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表,这个数组最后面放了一个nullptr。虚函数表中储存了为类对象进行声明的虚函数的地址。即有虚函数的指针将多包含一个指针,该指针指向该类中所有虚函数的地址表。
1.虚表存放规则
虚函数存放顺序按在类内的声明顺序存放的。vs下虚表存放在代码段。
2.派生类虚表构建规则
派生类拥有独立的虚函数表指针,派生类拷贝基类的虚表;若派生类重写了虚函数,则覆盖更新虚表;若派生类增加了新的虚函数,则将其放到最后。

2>多态的原理

即包含多态,接包含多态实例,如下

Person Father;
Student Child;
Func(Father);	//
Func(Child);	//不同对象调用不同函数实现多态
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++>多态,强制多态,参数类型多态,重载多态,包含多态(运行时多态),联编(早期联编,滞后联编),虚函数,虚函数重写(覆盖),函数重载、覆盖、隐藏的比较,抽象类,虚函数表。),) 的相关文章

  • linux---进程间通信(ipc)之共享内存

    前面我们讲解了进程间通信之管道 xff0c 这段我们讲解我们的共享内存 共享内存是所有进程间通信方式最快的一种 内存共享模型就像下面的图一样 xff0c 就是将物理内存映射到我们进程的虚拟地址上 xff0c 我们就可以直接操作我们虚拟地址空
  • Effective C++总结

    explicit关键字 C 43 43 中的explicit关键字只能用于修饰只有一个参数或者是其他参数有默认值的类构造函数 它的作用是表明该构造函数是显式的 而非隐式的 跟它相对应的另一个关键字是implicit 意思是隐藏的 类构造函数
  • 计算机网络(5)TCP之重传机制

    重传机制 超时重传数据包丢失确认应答丢失 快速重传SACKD SACK例一 ACK 丢包例2 xff1a 网络延时 TCP 是通过序列号 确认应答 重发控制 连接管理以及窗口控制等机制实现可靠性传输的 TCP 实现可靠传输的方式之一 xff
  • 中断与回调

    1 xff0c 回调函数 回调函数的原理是使用函数指针实现类似 软中断 的概念 比如在上层的两个函数A和B xff0c 把自己的函数指针传给了C xff0c C通过调用A和B的函数指针达到 当做了什么 xff0c 通知上层来调用A或者B 的
  • CUDA 程序的优化(3) 任务划分

    4 3 1任务划分原则 首先 xff0c 需要将要处理的任务划分为几个连续的步骤 xff0c 并将其划分为CPU端程序和GPU端程序 划分时需要考虑的原则有 列出每个步骤的所有可以选择的算法 xff0c 并比较不同算法在效率和计算复杂度上的
  • C++ Matlab混合编程时“函数或变量 ‘matlabrc‘ 无法识别”

    在QT中调用Matlab初始化时 xff0c 出现了 函数或变量 matlabrc 无法识别 的情况 xff0c 接着崩溃 而且比较神奇的是 xff0c 前一次是可以初始化的 xff0c 但运行过程中发生了崩溃 直接搜解决办法 xff0c
  • Notepad++全选一整列的靠谱办法

    遇到行数较少的可以直接按住ALT手动选取 xff0c 但遇到行数较多 xff0c 就得这么干 xff1a 鼠标放在第一行某一列 xff0c 按住Alt 43 Shift xff0c 然后鼠标选择最后一行该列 xff0c 输入内容即可 xff
  • 对象转xml格式工具类

    import com ruiyun gui store haikang haikang bean FCSearchDescription import com ruiyun gui store haikang haikang bean FD
  • 【无标题】MQ静态图片获取

    public void getImageV40 String path Integer buildingProjectId HttpServletResponse response JSONObject param 61 new JSONO
  • 数据加解密时Base64异常:Illegal base64 character 3a

    现象 用base64工具类对中文进行处理时出现异常 xff0c 在数据加解密场景中经常使用 java lang IllegalArgumentException Illegal base64 character 3a at java uti
  • Winsock编程实例---TCP&UDP

    0x1 基于TCP的通信 1 服务端 1 1 创建基本流程 创建一个TCP服务端的程序需要调用的函数流程 xff1a 初始化函数库 gt gt WSAStartup 创建套接字 gt gt socket 绑定套接字 gt gt bind 监
  • 数据结构---选择排序(直接选择排序和堆排序图解)

    选择排序思想 xff1a 每一次从待排序的数据元素中选出最小 xff08 或最大 xff09 的一个元素 xff0c 存放在序列的起始位置 xff0c 直到全部待排序的 数据元素排完 直接选择排序 在元素集合array i array n
  • Java HttpUtils类

    Java HttpUtils类 Java HttpUtils类 定义 Public class HttpUtils 收集HTTP Servlet使用的静态的有效方法 方法 1 getRequestURL public static Stri
  • Ubuntu打造家用NAS三——网盘与影视中心

    Ubuntu打造家用NAS三 网盘与影视中心 一 Ubuntu 挂载硬盘 通过 Putty 连接 NAS查看硬盘位置 xff1a sudo fdisk l找到需要挂载的硬盘 xff0c 我的是 Disk dev sdb xff1a xxx
  • Ardupilot笔记:Rover auto模式下的执行流程

    先从mode auto cpp的update 开始分析 流程如图 xff1a 进入函数update 后会执行函数navigate to waypoint mode auto cpp span class token keyword void
  • 串口通信协议 UART+I2C+SPI

    UART 异步 串行 全双工 I2C SPI 不同通信协议比较 UART UART协议详解 UART通信 xff0c 接收与发送 xff08 详细版 xff0c 附代码 xff09 UART串行通信详解 待整理 UART是Universal
  • c语言---宏

    宏 1 仅仅替换 2 不能定义宏参类型 3 不会检查宏参有没有定义 定义带参数的宏 define JH a b t t 61 a a 61 b b 61 t xff0c 对两个参数a b的值进行交换 xff0c 下列表述中正确的是 xff0
  • Ros安装rosdep update出错,解决办法(从根本入手)

    博主作为一个ros刚入门的新手 xff0c 之前也安装过ros ros2但是在Ros安装在进行rosdep update 时运气与网络是成功的关键 xff0c 在尝试了好多次 xff0c 运气好一次就成功了 xff0c 运气不好得不停的试错
  • vscode使用方法

    01 ctrl 43 u 返回上一个光标焦点 02 发送请求插件 到VSCode插件中搜索REST Client 搜索到 xff0c 点击install进行安装 创建一个 http文件 编写测试接口文件 右键选择 发送请求 xff0c 测试
  • 自主飞行无人机开发--SALM cartographer开源框架 rplidar A2/3

    参考学习网站 xff1a https google cartographer ros readthedocs io en latest 问题提出 xff1a 四旋翼搭载激光雷达A3进行SLAM室内定位 xff0c 其怎样Running Ca

随机推荐