多态的原理、单继承和多继承的虚函数表、以及虚函数表的打印。

news/2024/10/18 0:21:42/

一、多态原理

1、下面这个结果是多少?

class A
{
public:virtual void func(){cout << "func()" << endl;}private:int _a = 1;
};int main()
{printf("%d\n", sizeof(A));return 0;
}

是 4?8?还是多少?打印结果如下:

 

为什么是 8 呢?,通过调试看一下,如下:

 通过调试可以看到里面不只是有 _a 成员,还多了一个成员 __vfptr,这个成员就是虚函数表指针,为什么会有虚函数表指针呢?因为虚函数的地址要存放到虚函数表中!并且含有虚函数的类中至少有一个虚函数表指针!

2. 上面的代码修改一下,如下

class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}virtual void Func2(){cout << "A::Func2()" << endl;}void Func3(){cout << "A::Func3()" << endl;}
private:int _a = 1;
};class B : public A
{
public:virtual void Func1(){cout << "B::Func1()" << endl;}
private:int _b = 2;
};int main()
{A a;B b;return 0;
}

调试看一下内部结构是什么,如下:

  

通过上面的调试可以看到:

(1) 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

(2) 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

(3)另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

(4)虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

(5)总结一下派生类的虚表生成:

        a.先将基类中的虚表内容拷贝一份到派生类虚表中 

        b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 

        c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

(6)那么虚函数存在哪的?虚表存在哪的? 
        虚函数存在虚表?虚表存在对象中?
        不对!!!虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。

通过上面的例子,下面正式看一下多态原理!!

上一篇文章讲了多态的条件,满足多态条件的时候,传给父类指针或者引用的时候,会完成不一样的调用,那么下面解释一下为什么,如下:

class Person {
public:virtual void BuyTicket() { cout << "买票全价" << endl; }
};class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票半价" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person q;Func(q);Student st;Func(st);return 0;
}

因为 q 传给 p 引用的时候,p 是指向 q 对象的,所以发生了切片,但是又因为都是Person这个类的,所以调用了自己的方法。

st 传给 p 引用的时候,p 是指向 st 对象的,student 又继承了Person类,并且重写了虚函数,形成了多态,但是 st 对象里面的虚函数是重写了之后的!而不是继承下来的虚函数!可以调试查看一下,如下:

虽然发生切片, p 指向 st 对象中 Person 前面的地址,但是因为是方法是重写的,所以调用的是student 的方法!

下面汇编代码看一下,如下:

 多态的时候 call 的时候,call 的是寄存器!具体细节不展开讲了。

总之多态调用的时候,运行时去指向对象的虚表中找虚函数地址,进行调用!

那么不构成多态是什么呢?

class Person {
public:virtual void BuyTicket() { cout << "买票全价" << endl; }
};class Student : public Person
{
public://virtual void BuyTicket() { cout << "买票半价" << endl; }virtual void Buy() { cout << "买票半价" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person q;Func(q);Student st;Func(st);return 0;
}

我随便破坏一个条件已经不构成多态,那么调用的时候是什么样呢? 如下:

可以看到 call 的直接是函数地址! 

因为不满足多态的话就是普通函数调用,编译链接时候,就确认了函数地址,运行时直接调用。

二、单继承和多继承的虚函数表。

