C++之多态

1、什么是多态?
通俗来说就是不同的对象接收到相同的消息时,产生不同的行为。在C++程序设计中,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,这样就可以用同一个函数名调用不同函数的内容。其实我们之前学习的运算符就使用了多态,例如“+”,它可以实现int型,float型,double型各自的加法操作,但是它在加法的过程中却是不同的,是有不同的函数去实现的。
2、多态分为哪些?
多态分为静多态和动多态。
详解:一个源程序经过编译,连接成为可执行文件的过程是把可执行文件代码联编(也叫绑定)在一起的过程。其中在运行之前就完成的称为静态联编,又叫前期联编;而在运行时才完成的联编称为动态联编,也称后期联编。静态联编是指系统在编译时就决定如何实现某一动作。静态联编要求程序在编译时就知道调用函数的全部信息。因此,这种联编类型的函数调用速度很快。效率高是静态联编的主要优点。动态联编是指系统在运行时动态实现某一动作,采用这种方式联编,一直要到程序运行时才能确定调用哪个函数。动态联编的主要优点是:提供了更好的灵活性、问题抽象性和程序易维护性。
静态联编支持的多态性称为编译时多态性,也称静态多态性。在C++中,编译时多态性是通过函数重载(包括运算符重载)和模板实现的。而动态联编所支持的多态性称为运行时多态性,也称动态多态性。
3、使用多态有什么作用?动多态产生的条件是什么?
使用多态可以消除类型之间的耦合关系,通过分离做什么和怎么做,从另一角度将接口和实现分离开来。(就比如同一个自主购票机,相同的接口,它却可以有成人票、儿童票两种类型);
条件:

  • 指针或引用调用虚函数 + 对象必须完整
  • 完整对象:构造函数执行完毕,析构函数还没开始

下面通过代码来详细解释多态产生过程:
这个还是没加虚特性的:

class Person
{
public:
	Person(int x, int y)
	{
		a = x;
		b = y;
	}
	void show()
	{
		cout << "调用基类show()" << endl;
		cout << a << b << endl;
	}
private:
	int a, b;
};
class my_Person :public Person
{
public:
	my_Person(int x, int y, int z) :Person(x, y)
	{
		c = z;
	}
     void show()
	{
		cout << "调用子类show()" << endl;
	}
private:
	int c;
};

int main()
{
	Person mb(23, 34),*mp;
	my_Person mc(12, 23, 56);
	mp = &mb;
	mp->show();
	mp = &mc;
	mp->show();
	return 0;
}

该代码的执行结果:
在这里插入图片描述
问题1:为什么这里调用的都是基类的show函数?
答:C++中规定,基类的对象指针可以指向它的公有派生的对象,但是当其指向公有派生类是,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员。
问题2:既然都是基类的show函数,为什么打印的结果不一样?
答:当程序执行到mp = &mc;这一步时,基类指针指向子类对象,通过子类提供的参数给基类的对象赋值。

加虚特性之后

class Person
{
public:
	Person(int x, int y)
	{
		a = x;
		b = y;
	}
	virtual void show()
	{
		cout << "调用基类show()" << endl;
		cout << a <<" "<< b << endl;
	}
private:
	int a, b;
};
class my_Person :public Person
{
public:
	my_Person(int x, int y, int z) :Person(x, y)
	{
		c = z;
	}
    virtual void show()//这里也可不写virtual,因为同名函数会直接继承为虚函数
	{
		cout << "调用子类show()" << endl;
		cout << c << endl;
	}
private:
	int c;
};

int main()
{
	Person mb(23, 34),*mp;
	my_Person mc(12, 23, 56);
	mp = &mb;
	mp->show();
	mp = &mc;
	mp->show();
	return 0;
}

在这里插入图片描述
要想详细了解调用过程,在这里需要引入一个新概念:虚函数表(vftable)
虚函数表是在编译期产生的,用来存储虚函数指针,vfptr是虚函数表指针(注意区别)
虚函数表指针是程序编译时就产生的,而虚函数指针是在创建对象的时候才写进虚函数表的

