IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 深入C++虚函数表 -> 正文阅读

[C++知识库]深入C++虚函数表

文章目录

什么是多态?

多态的分类:

?运行时的多态

?虚函数的定义:

运行时的多态(晚绑定)

虚函数注意:

虚函数:

前提知识:函数指针的判别

重载、覆盖(重写)、隐藏(重定义)的对比?

?虚函数表

单一继承,无虚函数覆盖:?

单一继承,有覆盖:

多继承下,无覆盖:

多继承下,有覆盖:


什么是多态?

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

? ? ? ? 多态性是考虑在不同层次的类中,以及在同一类中,同名的成员的关系问题。? ??

? ? ? ? 说白了就是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。

多态的分类:

? ? ? ? 多态又分为编译时的多态性(静态的多态性),函数重载,运算符的重载都属于编译时的多态

? ? ? ? 以类的虚成员函数为基础的运行时的多态

?运行时的多态

?虚函数的定义:

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

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

? ? ? ? 关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如果虚函数在类外定义,则不可加virtual。当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中该函数始终保持虚函数的特征。

? ? ? ? 注意:运行时的多态性:公有继承+基类和子类有的同名同参的虚函数+基类的指针或者引用指向基类对象或者派生类对象

运行时的多态(晚绑定)

定义父类A,子类B继承A,编写测试用例父类对象传入指针,引用

class A
{
public:
	virtual void fn()
	{
		cout << "A::fn" << endl;
	}
};
class B :public A
{
public:
	virtual void fn()
	{
		cout << "B::fn" << endl;
	}
};
void test1(A a)
{
	a.fn();
}
void test2(A& pa)
{
	pa.fn();
}
void test(A* pa)
{
	pa->fn();
}
void main()
{
	A a;
	B b;
	a.fn();//a直接调用fn
	b.fn();//b直接调用fn
	test(&a);//传入a对象地址,找到A下虚表
	test(&b);//传入b对象地址,找到B下虚表
	test2(a);//同理,找到相应类下虚指针,指向所对应虚表,调用对应虚函数
	test2(b);
}

虚函数注意:

虚函数的默认参数是静态绑定的,在重新定义虚函数时候,不重新定义继承而来的参数值
除非在调用时候实际的传递想要的参数

class Parent
{
public:
	virtual void fn(int a = 10)
	{
		cout << "parent fn a = " << a << endl;
	}
};
class Child :public Parent
{
public:
	virtual void fn(int b = 20)
	{
		cout << "child fn b = " << b << endl;
	}
};
void main()
{
	Child cc;
	Parent* p = &cc;
	p->fn();      //child fn b=10 默认参数静态绑定
	p->fn(100);    //child fn b=100
}

给一个例题看看输出结果:?

class Parent
{
public:
	void print()
	{
		cout << "parent print" << endl;
		test();
	}
	virtual void test()
	{
		cout << "parent test" << endl;
	}
};
class Child :public Parent
{
public:
	void show()
	{
		cout << "Child show" << endl;
		print();
	}
	virtual void test()
	{
		cout << "child test" << endl;
	}
};
void main()
{
	Child cc;
	cc.show();
}
理解函数调用顺序
运行结果:
Child show
parent print
child test

虚函数:

注意以下几点:

  1. 派生类中定义虚函数必须和基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外
  2. 只有类的成员函数才能说明为虚函数,这是因为虚函数仅仅适用于有继承关系的类对象。友元函数和全局函数不能作为虚函数
  3. 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。
  4. 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。
  5. 构造函数和拷贝构造函数不能作为虚函数。构造函数和拷贝构造函数是设置虚表指针。
  6. 析构函数可以定义为虚函数,构造函数不能定义为虚函数,因为在调用函数构造时对象还没有完成实例化(虚表指针没有设置)。在基类中及其派生类中都有动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性。
  7. 实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针向基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。
  8. 函数执行速度要稍微慢一些,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总要是付出一定代价,但通用性是一个更高的目标。
  9. 如果定义放在类外,virtual只能加在函数声明前面,不能加在函数定义前面,正确的定义必须不包括virtual。

