C++中的多态原理探究

一、多态

1、多态问题抛出

如果子类定义了与父类中原型相同的函数会发生什么?
在子类中定义与父类中原型相同的函数,函数重写只发生在父类与子类之间。代码如下:

#include<iostream>
using namespace std;

class Parent{
public:
	void print(){
		cout << "Parent:print() do..." << endl;
	}
};

class Child : public Parent{
	public:
		void print(){
			cout << "Child:print() do..." << endl;
		}
};

void howToPrint(Parent* p){
	p->print();
}

void run(){
	Child child;
	Parent* pp = &child;
	Parent& rp = child;

	child.print();

	//通过指针
	pp->print();
	//通过引用
	rp.print();
	howToPrint(&child);
}
int main01(){
	run();
	system("pause");
	return 0;
}

int main02(){
	Child child;
	Parent *p = NULL;
	p = &child;
	child.print();
	child.Parent::print();//子类对象通过作用域分辨符::可以访问到父类中被隐藏的函数
	system("pause");
	return 0;
}
/*
函数重写
在子类中定义与父类中原型相同的函数
函数重写只发生在父类与子类之间

父类中被重写的函数依然会继承给子类
默认情况下子类中重写的函数将隐藏父类中的函数
通过作用域分辨符::可以访问到父类中被隐藏的函数

*/

main02 输出结果:
在这里插入图片描述
main01 输出结果:
在这里插入图片描述

2、面向对象新需求

编译器的做法不是我们期望的,根据实际的对象类型来判断重写函数的调用:
(1)如果父类指针指向的是父类对象则调用父类中定义的函数
(2)如果父类指针指向的是子类对象则调用子类中定义的重写函数
在这里插入图片描述

3、如何解决这样的情况呢?

C++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性。

4、多态案例

#include <iostream>
using namespace std;

//HeroFighter  AdvHeroFighter EnemyFighter
class HeroFighter{
public:
	//virtual C++会对这个函数特殊处理
	virtual int power(){
		return 10;
	}
};

class EnemyFighter{
public:
	int attack(){
		return 15;
	}
};

class AdvHeroFighter : public HeroFighter{
public:
	virtual int power(){
		return 20;
	}
};

class AdvAdvHeroFighter : public HeroFighter{
public:
	virtual int power(){
		return 30;
	}
};

//多态威力
//1 PlayObj给对象搭建舞台  看成一个框架
//15:20
void PlayObj(HeroFighter *hf, EnemyFighter *ef){
	//不写virtual关键字 是静态联编 C++编译器根据HeroFighter类型,去执行 这个类型的power函数 在编译器编译阶段就已经决定了函数的调用
	//动态联编: 迟绑定:  //在运行的时候,根据具体对象(具体的类型),执行不同对象的函数 ,表现成多态.
	if (hf->power() > ef->attack()) { //父类指针指向子类对象 hf->power()函数调用会有多态发生
		printf("主角win\n");
	}
	else{
		printf("主角挂掉\n");
	}
}


//多态的思想
//面向对象3大概念
//封装: 突破c函数的概念....用类做函数参数的时候,可以使用对象的属性 和对象的方法 
//继承: A B 代码复用
//多态 : 可以使用未来...


//多态很重要
//实现多态的三个条件
//C语言 间接赋值 是指针存在的最大意义
//是c语言的特有的现象 (1 定义两个变量  2 建立关联  3 *p在被调用函数中去间接的修改实参的值)

//实现多态的三个条件
//1 要有继承 
//2 要有虚函数重写
//3 用父类指针(父类引用)指向子类对象....


//使用多态的情况
void main02(){
	HeroFighter		hf;
	AdvHeroFighter	Advhf;
	AdvAdvHeroFighter advadvhf;
	EnemyFighter	ef;

	PlayObj(&hf, &ef);
	PlayObj(&Advhf, &ef);
	PlayObj(&advadvhf, &ef); //这个框架 能把我们后来人写的代码,给调用起来

	cout << "hello..." << endl;
	system("pause");
}

//不使用多态场景的情况
void main01(){
	HeroFighter		hf;
	AdvHeroFighter	Advhf;
	EnemyFighter	ef;

	if (hf.power() > ef.attack()){
		printf("主角win\n");
	}
	else{
		printf("主角挂掉\n");
	}

	if (Advhf.power() > ef.attack()){
		printf("Adv 主角win\n");
	}
	else{
		printf("Adv 主角挂掉\n");
	}
	cout << "hello..." << endl;
	system("pause");
	return;
}

5、多态的理论基础

1、静态联编和动态联编
(1)联编是指一个程序模块、代码之间互相关联的过程。
(2)静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。

  1. C++与C相同,是静态编译型语言
  2. 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
  3. 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象,从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。

