C++17 --- 多态性、虚函数、多态的条件、联编(捆绑,绑定)

news2025/8/3 5:15:22

一、多态性

1、多态

多态性是面向对象程序设计的关键技术之一。 若程序设计语言不支持多态性,不能称为面向对象的语言。

多态性(polymorphism) 多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。

函数的重载,运算符的重载,属于编译时的多态性。

以类的虚成员函数为基础的运行时的多态性,是面向对象程序设计的标志性特征。体现了 类推和比喻的思想方法。

多态的四种形态:

重载多态 -- 函数重载、运算符重载
包含多态 -- virtual 函数
参数多态 -- 模板
强制多态 -- 强制类型装换:static_cast , const_cast ,....

2、编译时的多态和运行时的多态

在这里插入图片描述

3、编译时多态,早期绑定

int max(int a,int b){ return a > b ? a : b;}

char Max(char a,char b){ return a > b ? a : b;}

double Max(double a,double b) { return a > b ? a : b; }

int main()
{
	int x = Max(12, 23) ;
	char ch = Max('c', 'b');
	double dx = Max(12.2334.45);
	return 0 ;
}

机器代码,编译链接时的函数名称:
在这里插入图片描述

4、运行时的多态

1)运行时多态的设计思想:

对于相关的类型,确定它们之间的一些共同特征,(属性和方法) , 将共同特征被转移到基类中,然后在基类中,把这些共同的函数或方法声明为公有的虚函数接口。然后使用派生类继承基类,并且在派生类中重写这些虚函数,以完成具体的功能。这种设计使得共性很清楚,避免了代码重复,将来容易增强功能,并易于长期维护。

客户端的代码(操作函数)通过基类的弓|用或指针来指向这些派生类型对象,对虚函数的调用会自动绑定到派生类对象上重写的虚函数。

2)虚函数的定义:

虚函数是一个类的成员函数,定义格式如下:

virtual 返回类型 函数名(参数表) ;

关键字 virtual 指明该成员函数为虚函数。只能将类的成员函数定义为虚函数。当某一个类的成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。

3)示例:运行时的多态性,晚绑定。

4)总结:

运行时的多态:共有继承 + 虚函数 + (指针或引用调用虚函数)。

二、虚函数

1、虚表

如果一个类包含了虚函数,不管有多少个虚函数,则增加了一个指针的大小。
有了一个虚指针 – VPtr ,vptr 指向一个虚表(vtable),虚表里面存储本类中虚函数的入口地址。

对于代码:

class A
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void fb(){cout << "A::fb" << endl;}
	virtual void fc(){cout << "A::fc" << endl;}
};

void main()
{
	A a;
}

在这里插入图片描述
在这里插入图片描述

2、虚表的大小

不论有多少个虚函数,只要有,就多一个指针的大小。

示例代码:

class A
{
public:
};

class B
{
public:
	void ffn(){}
};

class C1
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
};

class C2
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void fb(){cout << "A::fb" << endl;}
};
class C3
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void fb(){cout << "A::fb" << endl;}
	virtual void fc(){cout << "A::fc" << endl;}
};

void main()
{
	cout << sizeof(A) << endl;// 其为1
	cout << sizeof(B) << endl;// 也为1
	cout << sizeof(C1) << endl;//4
	cout << sizeof(C2) << endl;//4
	cout << sizeof(C3) << endl;//4
}

运行结果:

在这里插入图片描述

3、通过数组名加下标读取到虚表内的函数

	A a;
	typedef void (*FUN)(); //定义了一个函数指针
	FUN pf = NULL; //pf是函数指针类型,为指向返回值为void,里面没有参数的一类函数
	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + n ) + m ); //pf指向类a内虚表所指向的第一个函数函数名,n、m为自己给的值,+n表示第几张虚表,+m表示虚表内的第几个函数
	pf();

4、 一个类的虚表

class A
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void fb(){cout << "A::fb" << endl;}
	virtual void fc(){cout << "A::fc" << endl;}
};

void main()
{
	A a;
	typedef void (*FUN)(); //定义了一个函数指针
	FUN pf = NULL; //pf是函数指针类型,为指向返回值为void,里面没有参数的一类函数
	pf = (FUN)*(int *)(*(int *)(&a)); //pf指向类a内虚表所指向的第一个函数函数名
	pf();
	pf = (FUN)*((int *)(*(int *)(&a)) + 1);
	pf();
	pf = (FUN)*((int *)(*(int *)(&a)) + 2);
	pf();
}