前提知识:函数指针的判别

void ( *signal( int, void (*func) ( int, int )) ) ( int );
//signal是个函数名,往左看,是个*,说明返回值是个指针,向右看,右边是个(int),说明这个指针指向一个函数,再往左看,左边是函数的返回值类型
//函数指针函数

void fn()
{
	cout << "fn" << endl;
}
void main()
{
	void (*p)(); //函数指针---右左法则
	void (*q[3])();//找到q,向右看是个数组,所以q是个数组名,往左看,左边是个*(说明是指针),也就是说数组q中有3个指针,再继续向右看,右边是个(),说明是函数
	//函数指针数组 q[0],q[1],q[2]
	fn(); //地址
}
int max(int a, int b)
{
	return a > b ? a : b;
}
int min(int a, int b)
{
	return a < b ? a : b;
}
int Add(int a, int b)
{
	return a + b;
}
void main()
{
	int (*pf[3])(int, int) = { max,min,Add };
	//pf[0]指向的是max,pf[1]指向的是min,pf[2]指向的是Add
	for (int i = 0; i < 3; i++)
		cout << pf[i](2, 4) << endl;;
}

重载、覆盖(重写)、隐藏(重定义)的对比?

?虚函数表

当类中存在虚函数时,就会产生虚函数指针vfptr和虚函数表vbtable

有以下概念:

  1. 一个类一张虚表,所有对象共享虚函数表vftable。
  2. 为了实现共享 :每一个对象中有一个指针,称为虚函数指针vfptr,指向虚函数表,这样就可以实现虚函数表的共享。系统会在每一个类中提供虚函数指针vfptr,我们看不见,所以一旦有virtual关键字,那么类的大小就会多加4个,多出来的就是虚函数指针的大小。
  3. 类中的布局:虚函数指针在前,其他成员变量在后,因为虚函数指针的布局优先级最高(在没有虚继承时)
  • 虚函数表结构,三部分:
  • RTTI:run-time type infornation运行时类型信息,在运行阶段提取出来的类型,可以使用typeid()函数获取。
  • 偏移:虚函数指针相对于整体作用域的偏移,用整体作用域-vfptr的位置得到。
  • 虚函数入口地址

单一继承,无虚函数覆盖:?

示例1:

/*只要有虚函数,不管有多少个虚函数,都只多了4个字节,多了一个Vfptr
vfptr虚指针指向了一个vftable,vftable中存储了当前类的虚函数的入口地址
*/
#if 0
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 fd() { cout << "A::fd" << endl; }
private:
	int m_i;
};
void main()
{
		 cout<<sizeof(A)<<endl;
		 A a;
		 a.fa();
		 a.fb();
	//A a;
	//typedef void (*Fun)();
	//Fun pf = NULL; //函数指针pf可以指向类A中的fa,fb,fc
	//pf = (Fun) * ((int*)(*(int*)(&a)));
	//pf(); //A::fa

	//pf = (Fun) * ((int*)*(int*)(&a) + 1);
	//pf(); //A::fb
	//pf = (Fun) * ((int*)*(int*)(&a) + 2);
	//pf(); //A::fc
}

示例2:

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
{
public:
	virtual void fb() { cout << "B::fb" << endl; }
	virtual void gb() { cout << "B::gb" << endl; }
};
void main()
{
	cout << sizeof(B) << endl;
	B b;
	typedef void (*Fun)();
	Fun pf = NULL; //函数指针pf可以指向类B中的fa,fb,fc,fb,gb
	pf = (Fun) * ((int*)(*(int*)(&b)));
	pf(); //A::fa
	pf = (Fun) * ((int*)*(int*)(&b) + 1);
	pf(); //A::ga
	pf = (Fun) * ((int*)*(int*)(&b) + 2);
	pf(); //A::ha
	pf = (Fun) * ((int*)*(int*)(&b) + 3);
	pf(); //B::fb
	pf = (Fun) * ((int*)*(int*)(&b) + 4);
	pf(); //B::gb
}

