C++进阶多态

Posted Huang_ZhenSheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++进阶多态相关的知识,希望对你有一定的参考价值。

目录

多态的条件 

析构函数的重写 

C++11 override 和 final

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

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

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

抽象类

多态的原理

虚函数表

多态的原理

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

单继承中的虚函数表


多态的条件 

class Person

public:
	virtual void BuyTicket()
	
		cout << "正常排队—全价买票" << endl;
	
protected:
	int _age;
	string _name;
;
class Student:public Person

public:
	virtual void BuyTicket()
	
		cout << "正常排队—半价买票" << endl;
	
protected:
	int _age;
	string _name;
;
class Solider:public Person

public:
	virtual void BuyTicket()
	
		cout << "优先排队—全价买票" << endl;
	
protected:
	int _age;
	string _name;
;
//多态两个条件
//1,子类重写父类的虚函数
//2,必须是父类的指针或者引用去调用虚函数
void Func(Person* ptr)

	//多态—ptr指向父类对象调用父类的虚函数,指向子类对象调子类虚函数
	ptr->BuyTicket();

int main()

	Person ps;
	Student st;
	Solider sd;

	Func(&ps);
	Func(&st);
	Func(&sd);

多态的两个条件

1.子类重写父类的虚函数

2.必须是父类的指针或者引用去调用虚函数 

析构函数的重写 

class Person

public:
	virtual ~Person()
	
		cout << "~Person()" << endl;
	
;
class Student :public Person

public:
	virtual ~Student()
	
		cout << "~Student()" << endl;
	
;
//如果不是虚函数,他们之间是隐藏关系
//是虚函数,他们是重写关系
int main()

	普通场景下面,虚函数是否重写都是OK的
	//Person p;
	//Student s;
	//new对象特殊场景
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;//p1->destructor()
	delete p2;//p2->destructor()
	return 0;

注意!析构函数要定义成虚函数

有些书籍会把多态进行更多的划分:

1,静态的多态——函数的重载,调用同一个函数,传不同的参数,就有不同的行为/形态

2,动态的多态——调用一个虚函数,不同对象去调用,就有不同的行为/形态

多态的条件:

a,子类中重写父类的虚函数

b,必须是父类的指针或者引用去调用重写的虚函数

虚函数重写条件:

a,子类和父类都必须是虚函数

b,函数名,参数,返回值都必须相同

例外:

a,协变

b,析构函数被特殊处理

c,子类中的重写虚函数可以不加virtual

C++11 override 和 final

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

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

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

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

抽象类

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

class Car

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

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

public:
	virtual void Drive()
	
		cout << "BMW-操控" << endl;
	
;
void Test()

	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();

多态的原理

虚函数表

虚函数表:

如下代码的输出结果是?

class Base

public:
	virtual void Func1()
	
		cout << "Func1()" << endl;
	
private:
	int _b = 1;
;
int main()

	Base b;
	cout << sizeof(Base) << endl;
	return 0;

 在看一段代码:

class Base

public:
	virtual void Func1()
	
		cout << "Base::Func1()" << endl;
	
	virtual void Func2()
	
		cout << "Base::Func2()" << endl;
	
	void Func3()
	
		cout << "Base::Func3()" << endl;
	
private:
	int _b = 1;
;
class Derive : public Base

public:
	virtual void Func1()
	
		cout << "Derive::Func1()" << endl;
	
private:
	int _d = 2;
;
int main()

	Base b;
	Derive d;
	return 0;

父子类无论是否完成虚函数的重载,都有各自独立的虚表

一个类的所有对象,共享一张虚表

多态的原理

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 Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;

 分析===》

这里存在问题:多态的这种调用也是付出代价的,多态的调用要到虚表里面去找,会有一定程度的性能损失

复习:普通函数是如何确认地址的?编译或链接时确定它的地址

编译:编译的时候往上去找有无这个函数的定义,编译完,找到地址,编程call

链接:只有他的声明,他的定义在其他的目标文件里面,这个时候先过,call(?),链接的时候拿这个函数名去找

多态的调用?

先检查是不是满足多态,多态的调用是运行时去确定地址,如何确定?==》去指向对象的虚函数表中知道虚函数的地址

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

单继承中的虚函数表

class Base

public:
	virtual void Func1()
	
		cout << "Base::Func1()" << endl;
	
	virtual void Func2()
	
		cout << "Base::Func2()" << endl;
	
	void Func3()
	
		cout << "Base::Func3()" << endl;
	
	private:
		int _b = 1;
	;
class Derive : public Base

public:
	virtual void Func1()
	
		cout << "Derive::Func1()" << endl;
	
	virtual void Func4()
	
		cout << "Derive::Func4()" << endl;
	
private:
	int _d = 2;
;
typedef void(*VFPTR)();
//打印虚表
void PrintVFT(void* vft[])

	printf("%p\\n",vft);
	for (size_t i = 0; vft[i] != nullptr; i++)
	
		printf("vft[%d]:%p->",i,vft[i]);
		VFPTR f = (VFPTR)vft[i];
		f();
	
	printf("\\n");

int main()

	Base b;
	Derive d;
	PrintVFT((void**)*(int*)&b);
	PrintVFT((void**)*(int*)&d);

	return 0;

虚函数存在哪的?虚表存在哪的?

答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。

那么虚表存在哪的呢?===>常量区(代码段)

虚函数编译出来函数指令跟普通函数一样,存在代码段,虚函数地址又被放到虚函数表中

int main()

	Base bb;
	int a = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	auto pf = PrintVFT;
	static int b = 1;
	printf("栈祯变量:%p\\n",&a);
	printf("堆变量:%p\\n", p1);
	printf("常量区变量:%p\\n", p2);
	printf("函数地址变量:%p\\n", pf);
	printf("静态区变量:%p\\n", &b);
	printf("虚函数表地址:%p\\n", *(int*)&bb);
	return 0;

以上是关于C++进阶多态的主要内容,如果未能解决你的问题,请参考以下文章

C++多态

C++多态

多态 笔记

C++进阶----多态

面向对象进阶

C++进阶第十六篇——C++中的多态(多态的概念+多态的实现+抽象类+多态的原理)