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++之多态(中篇)(最全总结)

这里接上面C++之多态(上篇)

4.多态的原理

4.2 多态的原理

从上面虚函数的分析中我们已经知道了多态的原理,接下来我们从更深层次去探索多态。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-全价" << endl;
	}
	virtual void Func1(){}
};

class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-半价" << endl;
	}
	virtual void Func2(){}
};

int main()
{
	//同一个类型的对象共用一个虚表
	Person p1;
	Person p2;

	//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个
	Student s1;
	Student s2;
	return 0;
}

在这里插入图片描述
通过上图我们发现,同一个类型的对象共用同一个虚表,vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个,除此之外,我们发现,vs的监视窗口下,子类自己的虚函数Func2(), 它是在子类自己的虚函数表中的,但是vs的监视窗口却没有显示出来,下面我们用一段程序将其展示出来。
虚表的本质是一个函数指针数组
在这里插入图片描述
在这里插入图片描述

通过上图我们发现对象虚表的地址在它对象的地址处的值取前4个字节即可。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "Person::买票-全价" << endl;
	}
	virtual void Func1()
	{
		cout << "Person::Func1()" << endl;
	}
};

class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "Student::买票-半价" << endl;
	}
	virtual void Func2()
	{
		cout << "Student::Func2()" << endl;
	}
};

typedef void(*VFPTR)();

//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{
	//for (size_t i = 0; table[i] != nullptr; ++i)
	for (size_t i = 0; i < n; ++i)
	{
		printf("vft[%d]:%p->", i, table[i]);
		//table[i]();
		VFPTR pf = table[i];//有点类似与强制类型转换
		pf();
	}
	cout << endl;
}

int main()
{
	//同一个类型的对象共用一个虚表
	Person p1;
	Person p2;

	//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个
	Student s1;
	Student s2;

	//取对象头部虚函数表指针传递过去
	//这里的2,3是我们知道对象的虚函数的个数
	PrintVFTable((VFPTR*)*(int*)&p1,2);
	PrintVFTable((VFPTR*)*(int*)&s1,3);
	return 0;
}

在这里插入图片描述

4.3 C++ 11 override和final

从上面可以看出,C++对函数的重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名的字母次序写反而无法构成重载,而这种错误在编译期间是不会报出来的,只有程序运行时没有得到预期结果才来debug会得不偿失,因此:C++ 11提供了override和final这两个关键字,可以用来帮助用户检测是否重写。

1.final:修饰虚函数,表示该虚函数不能被重写

class Car
{
public:
	virtual void Drive()final{}
};

class Benz :public Car
{
public:
	virtual void Drive() //error
	{
		cout << "Benz-舒适" << endl;
	}
};

2.override:检查派生类虚函数是否重写基类某个虚函数,如果没有编译报错

class Car
{
public:
	virtual void Drive(){}
};

class Benz :public Car
{
public:
//检查子类虚函数是否完成重写
	virtual void Drive()override
	{
		cout << "Benz-舒适" << endl;
	}
};

4.4 重载、重写(覆盖)、隐藏(重定义)的对比 (函数之间的关系)

在这里插入图片描述

5.抽象类

5.1概念

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

class Car
{
public:
	virtual void Drive() = 0;
};

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};

class BwM:public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};

int main()
{
	//Car c;//抽象类不能实例化对象
	//BwM b;

	Car* ptr = new BwM;
	ptr->Drive();//多态的体现

	ptr = new Benz;
	ptr->Drive();
	return 0;
}

在这里插入图片描述

5.2接口继承和实现继承

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

6.单继承和多继承关系的虚函数表

需要注意的是在单继承和多继承关系中, 下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的。

class Base1
{
public:
	virtual void func1()
	{
		cout << "Base1::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base1::func2" << endl;
	}
private:
	int b1 = 1;
};

class Base2
{
public:
	virtual void func1()
	{
		cout << "Base2::func1" << endl;
	}
	virtual void func2()
	{
		cout << "Base2::func2" << endl;
	}
private:
	int b2 = 2;
};


class Derive :public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}

	virtual void func3()
	{
		cout << "Derive::func3" << endl;
	}
private:
	int d = 3;
};

typedef void(*VFPTR)();

//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{
	//for (size_t i = 0; table[i] != nullptr; ++i)
	for (size_t i = 0; i < n; ++i)
	{
		printf("vft[%d]:%p->", i, table[i]);
		table[i]();
		VFPTR pf = table[i];//有点类似与强制类型转换
		pf();
	}
	cout << endl;
}

int main()
{
	Derive d;

	PrintVFTable((VFPTR*)*(int*)&d,3);//打印Base1虚函数表
	PrintVFTable((VFPTR*)*(int*)((char*)&d + sizeof(Base1)), 2);//法一:打印Base2虚函数表
	//法二:打印Base2虚函数表
	Base2* ptr2 = &d;//切片得到Base2的地址
	PrintVFTable((VFPTR*)(*(int*)ptr2), 2);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
更深层次的问题
观察上图我们发现Base1中的func1和Base2中的func1都被Derive进行了重写,它们的内容是一样的,应该指向同一份函数,但是它们的地址为什么不一样呢?

下篇文章我们会揭晓!!!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:47:31  更:2022-12-25 10:49: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/22 18:56:31-

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