C++三大特性之多态

这篇博客我会涉及到多态的概念、多态的定义以及实现、抽象类、虚函数表以及多态的原理

多态的概念

简单来说就是不同继承关系的类,去调用同一函数,产生不同的行为
通俗点来说,就是三好学生类和差生类,都去完成找老师请假的这个行为函数,虽然都是请假这个函数,但是三好学生类可以请假,差生类就要挨打。

多态的定义以及实现

多态构成的条件

  • 必须由基类的指针或者引用去调用虚函数
  • 被调用的必须是虚函数,而且派生类必须对基类的函数进行重写

1.虚函数
虚函数:被virtual关键字修饰的类成员函数,这里的virtual和继承的virtual是两回事,有点迷糊的可以看一下这篇博客C++三大特性之继承

class A
{
public:
	virtual	void func()
	{
		cout << "--------\n" << endl;
	}
};

顺便我也了一个思维导图,来区分一下重载,重写,重定义之间的区别
C++三大特性之多态_第1张图片

2.虚函数的重写
虚函数的重写(覆盖):派生类中有一个与子类完全相同的虚函数(函数名、参数和返回值都相同)

class A
{
public:
	virtual ~A()
	// ~A()
	{
		cout << "~A()" << endl;
	}
};

class B : public A
{
public:
	virtual ~B()//此处的virtual不写也可以,但是尽量加上
	// ~B()
	{
		cout << "~B()" << endl;
	}
};

int main()
{
	A* a1 = new A;
	A* a2 = new B;

	delete a1;
	delete a2;

	return 0;
}

这里需要特别提一下的就是析构函数的重写
基类的虚构函数,只要加上virtual关键字,构成的虚函数。不论派生类的析构函数,加不加virtual关键字都构成重写。看到这可能会有一点疑惑,上面的重写明明强调了必须函数名相同,那这块岂不是与概念冲突。其实不是,编译器统一对这块进行的处理,编译后析构函数的名称统一处理成destructor

  • 既然提到了,析构函数的重写,那么为什么析构函数必须重写呢,如果不会怎么样

1.先运行没有加virtual关键字的析构函数代码,可以看到运行的结果,在析构a2的时候,不构成多态,并没有释放派生类的那部分,会导致内存泄漏C++三大特性之多态_第2张图片

2.然后运行加上virtual关键字的代码,对比结果可以很明显的发现差别,这里构成的多态,编译器在释放a2的时候,根据实例化时候的压栈顺序,先调用了B的析构函数,然后再调用了A的析构函数,保证了释放的顺序性,没有出现内存泄漏的问题
C++三大特性之多态_第3张图片

然后再提一下C++11中的两个关键字 override 和 final

1.override
帮助用户检查派生类是否重写了基类的虚函数,如果没有编译报错
2.final
放在函数定义的最后边,是虚函数不能被继承,进而不可以被重写

抽象类

概念:包含纯虚函数的类,叫做抽象类,抽象类不可以实现实例化出对象,派生类继承之后只有重写这个纯虚函数,才可以实例化出对象。

C++三大特性之多态_第4张图片

多态的原理

1.虚函数表
先通过调试看一下这段代码,很明显的就可以看出,实例化出对象的时候,按照预期对象a中,应该只有整形_a,然而多出了一个__vfptr,他的全称是virtual function table pointer,直译过来就是虚函数表指针。
C++三大特性之多态_第5张图片
虚函数表:一个含有虚函数的类中,都会有虚函数表指针,虚函数的地址就存在虚函数表中,虚函数表也叫虚表这块的虚表要与继承中的虚基表分清概念

那么我们再看看,派生类中这个表是否存在,与基类中的表有什么不一样呢。
C++三大特性之多态_第6张图片C++三大特性之多态_第7张图片
结合这两幅截图,我们可以得出以下结论

  1. 派生类对象也存在一个虚函数表,而且与基类中的虚表不是同一个表
  2. fun1()是虚表,继承了下来,存在于派生类的虚表中,指针没有变化
  3. fun2()也是虚表,但是我们发现,fun2()的地址在基类和派生类中是不一样的,这是一位func2()完成了重写,虚函数的重写也叫作覆盖
  4. fun3()不是虚函数,虽然继承了下来,但是不是虚函数,没有存在于虚表之中。
  5. 虚函数表本质上就是一个存虚函数指针的指针数组

那么可能会有人疑惑,虚函数存在哪里,虚表又存在哪里呢
首先虚函数没有存在虚表,虚函数和普通函数一样,都是存在代码段。虚表其实也是存在代码段

多态的原理
先看一下这段代码

class A
{
public:
	virtual void func1(){
		cout << "A::func1()" << endl;}
	virtual void func2(){
		cout << "A::func2()" << endl;}
	void func3(){
		cout << "A::func3()" << endl;}
private:
	int _a = 1;
};
class B : public A
{
public:
	virtual void func2(){
		cout << "B::func2()" << endl;}
private:
	int _b;
};

int main()
{
	A* a1 = new A;
	A* a2 = new B;

	a1->func2();
	a2->func2();

	system("pause");
	return 0;
}

满足多态的两个条件,基类的指针调用,虚函数进行了重写。然后我们结合虚表,就可以很清楚的知道,它到底是怎样去调用同一函数,完成多个行为的。
a1指向的是A对象时,a1->func2()在A对象的虚表中,找到与之对应的虚函数,通过地址去call函数A::func2()
a2指向的是B对象时,a2->func2()在B对象的虚表中,找到与之对应的虚函数,通过地址去call函数B::func2()
C++三大特性之多态_第8张图片
基础的概念大概就这么多了,希望大家自己或多或少学到一点点新知识

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