关于多态的简单阐述

在上篇博客中,简单提了一下C++的第一个特点继承,今天就来聊聊第二个特点:多态

一、多态的概念

1、什么是多态呢?简单来说,多态就是面向对象的重要特性,也可以说是同一种事物的不同体现,或者是同一事物表现出得多种形态。

2、多态的分类

   

静态多态:编译器在编译期间完成,编译器根据函数实参的类型(可能会进行隐式转换)。

动态多态:在程序运行期间(非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

3、动态多态实现的两个条件:

(1)调用虚函数--->一定在派生类中对基类的虚函数进行重写

重写(覆盖):在继承体系中,如果基类中有虚函数,在派生类中有和基类虚函数原型相同的虚函数。

重写的特例:1>协变:基类返回基类指针,派生类返回派生类指针

        2>析构函数:基类调用基类析构函数 ~Base() 派生类调用派生类构造函数 ~Derived()

(2)通过基类的指针或引用来调用虚函数

二、动态多态的实现及其原理

多态通过虚函数结合动态绑定来实现。

虚函数:通过关键字virtual所修饰的成员函数。

在讲动态多态的实现之前,必须先来讲一下虚表以及上篇提过的对象模型


先看一下下面的代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class A
{
public:
	void Test1()
	{
		cout << "A::Test1()" << endl;
	}
};

class B
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
};
int main()
{
	cout <<"A的大小" << sizeof(A) << endl;
	cout <<"B的大小" << sizeof(B) << endl;
	system("pause");
	return 0;
}

输出结果及分析

1、普通函数的调用:call函数地址

       虚函数的调用:   通过查询虚表

2、先来提两个问题

1>同一个类的对象是否公用一张虚表?   答案:是,公用同一张虚表

2>派生类是否和基类公用一张虚表?  答案:否,不共用

下面就重点讲一下虚表的构造

基类中的虚表:虚函数在类中的申明次序

派生类中的虚表:

  1、单继承

         1>先拷贝基类中的虚表

         2>检测派生类中是否对基类中的虚函数进行重写----是->用派生类中重写的虚函数来替代相同偏移量位置的基类虚函数

         3>在虚表之后添加派生类自己的虚函数

在此处说一下虚函数的调用:1>取对象地址2>取对象前四个字节中的内容—>虚表指针3>传this指针4>调对应指针

 下面就用几行代码来通俗的演示及解释一番

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}

private:
	int _a;
};

class B:public A
{
public:
	//重写基类虚函数
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	//派生类新定义的虚函数
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
private:
	int _b;
};
typedef void(*Pfun)();
//遍历虚表内容
void Display(const  A&a)
{
	Pfun*pfun = (Pfun*)*(int*)&a;
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
}
void test()
{
	A a;
	B b;
	Display(a);
	Display(b);
}
int main()
{
	
	test();
	system("pause");
	return 0;
}


通过对结果的分析可以看出在派生类的对象模型中,不仅继承了基类中的,而且也对在派生类中重写的虚函数做了修改,然后再下边补上了派生类自己的虚函数。

2、多继承

   在第一张虚表之后加上派生类自己的虚函数,其他与单继承类似。        

   将派生类的虚函数加在第一张虚表之后的好处:效率快,仅访问前四个字节就能访问到派生类虚函数。

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "A::Test2()" << endl;
	}
private:
	int _a;
};
class B
{
public:
	virtual void Test3()
	{
		cout << "B::Test3()" << endl;
	}

private:
	int _b;
};
class C:public A,public B
{
public:
	//重写基类虚函数
	virtual void Test1()
	{
		cout << "C::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "C::Test3()" << endl;
	}
	//派生类新定义的虚函数
	virtual void Test4()
	{
		cout << "C::Test4()" << endl;
	}
	
	
private:
	int _c;
};
typedef void(*Pfun)();
//遍历虚表内容
void Display(const  A&a)
{
	//访问A的虚表
	Pfun*pfun = (Pfun*)*(int*)&a;
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
	//跳过A的内容访问B的虚表
	pfun = (Pfun*)*(int*)((char*)&a + sizeof(A));
	while (*pfun)
	{
		(*pfun)();
		++pfun;
	}
}
void test()
{
	C c;
	Display(c);
}
int main()
{
	test();
	system("pause");
	return 0;
}


对代码的解读如下:


    多继承:先继承的基类的虚表在前,再把派生类自己的虚函数加在第一张虚表之后,再把第二个继承的基类的虚表接在后面。

3、菱形继承

   1)带虚函数的虚拟继承

           在此继承的体系下,则对象的对象模型需要再多四个字节,用来存放偏移量表格。

           <1>先看派生类仅重写了基类的虚函数,而无自己的虚函数的情况

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	int _a;
};
class B:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	int _b;
};
int main()
{
	cout << sizeof(B) << endl;
	A a;
	B b;
	b._a = 0;
	b._b = 1;
	system("pause");
	return 0;
}


          <2>先看派生类不仅重写了基类的虚函数,而且拥有自己的虚函数的情况

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	int _a;
};
class B:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
int main()
{
	cout << sizeof(B) << endl;
	A a;
	B b;
	b._a = 0;
	b._b = 1;
	system("pause");
	return 0;
}



    2)菱形继承

class A
{
public:
	virtual void Test1()
	{
		cout << "A::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "A::Test2()" << endl;
	}
	int _a;
};
class B1:virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B1::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "B1::Test3()" << endl;
	}
	int _b1;
};
class B2 :virtual public A
{
public:
	virtual void Test1()
	{
		cout << "B2::Test1()" << endl;
	}
	virtual void Test4()
	{
		cout << "B2::Test4()" << endl;
	}
	int _b2;
};
class D :public B1,public B2
{
public:
	virtual void Test1()
	{
		cout << "D::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "D::Test3()" << endl;
	}
	virtual void Test4()
	{
		cout << "D::Test4()" << endl;
	}
	virtual void Test5()
	{
		cout << "D::Test5()" << endl;
	}
	int _d;
};
typedef void(*FunPtr)();

void Print(A& a)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&a);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

void Print(B1& b1)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&b1);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

void Print(B2& b2)
{
	FunPtr* pfun = (FunPtr*)(*(int*)&b2);

	while (*pfun)
	{
		(*pfun)();
		pfun++;
	}
	cout << endl;
}

int main()
{
	cout << sizeof(D) << endl;
	D d;
	B1& b1 = d;
	Print(b1);

	B2& b2 = d;
	Print(b2);

	A& a = d;
	Print(a);
	return 0;
}


对于上述带虚函数的菱形继承的代码根据内存块的分析

以上就是我对多态的一点小小总结,希望各位大佬们多指点。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章