运行结果:

在这里插入图片描述

在这里插入图片描述

5、一个类继承另一个类的虚表

同名同参的虚函数,会进行重写/覆盖

class A
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void ga(){cout << "A::ga" << endl;}
	virtual void ha(){cout << "A::ha" << endl;}
};

class B:public A//B内有6个虚函数
{
public:
	virtual void fb(){cout << "B::fb" << endl;}
	virtual void gb(){cout << "B::gb" << endl;}
	virtual void hb(){cout << "B::hb" << endl;}
};

class C:public A//C内有5个虚函数
{
public:
	virtual void fa(){cout << "C::fc" << endl;}//同名同参的虚函数,会进行重写/覆盖
	virtual void gc(){cout << "C::gc" << endl;}
	virtual void hc(){cout << "C::hc" << endl;}
};

void main()
{
	cout << sizeof(A) << endl;//4
	cout << sizeof(B) << endl;//4
	cout << sizeof(C) << endl;//4
	cout<<endl;

	B b;
	typedef void(*FUN)();
	FUN pf = NULL;
	pf = (FUN)*(int *)(*(int *)(&b));
	pf();
	pf = (FUN)*((int *)(*(int *)(&b)) + 1);
	pf();
	pf = (FUN)*((int *)(*(int *)(&b)) + 2);
	pf();
	pf = (FUN)*((int *)(*(int *)(&b)) + 3);
	pf();
	pf = (FUN)*((int *)(*(int *)(&b)) + 4);
	pf();
	pf = (FUN)*((int *)(*(int *)(&b)) + 5);
	pf();
	cout<<endl;

	C c;
	pf = (FUN)*(int *)(*(int *)(&c));
	pf();
	pf = (FUN)*((int *)(*(int *)(&c)) + 1);
	pf();
	pf = (FUN)*((int *)(*(int *)(&c)) + 2);
	pf();
	pf = (FUN)*((int *)(*(int *)(&c)) + 3);
	pf();
	pf = (FUN)*((int *)(*(int *)(&c)) + 4);
	pf();
}

运行结果:
在这里插入图片描述

三个类中虚表的内存布局:

在这里插入图片描述

6、一个类继承多个基类的虚函数

多继承 – 一个类继承多个类
基类有几个,就有几个虚表
将函数挂到第一个继承的类虚表内

对于多继承,在子类的对象中,每个父类都有自己的虚表,将最终与类的虚函数放在第一个父类的虚表中,这样做解决了不同的父类类型的指针指向比较清晰
如果在子类中重写了多个父类的同名同参虚函数,那么在虚表中同样做了修改

1)没有覆盖的情况

class A
{
public:
	virtual void fa(){cout << "A::fa" << endl;}
	virtual void ha(){cout << "A::ha" << endl;}
};

class B
{
public:
	virtual void fb(){cout << "B::fb" << endl;}
	virtual void hb(){cout << "B::hb" << endl;}
};

class C
{
public:
	virtual void fc(){cout << "C::fc" << endl;}
	virtual void hc(){cout << "C::hc" << endl;}
};

class D:public A,public B,public C
{
public:
	virtual void fd(){cout << "D::fd" << endl;}
	virtual void hd(){cout << "D::hd" << endl;}
};

int main()
{
	cout << sizeof(D) << endl ;//大小为12
	D d;

	typedef void(*FUN)();
	FUN pf = NULL;
	pf = (FUN)*(int *)( *(int *)(&d));
	pf();
	pf = (FUN)*((int *)( *(int *)(&d)) + 1 );
	pf();
	pf = (FUN)*((int *)( *(int *)(&d)) + 2 );
	pf();
	pf = (FUN)*((int *)( *(int *)(&d)) + 3 );
	pf();

	cout<<endl;

	pf = (FUN)*((int *)*(int *)((int *)(&d) + 1 ));
	pf();
	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 1 ) + 1 );
	pf();

	cout<<endl;

	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ));
	pf();
	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ) + 1 );
	pf();
}

运行结果:

在这里插入图片描述

四个类的虚表内存布局

在这里插入图片描述

2)存在覆盖的情况

class A
{
public:
	virtual void ff(){cout << "A::fa" << endl;}//
	virtual void ha(){cout << "A::ha" << endl;}
};

class B
{
public:
	virtual void ff(){cout << "B::fb" << endl;}//
	virtual void hb(){cout << "B::hb" << endl;}
};

