多态(C++)

1.多态的概念

   同一种东西在不同场景下的多种形态

    比如举个例子:见人说人话,见鬼说鬼话

2.动态多态的分类

     多态(C++)_第1张图片  1>静态多态

  •   静态多态是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型的转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则出现编译错误

  2>动态多态

    我们先来看一段代码:

#define _CRT_SECURE_NO_WARNING 1

#include 
#include 
#include 
#include 
#include 
using namespace std;

class FittingRoom
{
public:
	void GoToManFittingRoom()
	{
		cout<<"Man--->Please go to left"<Please go to right"<GotoFitttingRoom(ft);
		delete p;
		Sleep(1000);
	}
}

int main()
{
	TestFittingRoom();
	return 0;
}
在该段代码里,我们实现了一个类FittingRoom(试衣间),试衣间分为男女试衣间,如果是男则去左边的试衣间,如果是女则去右边的试衣间。

多态(C++)_第2张图片

上述便是动态多态实现的一种实现场景

(1)动态多态的实现条件  --基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写

                                             --通过基类对象的指针或引用调用虚函数

      所谓重写是a.基类函数必须为虚函数

         b.派生类函数必须与基类中的虚函数保持一致(包括返回类型、函数名称、参数列表)

        但是有例外:即协变:第一种是:基类虚函数必须返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用   

                                                  第二种是:虚拟的析构函数(基类和派生类的析构函数名字不相同)  

                第三种是:派生类的虚函数可以与基类中的虚函数访问限定符不一样

 下面我们来一段代码实现一下重写以及特殊的重写方式·

#include 
using namespace std;

class Base
{
public:
	virtual void TestFunc1(int a = 1)
	{
		cout<<"Base::TestFunc1()"<TestFunc1();
	pb->TestFunc2(10);
	pb->TestFunc3(10);
	pb->TestFunc4(1);
	pb = new Derived;
	delete pb;
	return 0;
}
调用TestFunc1时,往进一执行,发现调用的是Base基类里的

  

调用TestFunc2时,一执行发现调用的是也是Base基类里面的


调用TestFunc3时,一执行发现调用的是Derived派生类里面的


调用TestFunc4时,一执行发现调用的是也是Base基类里面的


协变:>>>>我们new了一个派生类对象,现在我们看看调用的虚拟的析构函数调用的哪个

    

    我们发现调用的是派生类的析构函数

3>面试题

1.什么是函数重载,同名隐藏、重写

多态(C++)_第3张图片

2.哪些函数不能定义为虚函数(详解看。。。。)

    类的构造函数不能作为虚函数

    类的拷贝构造函数也不能作为虚函数

    静态成员函数也不能作为虚函数

    友元函数也不能作为虚函数

    赋值重载函数可以作为虚函数,但是不建议

    析构函数可以用作虚函数,并且建议在带有虚函数的类中,最好将基类中的析构函数作为虚函数

3.深究多态调用原理

在上述重写中我们发现有的调用的是基类中,有的调用的是派生类中的,那么为什么呢?下面我们就来探究一下

#include 
using namespace std;

class Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Base::TestFunc1()"<

我们以为会是4个字节,然而并不是,结果为8字节

通过调用内存窗口,取b的地址,会发现结果是这样的:

会发现在b = 1 的前面多了一个4字节的地址,然后对该地址里面的内容进行查看,发现是这样的:

,里面存放的是两个地址,那么这两个地址是什么呢,我们打印一下运行结果是这样的:,也就是说这两个地址是虚函数的地址,所以多出来的那四个字节是虚表指针,指向该类中的虚函数地址

多态(C++)_第4张图片

    图 Base类的对象模型

在Base的基础上我们增加一个派生类,公有继承自Base类

#include 
using namespace std;

class Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Base::TestFunc1()"<B: ");
	Derived& rd = d;
	PrintVFT(rd,"D VFT--->D: ");
	return 0;
}

多态(C++)_第5张图片

图 派生类的对象模型

总结一下:如果是普通函数,直接调用

                如果是虚函数,调用时:(1)从对象的前4个字节中取虚函数的地址

                                                     (2)传递this指针

                                                     (3)从虚表中获取虚函数的地址(在虚表地址+虚函数在虚表中的偏移量)

                                                     (4)调用虚函数

            如果是派生类继承自父类,全部继承父类的所有内容,自己的和父类的普通函数直接调用就行,但是如果是虚函数: (1)父类有自己没有,虚表里存放的是父类的虚函数地址

                  (2)父类没有自己有,虚表里存放的是自己的虚函数地址

                 (3)父类有自己也有,对其构成了重写,此时存放的是派生类的

4.再探虚表&不同继承下带有虚函数的对象模型

1. 单继承   

单继承对象模型上面已经论述过,不再多说

2. 多继承

代码:

#include 
using namespace std;

class B1
{
public:
	virtual void TestFunc1()
	{
		cout<<"B1::TestFunc1()"<B1: ");

	B2& rb2 = d;
	PrintVFT(rb2,"D VFT--->B2: ");

	D& rd = d;
	PrintVFT(rd,"D VFT--->D: ");
	return 0;
}

   多态(C++)_第6张图片
图 多继承的对象模型
3. 菱形继承   
多态(C++)_第7张图片菱形继承有二义性,看段代码
#include 
using namespace std;


class B
{
public:
	virtual void TestFunc1()
	{
		cout<<"B::TestFunc1()"<B: ");
	
	C1& c1 = d;
	PrintVFT(c1,"D VFT-->C1: ");

	C2& c2 = d;
	PrintVFT(c2,"D VFT--->C2: ");

	D& rd = d;
	PrintVFT(rd,"D VFT--->D: ");

	return 0;
}
此时编译器会报错:

4. 虚拟继承   

菱形虚拟继承
#include 
using namespace std;


class B //1,2
{
public:
	virtual void TestFunc1()
	{
		cout<<"B::TestFunc1()"<B: ");
	
	C1& c1 = d;
	PrintVTF(c1,"D VTF--->C1: ");

	C2& c2 = d;
	PrintVTF(c2,"D VFT--->C2: ");

	D& rd = d;
	PrintVTF(rd,"D VFT--->D: ");
	return 0;
}

多态(C++)_第8张图片


5.抽象类

1>概念

在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为虚函数,包含虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重新定义以后,派生类才能实例化对象

2>例子

#include 
using namespace std;

class Person //包含纯虚函数,不能实例化出对象
{
public:
	virtual void GoToWashRom() = 0;
private:
	string _strname;
};

class Man
{
public:
	virtual void GoToWashRoom() //Person类中的纯虚函数在派生类中进行了重新定义,所以派生类实例化出对象
	{
		cout<<"Man--->Please left"<

一经编译会报错



你可能感兴趣的:(C++)