图解:
C++之多态_第1张图片
问题:什么是RTTI,RTTI在什么时候产生?RTTI信息存储在哪里?有什么作用?
运行时期的类型信息,是一个指向类型信息的指针。
编译期产生,RTTI指针放在vftable里面,类型信息放在rodata段
在父类指针转化为子类指针时会提供一个特殊的操作符(拓展部分有解释)。

再联合上边的流程图看这幅监视图:
C++之多态_第2张图片
C++之多态_第3张图片
在监视图可以清晰的看到,子类把从父类继承过来的虚函数表进行覆盖
注意:虚函数表其实就是函数指针的地址,并不是直接把虚函数写入虚函数表,函数调用的时候,是通过函数指针所指向的函数来调用函数。虚函数表存放在只读数据段,虚函数存放在代码段

我们再看一下打印时的监视图:
在这里插入图片描述

C++之多态_第4张图片
由图可以看出,当调用不同的show()函数时,mp就指向不同的对象,对象里的vfptr再指向虚函数表,在虚函数表中找出对应的虚函数。
覆盖:子类中的成员方法会覆盖父类中相同(同返回值,同函数名,同参数列表)的虚函数
虚函数具有传递性:父类中如果有虚函数,那么子类中对应的相同的函数会被传递为虚函数,相同的指的是同返回值,同函数名,同参数列表

我们再来看一个代码:

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	~Base()
	{
		cout << "~Base()" << endl;
	}
	void fun1(int a)
	{
		cout << "Base::void fun1()" << endl;
	}
protected:

private:
	int _a;
};

class Derive :public Base
{
public:
	Derive()
	{
		cout << "Derive()" << endl;
	}
	~Derive()
	{
		cout << "~Derive()" << endl;
	}
	void fun1()
	{
		cout << "Derive::void fun1()"<<endl;
	}
	void fun1(int a,int b)
	{
		cout << "Derive::void fun1(int a)" << endl;
	}
protected:
private:
	int _b;
};


int main()
{
	Base *pb = new Derive();//父类指针指向子类对象
	pb->fun1(10);
	delete pb;
}

在这里插入图片描述
我们发现同时调用了基类的构造函数和子类的构造函数,但是却只调用了父类析构函数,却没有调用子类的析构函数,那么这个问题如何解决呢?
这个问题要解决很简单,只需要给父类析构函数前边加virtual:

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	virtual ~Base()
	{
		cout << "~Base()" << endl;
	}
	void fun1(int a)
	{
		cout << "Base::void fun1()" << endl;
	}
protected:

private:
	int _a;
};

再看执行结果:
在这里插入图片描述
这里需要注意一个问题:我们可能会疑惑析构函数的函数名并不相同,为什么还会覆盖?
C++中规定,析构函数就是它的函数名字。

对上述知识点再作一简单拓展:
4、动多态的产生过程?
使用指针或者引用调用虚函数
在对象中找到vfptr
找到vftable 在表中找到对应的虚函数指针
通过虚函数指针找到虚函数

5、vftable什么时候产生?在哪里存储?
编译期产生
放在rodata(只读数据段)

6、 构造函数能不能写成虚函数
不能
构造函数无法通过指针或者引用调用,所以写成虚函数没有意义
vfptr是在构造时候写入对象,而动态调用虚函数需要用到vfptr
7、 静态函数能不能写成虚函数
不能,静态函数不依赖于对象,无法产生动多态

8、析构函数能不能写成虚函数

9、虚函数能不能被处理成内联?
不能,虚函数需要将函数指针放到vftable,而内联函数在编译期展开
在release版本没有地址。

10、什么情况下析构函数必须写成虚函数?
当存在父类指针指向堆上的子类对象的时候,
就必须把父类的析构函数写成虚函数
11、父子类/组合类 的构造顺序?
类的编译顺序
先编译类名
再编译成员名
最后编译成员函数体

12、父类指针如何转化成子类指针?转化有什么条件?

  Derive* pd =  dynamic_cast<Derive*>(p);
  dynamic_cast 父类指针转为子类指针专用的类型强转

要求:1.必须有RTTI 2.父类指针指向的对象中的RTTI确实是子类的

你可能感兴趣的:(c++,开发语言,后端)