// 单继承
// 打印虚函数表
class Person
{
public:virtual void BuyTicket(){cout << "Person::BuyTicket()" << endl;}virtual void func_person(){cout << "Person::func_person()" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "Student::BuyTicket()" << endl;}// 监视窗口查看 s 的虚表 里面查看不到 func_student() 函数virtual void func_student(){cout << "Person::func_student()" << endl;}
};typedef void(*VFTABLE)();void PRINT_VFTABLE(VFTABLE table[], int n)
{//for (int i = 0; table[i] != nullptr; i++) // vs 在后面默认给的 0 ; 所以可以用 table[i] != nullptr; linux 需要显示写打印几个for (int i = 0; i < n; i++){printf("table[%d]->%p::", i, table[i]);VFTABLE pf = table[i];pf(); // 函数指针+() 调用自己对应的函数}cout << endl;
}//int main()
//{
//	Student s;
//
//	Person p;
//
//	//PRINT_VFTABLE((VFTABLE*)*(int*)&s);
//	PRINT_VFTABLE((VFTABLE*)*(int*)&s, 3); // 子类有 3 个 显示写打印 3 个
//	PRINT_VFTABLE((VFTABLE*)*(int*)&p, 2); // 父类有 2 个 显示写打印 2 个
//
//	return 0;
//}// 多继承
// 打印虚函数表class Base1
{
public:virtual void func1(){cout << "Base1::func1()" << endl;}virtual void func_Base1(){cout << "Base1::func_Base1()" << endl;}
};class Base2
{
public:virtual void func1(){cout << "Base2::func1()" << endl;}virtual void func_Base2(){cout << "Base2::func_Base2()" << endl;}
};class Base3 : public Base1, public Base2
{
public:virtual void func1(){cout << "Base3::func1()" << endl;}virtual void func_Base3(){cout << "Base3::func_Base3()" << endl;}
};typedef void(*VFTABLE)();void PRINT_VFTABLE(VFTABLE  table[])
{for (int i = 0; table[i] != nullptr; i++){printf("table[%d]->%p->", i, table[i]);VFTABLE pf = table[i];pf();}cout << endl;
}int main()
{Base3 b3;Base1 b1 = b3;Base2 b2 = b3;PRINT_VFTABLE((VFTABLE*)*(int*)&b1);PRINT_VFTABLE((VFTABLE*)*(int*)&b2);PRINT_VFTABLE((VFTABLE*)*(int*)&b3);return 0;
}

上面写了一个函数打印虚函数表,可以尝试调试打印看一下。 


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

相关文章

CSS3多列布局:达到无与伦比的网站排版

随着互联网技术的日益发展&#xff0c;网站排版也变得越来越重要。对于网站设计师而言&#xff0c;如何用 CSS3 实现美观、简洁、灵活的多列布局是一项必修的技能。在本篇文章中&#xff0c;我们将会一步步介绍 CSS3 多列布局的使用方法&#xff0c;带你完成一个完整的布局&…

npm i 常见问题

需要注意的是&#xff0c;如果你在使用 NPM 安装包的过程中遇到了任何问题&#xff0c;可以尝试使用 --verbose 参数打印更详细的错误信息&#xff0c;以便更好地诊断问题。例如&#xff1a; npm install --verbose 1、vue老项目缺少编译环境安装依赖报错的问题 待下载的项目…

Splunk安装配置

前言 Splunk 社区 &#xff0c;包括白皮书&#xff0c;各类手册&#xff0c;资源下载&#xff0c;社区问答等 入门&#xff1a;Splunk 入门指南 | Splunk 手册&#xff1a;Splunk Enterprise - Splunk Documentation 资源下载:数据可视化工具Splunk Enterprise免费下载 | S…

python绘制密度图

本期目录 1、绘图参数2、使用 matplotlib 库绘制密度图时常用的参数3、案例4、 运行结果python绘图往期系列文章目录 1、绘图参数 可以使用多种库来绘制密度图&#xff0c;其中最常用的是 seaborn 和 matplotlib。以下是使用 seaborn 库绘制密度图时常用的参数&#xff1a; i…

【网络编程】协议定制+Json序列化与反序列化

目录 一、序列化与反序列化的概念 二、自定义协议设计一个网络计算器 2.1TCP协议&#xff0c;如何保证接收方收到了完整的报文呢&#xff1f; 2.2自定义协议的实现 2.3自定义协议在客户端与服务器中的实现 三、使用Json进行序列化和反序列化 3.1jsoncpp库的安装 3.2改造…

如何用c++制作人生模拟器

要制作一个人生模拟器&#xff0c;首先需要设计游戏的基本框架&#xff0c;并构思游戏的玩法&#xff0c;规则和内容。 然后&#xff0c;在C中实现这个框架并添加游戏所需的各种类、函数和变量。其中&#xff0c;有几个关键的方面需要考虑&#xff1a; 模拟生命周期&#xff…

BigDecimal类型的数据如何保留小数点后四位

BigDecimal类型的数据如何保留小数点后四位 下面是使用Java的BigDecimal类来保留小数点后四位的示例&#xff1a; import java.math.BigDecimal; import java.math.RoundingMode;public class Main {public static void main(String[] args) {BigDecimal number new BigDecima…

日志模块封封装:单例模式+策略模式+构建者模式+bugly

日志模块封装:单例模式策略模式构建者模式bugly 一.单例模式策略模式构建者模式二.日志模块封装1.日志等级&#xff1a;LoggerLevel枚举类2.日志输出策略&#xff1a;LoggerStrategy枚举类3.ILogger接口4.LogCatLogger/FileLogger/NetWorkLogger/EmailLogger5.使用构建者模式和…