class C
{
public:
	virtual void ff(){cout << "C::fc" << endl;}//
	virtual void hc(){cout << "C::hc" << endl;}
};

class D:public A,public B,public C
{
public:
	virtual void ff(){cout << "D::ff" << endl;}//
	virtual void hd(){cout << "D::hd" << endl;}
};

int main()
{
	cout << sizeof(D) << endl ;//大小为12
	D d;

	typedef void(*FUN)();
	FUN pf = NULL;
	pf = (FUN)*(int *)( *(int *)(&d));//A::fa
	pf();
	pf = (FUN)*((int *)( *(int *)(&d)) + 1 );
	pf();
	pf = (FUN)*((int *)( *(int *)(&d)) + 2 );
	pf();
	/*pf = (FUN)*((int *)( *(int *)(&d)) + 3 );
	pf();*/

	cout<<endl;

	pf = (FUN)*((int *)*(int *)((int *)(&d) + 1 ));
	pf();
	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 1 ) + 1 );
	pf();

	cout<<endl;

	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ));
	pf();
	pf = (FUN)*( (int *)*(int *)( (int *)(&d) + 2 ) + 1 );
	pf();
}

运行结果:
在这里插入图片描述

四个类的内存布局

在这里插入图片描述

三、多态的条件

1、覆盖(重写):

1、最少两个类,必须是父子关系;
2、同名同参虚函数,基类写了virtual,子类可以不写,他会将virtual继承下来

2、多态的条件:

1、满足覆盖
2、基类的指针或者引用指向基类对象或者派生类对象

四、联编(捆绑,绑定) – 函数调用 和 函数体联系的过程

联编是指计算机程序彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把函数的调用和函数的入口地址相结合的过程。

早捆绑/联编 – 在编译时
晚捆绑 – 在运行时

1、静态联编(static binding)早期绑定:

静态联编是指在编译和链接阶段,就将函数实现和函数调用关联起来。

C语言中,所有的联编都是静态联编,并且任何一种编译器都支持静态联编。
C++语言中,函数重载和函数模板也是静态联编。
C++语言中,使用对象名加点"."成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译和链接时确定。(称为静态联编)。

代码示例:

class A
{
public:
	virtual void fn(){ cout << "A::fn" << endl;}
};
class B:public A
{
public:
	virtual void fn(){ cout << "B::fn" << endl;}
};

class C:public A
{
public:
	void fn(){ cout << "C::fn" << endl;}
};

void test(A aa)
{
	aa.fn();
}

void main()
{
	A a;
	B b;
	C c;
	a.fn();//编译时就知道这是a的fn(),为静态联编
	b.fn();//编译时就知道这是b的fn()
	c.fn();
	cout<<endl;

	test(a);
	test(b);//编译时就已经确定,test内的fn()是类a内的fn(),在编译时就已经捆绑,为静态联编
	test(c);
	
}

在这里插入图片描述

2、动态联编(dynamic binding)亦称滞后联编(late binding)或晚期绑定:

动态联编是指在程序执行的时候才将函数实现和函数调用关联起来。
C++语言中,使用类类型的引用或指针调用虚函数(成员选择符">"),则程序在运行时选择虚函数的过程,称为动态联编。

代码示例:
产生多态

class A
{
public:
	virtual void fn(){ cout << "A::fn" << endl;}
};
class B:public A
{
public:
	virtual void fn(){ cout << "B::fn" << endl;}
};

class C:public A
{
public:
	void fn(){ cout << "C::fn" << endl;}
};

void test2(A &aa)//引用,传本身
{
	aa.fn();
}

void test3(A *p)//指针
{
	p->fn();
}


