多态与虚函数

news/2024/11/24 13:24:55/

多态与虚函数

  • 多态的引入
  • 多态与虚函数
    • 多态
    • 编译时多态
    • 运行时多态
  • 多态的原理
  • 静态联编和动态联编

多态的引入

学过C++继承的话应该都知道在继承中存在一种菱形继承,假设存在一个类(person),其派生出两个子类,分别是student类和Migrant类,我们但是这两个子类共同同派生出一个MigStu的类,我们可以通过画图来理解一下,
在这里插入图片描述
我们画出他的继承图会发现其是一个菱形,我们再画出他的内存分布图在这里插入图片描述
会发现存在两个person类,那么其中的公共成员变量就会发生冲突,比如学生是男性,打工人是女性,那么我们的在校打工人是男是女,这很显然发生了冲突。为了解决这个问题C++引入了virtual关键字,在person中我们加入virtual关键字之后,就会只在类中生成一个person对象,在学生和打工人的结构中原本存放person的地方会存在一个指针,指向我们的person类,也就解决了冗余的问题。但是菱形继承的底层实现极为复杂,所以现在很少设置这种菱形继承。而我们使用virtual继承基类,被称为虚基类,但是其并非基类是虚的,而是继承方式是虚的,而使用多态时,便使用的是虚函数,同样时virtual关键字。

多态与虚函数

多态

多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,则不能称为面向对象语言。多态性是考虑在不同层次的类中,同名的成员函数之间的关系。函数的重载,运算符的重载,属于编译时的多态性。以类的虚成员函数作为基础运行时的多态性是面向对象程序设计的标志性特征。
多态分为编译时多态和运行时多态

编译时多态

编译时多态也就是函数重载,函数名相同,参数不同,其本质上是代码在编译时期对函数进行了名字粉碎技术,将函数名,参数,返回值做了一个粉碎,导致其在运行时编译器认为其名字不相同从而实现函数重载,

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;}

运行时多态

在一些类中会存在一些共同的特性,为此会设置一个基类将所有的共性存放到这个类中,并将这些共性设置成虚函数,在派生类中再一次实现具体的操作,这样就避免了代码的复用,并且使得代码更容易维护,也就是说以后有其他类的话直接加入派生类中即可。在调用时必须使用指针或者引用才可以将虚函数绑定到派生类的对象上重写虚函数。

#include <iostream> 
using namespace std;class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}virtual int area(){cout << "Parent class area :" <<endl;return 0;}
};
class Rectangle: public Shape{public:Rectangle( int a=0, int b=0):Shape(a, b) { }int area (){ cout << "Rectangle class area :" <<endl;return (width * height); }
};
class Triangle: public Shape{public:Triangle( int a=0, int b=0):Shape(a, b) { }int area (){ cout << "Triangle class area :" <<endl;return (width * height / 2); }
};
// 程序的主函数
int main( )
{Shape *shape;Rectangle rec(10,7);Triangle  tri(10,5);// 存储矩形的地址shape = &rec;// 调用矩形的求面积函数 areashape->area();// 存储三角形的地址shape = &tri;// 调用三角形的求面积函数 areashape->area();return 0;
}

使用多态时我们必须存在三个条件:

  • 必须是公有继承
  • 必须加入virtual
  • 必须使用指针或者引用来调用
    定义虚函数需要注意:
  • 派生类中定义虚函数必须与基类中的虚函数同名,同参数表,同返回类型。否则会被认为是同名覆盖,不具有多态性,但存在一个例外:基类中返回基类指针,派生类中返回派生类指针(协变)。
  • 只有类的成员函数才能说明是虚函数,因为虚函数仅使用于有继承关系的类对象。有缘函数和全局函数都不能作为虚函数
  • 构造函数和拷贝构造函数不能作为虚函数,拷贝函数和拷贝构造函数是设置虚表指针。
  • 析构函数可以定义为虚函数,构造函数不能定义为虚函数,因为在调用构造函数时对象还没有完成实例化(虚表指针没有设置)。在基类中及其派生类中都动态分配内存空间时,必须把析构函数定义为虚函数,实现撤销对象的时的多态性。
  • virtual之正在函数声明前加,不能再函数定义时加入,正确的定义必须不包括virtual,函数默认参数值也必须在声明时加入,定义时不能加入。

多态的原理

多态的原理就是虚函数表简称为虚表,虚表就是虚函数指针的集合,虚函数指针表本质上是一个存储虚函数指针的指针数组,这个数组的首元素之上存储RTTI(运行时类型识别信息的指针),从数组下标开始依次存储虚表地址,最后放了一个nullptr,虚表存放于只读数据段,其在编译时确定,并且一个类只存在一个虚表。

