C++语言学习笔记6

多态定义及产生的条件

#include <iostream>
using namespace std;

//	多态:	相同的行为方式导致了不同的行为结果,即为多态性。在程序上,也就是同一行语句展现了多种不同的表现形态。
//			父类指针可以指向任何继承于该类的子类,且具有子类的行为方式,多种子类具有多种形态,由父类指针进行统一管理,父类指针具有多种形态。

//多态产生的条件:1. 必须有继承关系存在(父类、子类);2. 父类的指针指向子类的对象;3. 子类必须重写父类的虚函数

//重写:子类中存在和父类函数一模一样(函数名 参数列表 返回值 都相同)的虚函数

class CFather
{
public:
	//void show() {
	//	cout << "void show()" << endl;
	//}

	virtual void show() {//	virtual:虚函数的关键字
		cout << "void show()" << endl;
	}
protected:
private:
};

//class CSon1 :CFather	//默认继承是私有的
class CSon1 :public CFather
{
public:
	void show() {
		cout << "CSon1 show()" << endl;
	}
protected:
private:
};

class CSon2 :public CFather
{
public:
	void show(int a) {	//这个就不能算是重写 其参数列表和父类不一样
		cout << "CSon2 show()" << endl;
	}
protected:
private:
};

int main() {

	CFather* pFather = new CSon1;
	pFather->show();

	CFather* pFather1 = new CSon2;
	pFather1->show();

	system("pause");
	return 0;

多态原理

#include <iostream>
using namespace std;

class CTest {
public:
	int m_a;
	int m_b;
public:
	CTest() {
		cout << "CTest" << endl;

	}
public:
	virtual void show() {
		cout << "void show()" << endl;
	}
	virtual void play() {
		cout << "void play()" << endl;
	}
	void code() {
		cout << "void code()" << endl;
	}
};

//	一个类中的虚函数由一个虚函数列表进行管理,该虚函数列表为一个数组,数组的每个元素是一个函数指针,函数指针指向虚函数,虚函数指针将在构造初始化列表中进行初始化,指向虚函数列表,虚函数指针是属于对象的,只有定义对象了才会存在。
//	在调用虚函数时,需要使用虚函数指针(指向虚函数列表)来遍历虚函数列表,进行查找对应的函数指针,然后用函数指针去调用具体的虚函数。
//	虚函数列表属于类,编译期便存在
//	__vfptr  虚函数指针; vftable	虚函数列表

int main() {

	cout << sizeof(CTest) << endl;
	//空类大小输出为占位1,类中有1个虚函数后输出4,有2个虚函数也输出4,有2个虚函数+1个正常函数的情况下依然大小输出为4
	//说明:普通函数不影响类的大小;类中存在1个或多个虚函数时,定义对象时分配的空间将多出4个字节。

	/*CTest tst1;

	tst1.play();
	tst1.code();*/

	CTest* pTst = NULL;	//空类指针
	pTst->code();	//直接通过类指针调用普通函数
	//pTst->play();	//该句无法编译通过 因为没有对象亦没有虚函数指针 无法通过空类指针调用虚函数
					
					//可作如下修改:
	CTest tst2;
	CTest* pTst1 = &tst2;
	pTst1->play();

	cout << sizeof(CTest) << endl;

	CTest tst;
	cout << &tst << endl;	
	cout << &tst.m_a << endl;	//首地址和m_a的地址差4,实际上就是虚函数指针的大小(虚函数指针在定义对象的空间的前4个字节)	
	cout << &tst.m_b << endl;


	system("pause");
	return 0;
}

继承关系下多态发生的原理

class CFather
{
public:
	CFather() {
		cout << "father" << endl;
	}
	virtual void show() {
		cout << "CFather::show" << endl;
	}
	virtual void code() {
		cout << "CFather::code" << endl;
	}
protected:
private:
};

class CSon :public CFather
{
public:
	CSon() {
		cout << "son" << endl;
	}
	virtual void play() {
		cout << "CSon::play" << endl;
	}
	/*virtual*/ void show() {	
		//覆盖:子类的虚函数重写了父类的虚函数,在虚函数列表中会替换掉父类的虚函数
		//同时,由于子类重写父类的虚函数,子类前无论加不加虚函数关键字virtual,该子类函数均为虚函数
		cout << "CSon::show" << endl;
	}
protected:
private:
};	//继承关系下,子类不但继承父类成员,还会继承父类的虚函数列表

int main() {

	//CFather* pFa = new CSon;
	new的是哪个子类,那么vfptr(虚函数指针)最终就指向哪个子类的虚函数列表

	//pFa->show();
	pFa->play();	//无法使用,play并非CFather的成员

	//CSon* pSon = new CSon;
	//pSon->play();

	//除了上述方式,还可以通过手动遍历数组来实现以虚函数指针调用虚函数
	CFather* pFa = new CSon;	//父类指针指向new的子类的对象
	//(int*)pFa:以int(同为4个字节)强制转换成new的子类的前四位,也就是虚函数指针
	//(*(int*)pFa):间接引用(int*)pFa,虚函数指针指向虚函数列表的首地址
	//(int*)(*(int*)pFa):以int(同为4个字节)强制转换成虚函数列表的前四位,也就是第一个虚函数的指针
	//(((int*)(*(int*)pFa)) + 0):偏移0后(取第0个元素)间接引用,也就是取到第一个元素【0】里的值,也就是第一个虚函数指针,指向的第一个虚函数
	typedef void(*P_FUN)();//定义一个函数指针
	P_FUN p_fun1 = (P_FUN) * (((int*)(*(int*)pFa)) + 0);
	P_FUN p_fun2 = (P_FUN) * (((int*)(*(int*)pFa)) + 1);//获取虚函数列表第二个元素【1】
	P_FUN p_fun3 = (P_FUN) * (((int*)(*(int*)pFa)) + 2);//获取虚函数列表第三个元素【2】
	(*p_fun1)();
	(*p_fun2)();
	(*p_fun3)();

	system("pause");
	return 0;
}

image-20220314000323162

虚析构

#include <iostream>
using namespace std;

class CFather
{
public:
	CFather(){
		cout << "father" << endl;
	}
	virtual ~CFather() {		//	虚析构
		cout << "~father" << endl;
	}
protected:
private:
};

class CSon :public CFather
{
public:
	CSon() {
		cout << "son" << endl;
	}
	~CSon() {
		cout << "~son" << endl;
	}
protected:
private:
};

void Fun(CFather* pFa) {
	delete pFa;//	此处无法进行强转,因为根本不知道其指向的是哪个子类,因此还是无法执行子类的析构.
}

int main() {
	//CSon* pson = new CSon;
	//delete pson;
	//pson = NULL;
	//	父类构造 子类构造 子类析构 父类析构

	//CFather* pFather = new CSon;
	//delete pFather;
	//pFather = NULL;
	//	父类构造 子类构造 父类析构
	//	delete pFather销毁的是父类的空间 是按照父类类型进行删除的 所以和子类析构没有关系
	//	利用强转:	delete (CSon*)pFather;	//	父类构造 子类构造 子类析构 父类析构	
	//	但该方式比较勉强,如果是void Fun(CFather* pFa)处的情况则无法完成要求,子类的析构还是执行不了,子类构造new了空间,如果不执行析构的话会造成空间浪费,所以要想办法执行子类的析构,故采用虚析构

	//	虚析构是虚函数,因此调用时流程和虚函数一样,需要虚函数指针,遍历虚函数列表,此时虚函数列表内的析构函数实际上为子类的析构函数,因此,之所以能够调用子类的析构函数,其本质原理是多态行为的发生

	//	虚函数列表内的析构函数实际上为子类的析构函数的原因如下:
				//覆盖:子类的虚函数重写了父类的虚函数,在虚函数列表中会替换掉父类的虚函数
				//同时,由于子类重写父类的虚函数,子类前无论加不加虚函数关键字virtual,该子类函数均为虚函数
	//	因此虚函数列表里是子类的析构,而程序后面还会走父类的析构,这个部分就不再是走虚函数列表的原因了,而是因为父类类型指针本身要去删除其所占用的父类的空间

	CFather* pFather = new CSon;
	delete pFather;


	system("pause");
	return 0;
}

多态的缺点

  • 效率较低(正常的函数直接调用,但虚函数要指针遍历虚函数列表……)
  • 占用额外空间(每定义一个对象虚函数指针占多4字节,每个类的虚函数列表也占用空间……)
  • 安全性隐患(可以通过某些方法调用访问私有的虚函数,私有的虚函数也在虚函数列表里……例:P_FUN p_fun1 = (P_FUN) * (((int*)((int)pFa)) + 0))

纯虚函数

#include <iostream>
using namespace std;

class CFather
{
public:
	virtual void play() = 0;//纯虚函数
		//以前面三种人,每种人有不同玩法为例,如果父类中的玩没有公共的可以实现的部分(or父类play函数不需要实现),需要各个子类的人种中有各自的玩法,此时可以将父类中设置为纯虚函数。
protected:
private:
};

class CSon:public CFather
{
public:
	virtual void play() {
		cout << "play" << endl;
	}
protected:
private:
};

int main() {

	CSon son;	//在CSon中没有函数的情况下会报错:纯虚函数没有替代项
				//说明:子类必须重写父类的纯虚函数,纯虚函数必须在子类中实现,父类无法实现
	son.play();

	//CFather father;	
	//不允许使用抽象类类型定义对象,也就是说有纯虚函数的CFather类是抽象类,抽象类是不允许定义对象的。
	//抽象类:类中存在纯虚函数	VS	接口类:类中虚函数都是纯虚函数(接口类当然也不允许定义对象)

	system("pause");
	return 0;
}

头文件 -源文件

//6-2.cpp
    
#include <iostream>
using namespace std;

#include "6-2-0.h"

//	头文件-源文件
//	头文件:变量声明、函数声明、类型声明、类的定义、宏
//	源文件:函数的实现

int main() {
	//cout << a << endl;
	//cout << c << endl;
	show(2);

	CTest tst;
	tst.show();
	tst.play();		//	静态成员函数可以通过对象调用
	CTest::play();	//	也可以通过类名作用域直接调用
	tst.code();
	tst.run();

	system("pause");
	return 0;
}
//6-2-0.h
//	头文件:变量、函数、类型声明、类的定义、宏


void show(int a);	//函数的声明

//int a;		//	根据在内存中是否申请空间来确定其为声明还是定义 
			//	该句为变量的定义
//int b = 10;			//	定义并初始化

//extern int c;			//	变量的声明
//extern int c = 30;	//	定义

class CTest
{
public:
	int m_a;
	const int m_b;
	//const一旦定义就不能修改,所以要去设置其初始值时应该在参数列表里设置
	static int m_c;
	//类外初始化,编译期就存在,不属于对象
public:
	CTest();
	~CTest();
	void show();//	类成员函数的声明
	static void play();
	void code()const;	//	常函数
	virtual void run();	//	虚函数
protected:
private:
};

//int CTest::m_c = 60;	//报错:多重定义
						//原因:这个头文件在6-2.cpp和6-2-1.cpp都被include了一次,所以m_c被定义了两次,重定义了,说明静态的这个定义最好是不要定义在头文件中,应该放到源文件里进行初始化
//6-2-0.cpp
//	源文件:函数的实现

#include <iostream>
using namespace std; 

#include"6-2-0.h"

int CTest::m_c = 60;

void show(int a) {
	cout << "show" << a << endl;
}

//int c = 30;


CTest::CTest():m_b(20){
	m_a = 10;
};
CTest::~CTest() {

};
void CTest::show() {
	cout << m_a << endl;
	cout << m_b << endl;
	cout << m_c << endl;
};

void CTest::play() {	//静态成员函数(声明于头文件,在源文件中实现时)的实现需要去掉静态关键字statuc
	cout << "static play" << endl;
}

void CTest::code()const {	//常函数(声明于头文件,在源文件中实现时)在源文件中定义需要保留关键字const
	cout << "const code" << endl;
}

void CTest::run() {		//虚函数(声明于头文件,在源文件中实现时)的实现需要去掉virtual关键字
	cout << "virtual run" << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

97Marcus

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

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

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

打赏作者

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

抵扣说明:

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

余额充值