void main()
{
	A a;
	B b;
	C c;
	a.fn();//编译时就知道这是a的fn(),为静态联编
	b.fn();//编译时就知道这是b的fn()
	c.fn();
	cout<<endl;

	test2(a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编,也称其为多态
	test2(b);
	test2(c);
	cout<<endl;
	
	test3(&a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编
	test3(&b);
	test3(&c);
}

在这里插入图片描述

3、不加虚函数,不会产生多态

class A
{
public:
	 void fn(){ cout << "A::fn" << endl;}
};
class B:public A
{
public:
	 void fn(){ cout << "B::fn" << endl;}
};


void test(A aa)
{
	aa.fn();
}

void test2(A &aa)//引用,传本身
{
	aa.fn();
}

void test3(A *p)//指针
{
	p->fn();
}

void main()
{
	A a;
	B b;
	a.fn();//编译时就知道这是a的fn(),为静态联编
	b.fn();//编译时就知道这是b的fn()
	cout<<endl;

	test(a);
	test(b);//编译时就已经确定,test内的fn()是类a内的fn(),在编译时就已经捆绑,为静态联编
	cout<<endl;
	
	test2(a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编,也称其为多态
	test2(b);
	cout<<endl;
	
	test3(&a);//在程序执行时,才知道到调用谁的fn(),在程序执行时才将函数实现和函数调用关联起来,为动态联编
	test3(&b);
	
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/34924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

弘玑Cyclone2022产品发布会:超级自动化下的流程挖掘——弘观流程智能

近日&#xff0c;在弘玑Cyclone“智无边界&#xff0c;数字未来”发布会上&#xff0c;弘玑Cyclone2022年超级自动化系列产品全新亮相&#xff0c;首席产品官贾岿博士带领产品团队以创新技术对新时代语境下的数字生产力进行了全新解读。 本文将为大家分享本次发布会重磅推出的…

C++之字符串处理函数

字符串操作函数 C语言中几个处理字符串的函数都是以str开头,处理时默认遇到\0结束操作 这些字符串函数都使用了下面这个头文件: #include <string.h> 测量字符串的长度strlen 函数原型 size_t strlen(const char *s) //s指需要测量的字符串首元素地址注意,测量时不计…

建筑设计中,如何快速获得场地的等高线图?

等高线指的是地形图上高程相等的相邻各点所连成的闭合曲线。把地面上海拔高度相同的点连成的闭合曲线&#xff0c;并垂直投影到一个水平面上&#xff0c;并按比例缩绘在图纸上&#xff0c;就得到等高线。&#xff08;来自百度百科的定义&#xff09; 图新地球软件&#xff0c;…

YOLOS

太多了 yolo v x 现在又s了 Transformer能否从纯序列到序列的角度执行2D目标级识别&#xff0c;而对2D空间结构知之甚少&#xff1f;为了回答这个问题&#xff0c;今天就展示了“You Only Look at One Sequence” (YOLOS)&#xff0c;这是一系列基于朴素视觉变换器的目标检测模…

磨金石教育摄影技能干货分享|古风人像修图与调色技巧

上一篇文章我们介绍了古风人像的拍摄技巧&#xff0c;这期我们再来了解一下后期修图与调色的技巧。 一、修 饰 皮 肤 首先我们把拍好的照片拖到PS里。 1、ctrlj复制一个图层。然后选择污点修复画笔把脸部瑕疵去掉&#xff0c;嘴巴部位使用的是修复画笔。这样我们可以看到人像…

pyenv的安装与简单使用

一、pyenv是什么&#xff1f; pyenv 是 python 的 多环境管理 工具&#xff0c;可以安装多个版本的 python&#xff0c;并为 全局 或 单个应用 设置指定版本 二、pyenv的安装 2.1 下载 pyenv 压缩包 压缩包下载地址&#xff1a;https://github.com/pyenv-win/pyenv-win#rea…

测开 - 自动化测试selenium(WebDriver API) - 细节狂魔

文章目录回顾什么是驱动&#xff1f;驱动的工作原理是什么&#xff1f;一个简单的 Web自动化 演示1、定位元素的方法 - 只介绍两种最常使用的2、元素的操作3、等待3.1、强制等待强制等待的优点 && 缺点3.2、隐式等待隐式等待的优缺点3.3、显示等待显示等待的优缺点&…

五种方法帮你解决电脑内存占用大的问题

有用户反映自己的电脑什么都没开&#xff0c;但是运行内存显示占用90%以上&#xff0c;这是什么情况&#xff1f;运行内存占用大&#xff0c;直接影响了用户的使用体验&#xff0c;下面小编就给大家分享五个解决电脑内存占用大的办法吧。 方法一&#xff1a; 1、右键【我的电脑…

C++ —— 模拟实现vector和迭代器失效

目录 1.成员变量的设计 2.迭代器以及接口设计 3.容量接口 4.reserve的设计 5.resize的设计 6.尾插尾删的设计 7.构造、析构函数 8.运算符重载 9.insert接口设计与迭代器失效 10.erase的设计和迭代器失效 11.双层深拷贝问题 12.完整代码 1.成员变量的设计 成员变量…

Stream流、FiLe和IO流、

package com.streamdemo; import java.util.ArrayList; import java.util.List; /*** 体验Stream流** 创建一个集合&#xff0c;存储多个字符串元素* "张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"** 把…

【Java八股文总结】之面试题(一)

文章目录面试题1、说一下ArrayList和LinkedList区别2、说一下HashMap的Put方法3、ThreadLocal4、说一下JVM中&#xff0c;哪些是共享区&#xff0c;哪些可以作为gc root?5、如何排查项目中遇到的JVM问题?6、如何查看线程死锁?7、线程之间如何进行通讯的?8、介绍一下Spring&…

分布式全局唯一 ID生成器(百度UidGenerator)

文章目录为什么要使用全局ID生成器&#xff1f;使用UUID作为主键&#xff1f;使用数据库主键自增&#xff1f;UidGenerator简介雪花算法snowflakeSpringBoot整合百度UidGenerator为什么要使用全局ID生成器&#xff1f; 在分库分表中必定会面临着一个问题&#xff0c; 就是如何…

steam搬砖项目怎么样

大家好&#xff0c;我是阿阳 Steam搬砖就是利用一些技巧和经验去Steam购买一些低价格商品&#xff0c;我们低价拿到道具&#xff0c;再以低于国内市场价的价格销售&#xff0c;保持了发货的稳定性和速度&#xff0c;赚取了利润。 如果是以前有人给我安利这种看着就不靠谱的赚…

scrapy 使用FilesPipeline和ImagesPipeline

除了爬取文本&#xff0c;我们可能还需要下载文件、视频、图片、压缩包等&#xff0c;这也是一些常见的需求。scrapy提供了FilesPipeline和ImagesPipeline&#xff0c;专门用于下载普通文件及图片。两者的使用方法也十分简单&#xff0c;首先看下FilesPipeline的使用方式。 Fi…

基于Netty的高性能RPC框架(分布式缓存、雪花算法、幂等性)

文章目录前言介绍1. 服务提供2. 安全策略3. 设计模式亮点1. 信息摘要算法的应用2. 心跳机制3. SPI 机制4. IO 异步非阻塞5. RNF 协议快速开始1.依赖1.1 直接引入1.2 maven引入2. 启动 Nacos3. 提供接口4. 启动服务5. 启动客户端5. 额外配置5.1 配置文件5.2 日志配置6. 场景应用…

AxGlyph矢量绘图软件 | 绘图软件

文章目录AxGlyph矢量绘图软件安装教程所见即所得滚动式符号面板&#xff0c;多底色、面板符号定制和分页顺序调整格式化图形&#xff0c;通过节点控制可获取丰富的变形支持自由矢量画笔、混合矢量路径和矢量漫水填充整合精简版的AxMath*&#xff0c;可方便的在图形中嵌入数学公…

Python的PyQt框架的使用-构建环境篇

Python的PyQt框架的使用-构建环境篇一、前言二、安装PyQt三、使用第三方开发工具一、前言 个人主页: ζ小菜鸡大家好我是ζ小菜鸡&#xff0c;小伙伴们&#xff0c;让我们一起来学习Python的PyQt框架的使用。如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连) Python起初是一…

SpringBoot-Eureka-xstream-rce漏洞复现

SpringBoot-Eureka-xstream-rce actuator 是 springboot 提供的用来对应用系统进行自省和监控的功能模块。其提供的执行器端点分为两类&#xff1a;原生端点和用户自定义扩展端点&#xff0c;原生端点主要有&#xff1a; 漏洞利用 1.利用trace&#xff0c;获取认证信息&#…

虚拟内存初探CSAPP

VM as a tool for caching CMU213-CSAPP-Virtual-Memory-Concepts | GreenHatHGのBlog 理解这个概念&#xff0c;就是说&#xff0c;VM是看作是独立与CPU和主存之外的disk&#xff0c;然后主存看成是这个虚拟地址数列的cache (DRAM就是物理的主存&#xff09; 重新用自己的…

大规模异构图召回在美团到店推荐广告的应用

总第530篇2022年 第047篇美团到店推荐广告团队在图神经网络的长期落地实践中&#xff0c;思考分析了场景的特点与挑战&#xff0c;针对性地进行了模型设计&#xff0c;并通过大规模训练工具及线上部署优化多次成功落地&#xff0c;带来了线上收入提升。本文主要介绍了大规模图召…