(3)动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。switch 语句和 if 语句是动态联编的例子。

二、多态原理探究

理论知识:

  • 当类中声明虚函数时,编译器会在类中生成一个虚函数表
  • 虚函数表是一个存储类成员函数指针的数据结构
  • 虚函数表是由编译器自动生成与维护的
  • virtual成员函数会被编译器放入虚函数表中
  • 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象子类对象提前布局vptr指针;当进行howToPrint(Parent*base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可)
  • VPTR一般作为类对象的第一个成员 。

1、多态的实现原理

C++中多态的实现原理

  • 当类中声明虚函数时,编译器会在类中生成一个虚函数表
  • 虚函数表是一个存储类成员函数指针的数据结构
  • 虚函数表是由编译器自动生成与维护的virtual成员函数会被编译器放入虚函数表中
  • 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
结论2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数。
结论3 :
C++编译器,执行HowToPrint函数,不需要区分是子类对象还是父类对象。

#include <iostream>
using namespace std;

class Parent{
public:
	Parent(int a = 0){
		this->a = a;
	}
	//1 动手脚  写virtal关键字 会特殊处理 //虚函数表
	virtual void print()  {
		cout << "我是爹" << endl;
	}
private:
	int a;
};

class Child : public Parent
{
public:
	Child(int a = 0, int b = 0) :Parent(a){
		this->b = b;
	}

	virtual void print(){
		cout << "我是儿子" << endl;
	}
private:
	int b;
};

void HowToPlay(Parent *base){
	base->print(); //有多态发生  //2 动手脚  
	//效果:传来子类对 执行子类的print函数 传来父类对执行父类的print函数 
	//C++编译器根本不需要区分是子类对象 还是父类对象
	//父类对象和子类对象分步有vptr指针 , ==>虚函数表===>函数的入口地址
	//迟绑定 (运行时的时候,c++编译器才去判断)
}

void main(){
	Parent	p1; //3 动手脚 提前布局  
	//用类定义对象的时候 C++编译器会在对象中添加一个vptr指针 
	Child	c1; //子类里面也有一个vptr指针

	HowToPlay(&p1);
	HowToPlay(&c1);

	cout << "hello..." << endl;
	system("pause");
	return;
}

在这里插入图片描述
如果没有添加virtual 关键, 函数调用输出均为:我是爹。

2、如何证明vptr指针的存在

#include <iostream>
using namespace std;

class Parent01{
public:
	Parent01(int a = 0){
		this->a = a;
	}
	void print(){
		cout << "我是爹" << endl;
	}
private:
	int a;
};

class Parent02{
public:
	Parent02(int a = 0){
		this->a = a;
	}
	//parent02 添加了virtual关键字
	virtual void print(){
		cout << "我是爹" << endl;
	}
private:
	int a;
};

void main(){
	printf("sizeof(Parent01):%d sizeof(Parent02):%d \n", sizeof(Parent01), sizeof(Parent02));
	system("pause");
	return;
}

在这里插入图片描述

3、构造函数中能调用虚函数,实现多态吗?

1、对象中的VPTR指针什么时候被初始化?

  • 对象在创建的时,由编译器对VPTR指针进行初始化
  • 只有当对象的构造完全结束后VPTR的指向才最终确定
  • 父类对象的VPTR指向父类虚函数表
  • 子类对象的VPTR指向子类虚函数表
#include <iostream>
using namespace std;

class Parent{
public:
	Parent(int a = 0){
		this->a = a;
	}
	//1 动手脚  写virtal关键字 会特殊处理 //虚函数表
	virtual void print()  {
		cout << "我是爹" << endl;
	}
private:
	int a;
};

class Child : public Parent
{
public:
	Child(int a = 0, int b = 0) :Parent(a){
		this->b = b;
	}

	virtual void print(){
		cout << "我是儿子" << endl;
	}
private:
	int b;
};

void HowToPlay(Parent *base){
	base->print(); //有多态发生  //2 动手脚  
	//效果:传来子类对 执行子类的print函数 传来父类对执行父类的print函数 
	//C++编译器根本不需要区分是子类对象 还是父类对象
	//父类对象和子类对象分步有vptr指针 , ==>虚函数表===>函数的入口地址
	//迟绑定 (运行时的时候,c++编译器才去判断)
}
void main(){
	Parent	p1; //3 动手脚 提前布局  
	//用类定义对象的时候 C++编译器会在对象中添加一个vptr指针 
	Child	c1; //子类里面也有一个vptr指针
	HowToPlay(&p1);
	HowToPlay(&c1);
	cout << "hello..." << endl;
	system("pause");
	return;
}

在这里插入图片描述
2、分析过程如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值