(11)C++多态与虚函数

大家好嘿呀!♥
这是一篇超细的博文哦~♥
虽然写的不怎么样,还很乏味!♥
但我真的真的真的是用心写的!♥
还请大家多多包涵♥
另外,大家可以叫我小小强哦~♥
不是打不死的小强!是小小强!♥
不多说了,奔主题吧!♥


多态与虚函数初步


多态:父类的一个指针,可以有多种执行状态或结果,即多态
1.多态与虚函数的关系:
多态是一种泛型编程思想,虚函数是实现这个思想的语法基础
(类比类和面向对象的关系)
泛型编程思想:同样的代码实现不同功能;宏观体现:父类的指针,调用子类的函数 。
2.父类指针调用子类函数,代码实现:
思路:
在父类声明一个指针,指向子类的函数

Cfarther *farther = new Cson;
farther->print1();//只能调用父类函数成员

farther指针只能读父类的成员,指针如何读空间完全由类型决定
普通的只能调用父类的成员 ,要想调用子类的就要用到虚函数了。
3.虚函数:
(1)关键字:virtual

class Cfarther
{
public:

	virtual void print()
	{
		cout << "Cfarther" << endl;
	}
};
class Cson :public Cfarther
{
public:
	int a; 
	void print()
	{
		cout << "Cson" << endl;
	}
};
//主函数调用
Cfarther *farther = new Cson;
farther->print();//只能调用父类函数成员

(2)注意:
a.在父类的函数中加一个virtual,就会调用子类的函数了(前提:两类中的函数名一样)
b.若有多个子类的函数名一样,则父类指针指向谁的空间,就调用谁的函数Cfarther *farther = new Cson;
c.多态针对于指针对象
虚函数特点
1.重写:
a.在覆盖的基础上,加上virtual虚函数,两函数间就构成了重写的关系,子类去重写父类
b.重写是针对于虚函数的,覆盖是针对于普通函数的,对数据成员没任何效果
c.子类重写的函数,默认是虚函数,virtual可加,可不加
测试:再定义一个子类的子类测试
d.构成重写关系前提:几个类中函数的返回值,参数,函数名,必须相同
特殊情况:协变

class Cfarther
{
public:
	virtual Cfarther& print()
	{
		cout << "farther" << endl;
		return (*this);
	}
};
class Cson : public Cfarther
{
public:
	Cson& print()
	{
		cout << "son" << endl;
		return (*this);
	}
};

即,函数名都是引用
2.虚函数不能是内联函数
在编译时会成功,系统自动去掉了内联
3.构造函数不能是虚函数(内联是构造函数唯一的合法存储类)

虚表(多态与虚函数的实现原理)

虚表:虚函数列表,装虚函数地址的列表
1.如果子类有重写,创建对象的时候,就会覆盖掉父类中同名的虚函数地址,调用时,通过虚函数地址调用。
2.每个类都会有自己的一个虚函数列表
3.过程:
根据父类指针找到父类函数,看父类的函数是不是虚函数,是的话就进到虚表中,找到虚函数名,执行表中的函数(自己或重写),不是就执行自己。
执行子类时的虚函数,看与父类的虚函数名是否相同,相同就是重写,就把子类的虚函数地址覆盖到父类的虚函数地址。
4.取虚表地址及内容
(1)对象空间的最开始四字节内容,就是虚表(虚函数列表)的地址,叫虚指针;虚表地址:对象地址转成int*再取首地址,即虚表地址

//全局区如上:
//主函数调用
	Cfarther* farther = new Cson;
	*(int*)farther;  //这就是虚表4字节的的地址,强转4字节,再取

(2)虚表里的每个函数元素都是四字节,由于函数类型都有可能是不一样的,所以无法通过地址增量来偏移。
函数地址强转,利用虚表的函数地址取函数内容:

void we() {}//函数名换成*p

函数指针类型:void(*p)()
虚表元素都是函数指针类型,所以无法通过首地址偏移来取其他元素
所以要想得到元素,就要进行函数的强制转换:
将虚表的首地址强转成int*即虚表就成了int*型的指针数组了,每个元素都是指针类型的int*,int*再取*就得到元素的内容了,即函数地址
总体代码步骤如下:
1)对象地址的前四字节为虚表地址,将对象首地址转成int*,取到对象首地址的前4个内容,即就是虚表的首地址,如下

*(int*)farther;

2)此时虚表的类型并不知道是什么,元素是函数地址,函数的类型也是不通的。所以虚表首地址不能偏移取其他元素,想能偏移取元素就把虚表首地址转成int*型,然后虚表就成了(int*)类型的指针数组了,每个元素都是4字节的int*如下

 	(int*)*(int*)farther;  //再取(int*)虚表首地址就变成int*了

3)变成int*后,就可以用地址偏移来取其他地址了

	((int*)*(int*)farther + 0); //这就是第一个元素函数的地址
*((int*)*(int*)farther + 0); //再加个地址操作符就取到了元素内容了
//此时的元素内容即是函数的地址

4)加0,是第一个元素;加1是第二个元素;以此类推,不过虚表结尾还有0x00000,也占一个元素
在这里插入图片描述
5)通过地址,调用函数
下面这种是int*型的函数地址,还要强转成函数类型的地址

*((int*)*(int*)farther + 0);

那如何强转成函数类型的地址呢?
声明函数指针:函数指针类型:typedef void (*p)()


typedef

将变量名定义成新的变量名,如

