C++基础——多态

1多态

1.1问题引出

函数重写
在子类中定义与父类中原型相同的函数
函数重写只发生在父类与子类之间

#include "iostream"
using namespace std;

class Parent
{
public:
	Parent(int a)
	{
		this->a=a;
		cout<<"Parent  a:"<<a<<endl;
	}
	virtual  void print()
	/*void print()*/
	{
		cout<<"Parent 打印  a:"<<a<<endl;
	}
protected:
private:
	int a;
};

class Child : public Parent
{
public:
	Child(int b):Parent(10)
	{
		this->b=b;
		cout<<"Child    b:"<<b<<endl;
	}

	void print()   //子类和父类的函数名称一样
	{
		cout<<"Child 打印  b:"<<b<<endl;
	}

protected:
private:
	int b;
};

void howToPrint(Parent *base)
{
	base->print();
}
void howToPrint2(Parent &base)
{
	base.print();
}

void main()
{
	Parent  *base=NULL;
	Parent	p1(20);
	Child	c1(30);

	base=&p1;
	base->print();  //执行父类打印函数

	base=&c1;
	base->print();   //执行谁的函数?    答:父类的
	                 //面向对象新需求

	{
		Parent &base2=p1;
		base2.print();

		Parent &base3=c1;   //base3是c1的别名
		base3.print();
	}

	//函数调用
	howToPrint(&p1);
	howToPrint(&c1);

	howToPrint2(p1);
	howToPrint2(c1);

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

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

在同一个类里面能实现函数重载
继承的情况下,发生重写
重载不一定;
重写的定义
静态联编 重载是
动态联编

1.2面向对象新需求

在这里插入图片描述

1.3解决方案

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

1.4多态实例

#include "iostream"
using namespace std;

class HeroFighter
{
public:
	virtual int power()   //C++会对此函数特殊处理
	{
		return 10;
	}
protected:
private:
};

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

private:
};

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

class AdvHeroFighter3 : public HeroFighter
{
public:
	virtual int power()
	{
		return 30;
	}
protected:
private:
};

//多态的威力
//1   PlayObj给对象搭建舞台   看成一个框架
void PlayObj(HeroFighter *hf,EnemyFighter *ef)
{ 
	//不写virtual会是静态联编 C++编译器很具Herofighter类型,去执行这个类型的power函数
	//动态联编:迟绑定->   在运行的时候
	//hf->power()   将会有多态发生,根据对象(具体的类型),执行不同对象的函数,表现成多态
	if (hf->power() > ef->attack())    
	{
		cout<<"主角  win"<<endl;
	} 
	else
	{
		cout<<"主角  挂了"<<endl;
	}
}

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


//多态很重要

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

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