根据看监视:
每个类里面只有一个虚指针,指向一个虚标
发现B中的虚表中有5个虚函数,前三个是从A继承过来的虚函数的入口地址,后面两个是B类自己的虚函数?

内存分布如下

单一继承,有覆盖:

class A
{
public:
?? ?virtual void fa() { cout << "A::fa" << endl; }
?? ?virtual void fb() { cout << "A::fb" << endl; }
?? ?virtual void fc() { cout << "A::fc" << endl; }
};
class B :public A
{
public:
?? ?/*
?? ?覆盖 子类重写了基类的同名同参的virtual函数,在B类的对象模型中,在继承下来虚表的同一处地址修改成B的重写的函数
?? ?*/
?? ?virtual void fa() { cout << "B::fa" << endl; }
?? ?virtual void gb() { cout << "B::gb" << endl; }
private:
?? ?int m_i;
?? ?int m_j;
};
void main()
{
?? ?B b;?
}

多继承下,无覆盖:

对于多继承,每个父类都有自己的虚表
将最终子类的虚函数放在第一个父类的虚表中
这样做解决了不同的父类类型的指针指向比较清晰

class A
{
public:
	virtual void fa() { cout << "A::fa" << endl; }
	virtual void ga() { cout << "A::ga" << endl; }
};
class B
{
public:
	virtual void fb() { cout << "B::fb" << endl; }
	virtual void gb() { cout << "B::gb" << endl; }
};
class C
{
public:
	virtual void fc() { cout << "C::fc" << endl; }
	virtual void gc() { cout << "C::gc" << endl; }
};
class D :public A, public B, public C
{
public:
	virtual void fd() { cout << "D::fd" << endl; }
	virtual void gd() { cout << "D::gd" << endl; }
};

void main()
{
	D d;
	cout << sizeof(D) << endl;
}

监视其内存:

多继承下,有覆盖:

不相同的子类虚函数会贴在第一个父类虚表后面的位置。

相同的全部覆盖。

class A
{
public:
	virtual void f() { cout << "A::f" << endl; }
	virtual void ga() { cout << "A::ga" << endl; }
};
class B
{
public:
	virtual void f() { cout << "B::f" << endl; }
	virtual void gb() { cout << "B::gb" << endl; }
};
class C
{
public:
	virtual void f() { cout << "C::f" << endl; }
	virtual void gc() { cout << "C::gc" << endl; }
};
class D :public A, public B, public C
{
public:
	virtual void f() { cout << "D::f" << endl; }
	virtual void gd() { cout << "D::gd" << endl; }
};

void main()
{
	D d;
	cout << sizeof(D) << endl;
	//函数指针

	typedef void(*Fun)();
	//第一个虚表
	Fun pf = (Fun) * (((int*)*(int*)(&d)));
	pf();//D:f
	pf = (Fun) * (((int*)*(int*)(&d)) + 1);
	pf();//A:ga
	pf = (Fun) * (((int*)*(int*)(&d)) + 2);
	pf();//D:gd
	//第二个虚表
	pf = (Fun) * ((int*)*(int*)((int*)(&d) + 1));
	pf();//D:f
	pf = (Fun) * ((int*)*(int*)((int*)(&d) + 1) + 1);
	pf();//B:gb
	//第三个虚表
	pf = (Fun) * ((int*)*(int*)((int*)(&d) + 2));
	pf();//D:f
	pf = (Fun) * ((int*)*(int*)((int*)(&d) + 2) + 1);
	pf();//c:gc

}

?内存分布:

日常巩固复习

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:25:04  更:2022-08-06 10:29:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:28:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码