int a;//———— 传统变量声明表达式
int myint_t;// ———— 使用新的类型名myint_t替换变量名a
typedef int myint_t; //这样就被替换了

此刻的p就是指向函数的指针,p装的就是函数类型的函数地址了


typedef就是将函数指针类型命名为p;
函数地址整体再强转成p类型的,即就是函数类型的函数地址了,如下:
此时的p装的就是函数类型的函数地址

(p)(*((int*)*(int*)farther + 0));

通过函数地址调用函数

((p)(*((int*)*(int*)farther + 0)))(); 
//函数地址后加个圆括号就是调用函数了

总代码奉上♥

#include<iostream>
using namespace std;
class Cfarther
{
public:
	virtual void print()
	{
		cout << "farther" << endl;
	}
};
class Cson:public Cfarther
{
public:
	virtual void print()
	{
		cout << "son" << endl;
	}
};
int main()
{
	typedef void(*p)();
	Cfarther* farther = new Cson;
	((p)(*((int*)*(int*)farther + 0)))(); 
	//取farther里的print,又是虚函数,就直接打印son
	system("pause");
	return 0;
}

虚析构


析构函数:

#include<iostream>
using namespace std;
class Cfarther
{
public:
	~Cfarther()
	{
		cout << "~Cfarther" << endl;
	}
};
class Cson :public Cfarther
{
public:
	~Cson()
	{
		cout << "~Cson" << endl;
	}
};
//主函数调用
	Cfarther*farther = new Cson;
	delete farther;

这样只是调用了~Cfarther()里的内容,那如何将两个类的析构函数都调用呢?
这就用到虚析构了。

virtual ~Cfarther(){}
virtual ~Cson(){}

调用结果:

~Cson
~Cfarther

//注意: 1.delete哪个类型的指针,就调用哪个的析构函数---------------------- 2.不过释放的空间还是farther的,只是调用的析构变了
测试:delete (Cson*)farther //强转成Cson型的指针

class Cgson //: public Cson
{
public:
	  ~Cgson()
	{
		cout << "~Cgson" << endl;
	}
};
//主函数调用
		Cfarther*farther = new Cson;
		delete (Cgson*)farther;
//结果:打印~Cgson  释放空间farther
//对象不会被泄露,对象成员申请的空间有可能会被泄露	

纯虚函数


在虚函数的基础上加了赋值 0
代码演示:

class Cfarther
{
public:
	virtual void print() = 0; //在类中没有实现 不能实际化对象	
};

特点:
1.在类中无实现,
即无代码块
2.不能实例化对象,即

Cfarther farther;

即使在类外Cfarther::print()也是不行的
那如何定义对象呢??
继承这个父类的子类,必须实现它,才能定义对象(自己不能实现,要靠子类实现)

class Cfarther
{
public:
	virtual void print() = 0; //在类中没有实现 不能实际化对象	
};
class Cson:Cfarther
{
public:
	virtual void print()
	{
		cout << "Cson" << endl;
	}
};

注意:
1.有纯虚函数的类,必须用子类重写该纯虚函数,才能实例化对象。
2.有纯虚函数的类,就算重写,也不能创建对象
3.在子类中,若不重写则子类也不能创建对象了,原因是继承之后,子类也有一个纯虚函数了。

纯虚函数分类

抽象类 :只要有纯虚函数就叫抽象类
特点:不能实例化,必须通过子类重写,才能通过子类实例化对象
类内还有普通函数

class Cfarther
{
public:
	void pr(){}
	virtual void print() = 0; //在类中没有实现 不能实际化对象	
};

接口类:全都是纯虚函数,类内无普通函数
作用:与外设连接的接口

class Cfarther
{
public:
	virtual void pr() = 0;
	virtual void print() = 0; //在类中没有实现 不能实际化对象	
	virtual ~Cfarther() = 0;//虚析构也可以是纯虚函数
};

虚继承


1.概念:虚继承基于多继承,有多继承,虚继承才会有意义
是为解决多继承问题而出现的

多继承:
一个子类继承多个父类
class CD :public CB ,public CC如此样就是多继承

那什么时候要用到虚继承呢?请看下面代码

#include<iostream>
using namespace std;
class CA
{
public:
	void print()
	{
		cout << "CA" << endl;
	}
};
class CB:public CA
{
public:
};
class CC:public CA
{
public:
};
class CD :public CB ,public CC
{
public:
};
int main()
{
	CD D;
	D.print();  //会报错,print()访问不明确

	system("pause");
	return 0;
}

上面代码具体解析:

A类把print继承给B,C类。然后B,C类内都有一个print
之后B,C把print继承给D,此时的D类就有两个print了,这就造成访问不明确的情况,这时就要用到虚继承了

2.形式

class CB:virtual public CA
class CC:virtual public CA

在继承的位置加上virtual
原理:加上virtual后C空间里是没有print()的,它不占空间,仅仅是继承过来的使用权,B同样也是。他俩都继承给D,D也仅仅是对A内的print()有使用权,并不存在。即在整个过程中仅有一份A中的print()
3.总结
(1)父类叫做虚父类
(2)解决访问不明确的问题
(3)虚继承仅仅是赋予使用权

|♥ |
|♥ |
|♥ |
到底啦!!!!
♥♥
坚持看完的你,真的是能掀起计算机界的浪潮的!!
♥♥
继续加油哦!!
♥♥
让我们在最高处相见吧!♥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐鑫本鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值