void main()
{
	HeroFighter		hf;
	AdvHeroFighter  Advhf;
	EnemyFighter	ef;
	AdvHeroFighter3 Advhf3;

	PlayObj(&hf,&ef);
	PlayObj(&Advhf,&ef);
	PlayObj(&Advhf3,&ef);   //此框架可将后来人写的代码给调用起来

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





void main1401()
{
	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 ;
}

实现多态的三个条件

  • 1 要有继承
  • 2 要有虚函数重写
  • 3 用父类指针(父类引用)指向子类对象

1.5多态工程意义

面向对象3大概念

  • 封装
    突破了C语言函数的概念。。

  • 继承
    代码复用 。。。。我复用原来写好的代码。。。

  • 多态
    多态可以使用未来。。。。。80年代写了一个框架。。。。。。90人写的代码
    多态是我们软件行业追寻的一个目标。。。
    写了一个框架,可以调用后来人,写的代码的能力

1.6多态成立的条件

  • 间接赋值成立的3个条件
    //1 定义两个变量。。。
    //2 建立关联 。。。。
    //3 *p

  • 多态成立的三个条件
    //1 要有继承
    //2 要有函数重写。。。C 虚函数
    //3 要有父类指针(父类引用)指向子类对象
    //多态是设计模式的基础,多态是框架的基础

1.7多态的理论基础

静态联编和动态联编
1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。

1.8 虚析构函数

在这里插入图片描述
在这里插入图片描述


#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;

class A
{
public:
	A()
	{
		p = new char[20];
		strcpy(p,"obja");
		cout<<"A()"<<endl;
	}
	virtual ~A()
	{
		delete [] p;
		cout<<"~A()"<<endl;
	}
protected:
private:
	char *p;
};

class B : public A
{
public:
	B()
	{
		p=new char[20];
		strcpy(p,"objb");
		cout<<"B()"<<endl;
	}
	~B()
	{
		delete [] p;
		cout<<"~B()"<<endl;
	}
protected:
private:
	char *p;
};

class C : public B
{
public:
	C()
	{
		p=new char[20];
		strcpy(p,"objc");
		cout<<"C()"<<endl;
	}
	~C()
	{
		delete [] p;
		cout<<"~C()"<<endl;
	}
protected:
private:
	char *p;
};

//只执行了  父类的析构函数
//想通过父类指针  把 所有的子类对象的析构函数都执行一遍,
//通过父类指针,释放所有的子类资源
void HowToDelete(A *base)
{
	delete base; //不会表现出多态的属性
}

void main()
{
	C *myC=new C;
	HowToDelete(myC);


	//delete myC;//直接通过子类对象释放资源,不需要virtual

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

1.9 重载重写重定义

重写 重载 重定义

  • 重写发生在2个类之间
  • 重载必须在一个类之间

重写分为2类

  • 1、虚函数重写 将发生多态
  • 2、非虚函数重写 (重定义)

#include <iostream>
using namespace std;

//重写 重载 重定义
//重写发生在2个类之间
//重载必须在一个类之间

//重写分为2类
//1 虚函数重写  将发生多态
//2 非虚函数重写 (重定义)

class Parent
{
	//这个三个函数都是重载关系
public: 
	void abc()
	{
		printf("parent abc");
	}

	virtual void func() 
	{
		cout<<"func() do..."<<endl;
	}
	virtual void func(int i)
	{
		cout<<"func() do..."<<i<<endl;
	}
	virtual void func(int i, int j)
	{
		cout<<"func() do..."<<i<< " "<<j<<endl;
	}

	virtual void func(int i, int j, int m , int n)
	{
		cout<<"func() do..."<<i<< " "<<j<<endl;
	}
protected:
private:
};


class Child : public Parent
{
	
public: 
	void abc()
	{
		printf("child abc");
	}
	/*
	void abc(int a)
	{
		printf("child abc");
	}
	*/
	virtual void func(int i, int j)
	{
		cout<<"func(int i, int j) do..."<<i<< " "<<j<<endl;
	}
	virtual void func(int i, int j, int k)
	{
		cout<<"func(int i, int j) do.."<< endl; 
	}
protected:
private:
};


//重载重写和重定义
void main()
{
	//: error C2661: “Child::func”: 没有重载函数接受 0 个参数
	Child c1;
	c1.abc();
	//c1.func();
	//子类无法重载父类的函数,父类同名函数将被名称覆盖
	//c1.Parent::func();

	//1 C++编译器 看到func名字 ,因子类中func名字已经存在了(名称覆盖).所以c++编译器不会去找父类的4个参数的func函数
	//2 c++编译器只会在子类中,查找func函数,找到了两个func,一个是2个参数的,一个是3个参数的.
	//3 C++编译器开始报错.....  error C2661: “Child::func”: 没有重载函数接受 4 个参数
	//4 若想调用父类的func,只能加上父类的域名..这样去调用..
	//c1.func(1, 3, 4, 5);

	//c1.func();
	//func函数的名字,在子类中发生了名称覆盖;子类的函数的名字,占用了父类的函数的名字的位置
	//因为子类中已经有了func名字的重载形式。。。。
	//编译器开始在子类中找func函数。。。。但是没有0个参数的func函数 


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

2. 多态相关面试题

2.1 请谈谈你对多态的理解

  • 多态的实现效果
    多态:同样的调用语句有多种不同的表现形态;
  • 多态实现的三个条件
    有继承、有virtual重写、有父类指针(引用)指向子类对象。
  • 多态的C++实现
    virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用
  • 多态的理论基础
    动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。
  • 多态的重要意义
    设计模式的基础 是框架的基石。
  • 实现多态的理论基础
    函数指针做函数参数
    C函数指针是C++至高无上的荣耀。C函数指针一般有两种用法(正、反)。

2.2 对重写,重载理解

  • 函数重载
    必须在同一个类中进行
    子类无法重载父类的函数,父类同名函数将被名称覆盖
    重载是在编译期间根据参数类型和个数决定函数调用
  • 函数重写
    必须发生于父类与子类之间
    并且父类与子类中的函数必须有完全相同的原型
    使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
    多态是在运行期间根据具体对象的类型决定函数调用

3多态原理探究

理论知识:

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

3.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;
	}
	virtual void print()   //1  动手脚   写virtual关键字  会特殊处理
		//                      生成虚函数表
	{
		cout<<"我是爸爸 。。。"<<endl;
	}
protected:
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;
	}
protected:
private:
	int b;
};

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

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

	howtoPlay(&p1);
	howtoPlay(&c1);


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

3.2如何证明vptr指针的存在

#include "iostream"
using namespace std;

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

protected:
private:
	int a;
}; 

class Parent2
{
public:
	Parent2(int a=0)
	{
		this->a=a;
	}
	virtual void print()   	
	{
		cout<<"我是爸爸 。。。"<<endl;
	}

protected:
private:
	int a;
}; 

void main()
{
	printf("sizeof(Parent1) %d,sizeof(Parent2):%d   \n",sizeof(Parent1),sizeof(Parent2));
	cout<<"hello..."<<endl;
	system("pause"); 
	return ;
}

3.3构造函数中能调用虚函数,实现多态吗

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

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

2)分析过程
画图分析
在这里插入图片描述

#include "iostream"
using namespace std;

//构造函数中调用虚函数能发生多态吗

class Parent
{
public:
	Parent(int a=0)
	{
		this->a=a;
		print();
	}
	virtual void print() 
	{
		cout<<"我是爸爸 。。。"<<endl;
	}
protected:
private:
	int a;
};

class Child : public Parent
{
public:
	Child(int a=0,int b=0):Parent(a)
	{
		this->b=b;
		print();
	}
	virtual void print()
	{
		cout<<"我是儿子 。。。"<<endl;
	}
protected:
private:
	int b;
};

void howtoPlay(Parent *base)
{
	base->print();
}

void main()
{
	Child	c1;  //定义一个子类对象,在这个过程中在父类构造函数中调用虚函数能发生多态吗

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值