class object{
private: int value;
pubic:
object(int X = 0) :value(x) {}
virtual void add() { cout << "object: :add()" << end1; }
virtual void fun() { cout << "object: :fun()" << endl; }
virtual void print() const
{ cout << "object::printO" << end1; }
};
class Base: pub1ic object
private:
int num;
public:
Base(int x = 0) :object(x) ,num(x + 10) {}
virtual void add() { cout << "Base: :add()" << end1; }
virtual void fun() { cout << "Base: :fun()" << end1; }
virtual void show() { cout << "Base::show()" << end1; }
};
class Test : public Basve{
private:
int count;
public:
Test(int x = 0) :Base(x), count(x + 10) {}
virtual void add() { cout << "Test: :add()" << end1; }
virtual void print() const
{ cout << "Test: :print()" << end]; }
virtual void show() { cout << "Test: :show()" << end1; }
};
int main(){
object *op = nu11ptr;
objece obj;
Base base;
Test test;
op = &test;
op->print() ;
return 0;
}

我们观察上面代码,化出虚表内存分布图以及虚表图如下:
在这里插入图片描述
如图便是三个类的虚表,实在编译时就确定了的,例如obj派生出了Base类,所以Base的虚表就是将obj的虚表拷贝了一份然后进行同名覆盖,而在创建对象的时候,我们以创建Test对象为例,创建该对象首先创建Base,而创建Base需要创建obiect,而当类中有虚函数时类的大小就会多4(32位)字节,用于存放指向虚表的指针,创建Test时,指针首先指向object的虚表,然后指向Base的虚表,最后指向Test的虚表,根据其构建顺序。这个过程是在运行时进行的,虚表创建在编译时。

静态联编和动态联编

其实两者的差异就在于关联(函数实现和函数调用关联)的时期不一样,静态联编在编译时就确定了关联,而动态联编是在运行时确定的关联关系。
静态关联就是用直接用对象调用函数,动态联编就是用指针调用函数,注意虚表指针是否指向虚表。
C++中函数重载,函数模板都是静态联编,使用指针引用都是动态联编


http://www.ppmy.cn/news/68500.html

相关文章

ModuleNotFoundError: No module named ‘Multiscaledeformableattention‘

在实现DINO Detection方法时&#xff0c;我们可能会遇到以上问题。因为在DeformableAttention模块&#xff0c;为了加速&#xff0c;需要自己去编译这个模块。 如果你的环境变量中能够找到cuda路径&#xff0c;使用正确的torch版本和cuda版本的话&#xff0c;这个问题很容易解…

C++的string类使用介绍

string类 1.为什么要学习string类&#xff1f;1.1.C语言中的字符串1.2. 日常中 2. 标准库中的string类2.2 string类(对于单字节的字符)的常用接口说明①string常见的构造函数②string类对象的容量操作③string类对象的访问以及遍历操作④string类对象的修改操作⑤string类非成员…

《测试开发》测试启蒙

文章目录 测试是什么调试和测试的区别 什么是需求需求的定义需求的特征测试人员眼里的需求是什么如何深入了解需求 测试用例什么是测试用例为什么有测试用例 bug如何描述一个bug如何定义bug的级别bug的生命周期 软件测试的生命周期 &#x1f451;作者主页&#xff1a;Java冰激凌…

作业 编程读写-一个文件test.txt ,每隔1秒向文件中写入年月日时分秒行 数据。5.12

题目要求:编程读写-一个文件test.txt ,每隔1秒向文件中写入行数据,类似这样: 1&#xff0c; 2007-7-30 15:16:42 2&#xff0c; 2007-7-30 15:16:43 该程序应该无限循环&#xff0c;直到按Ctr1-C中 断程序。 再次启动程序写文件时可以追加到原文件之后&#xff0c;并且序号能够…

Socket请求和Http请求区别和场景

我们在开发过程中遇到http请求和socket请求。大部分前后交互都是通过http请求的方式&#xff0c;那socket请求怎么使用&#xff0c;什么情况下使用呢&#xff1f; 基本概念 http请求&#xff1a;基于http协议的soap协议&#xff0c;常见的http数据请求方式有get和post&#xf…

报表开发组件FastReport Mono v2023.1 - 支持与My Reports Cloud集成

FastReport Mono v2023.1现已推出! 最新版中更新了与 My Reports Cloud 的部分集成、来自 JasperReports 的模板转换器等功能&#xff0c;同时修复了10余处问题&#xff0c;点击下方免费试用哦~ FastReport Mono v2023.1现已推出! 今天将为大家带来FastReport Mono v2023.1更新…

Fastjson 的使用总结

一.JSON 介绍 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 JSON建构于两种结构&#xff1a; “名称/值”对的集合&#xff08;A collection of name/value pairs&#xff09;。不同的语言中&#xff0c;它…

【FMC136】AD9467之4通道 250MSPS 采样率16位AD 采集子卡模块得设计原理图中文资料

板卡概述 FMC136 是一款4 通道250MHz 采样率16 位AD 采集FMC子卡&#xff0c;符合VITA57 规范&#xff0c;可以作为一个理想的IO 模块耦合至FPGA前端&#xff0c;4 通道AD 通过高带宽的FMC 连接器&#xff08;HPC&#xff09;连接至FPGA 从 而大大降低了系统信号延迟。该板卡支…