栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++之多态

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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)
如图:

图解:

再联合上边的监视图看这幅监视图:

在监视图可以清晰的看到,子类把从父类继承过来的虚函数表进行覆盖

我们再看一下打印时的监视图:


由图可以看出,当调用不同的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()"<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++中规定,析构函数就是它的函数名字。

对上述知识点再作一简单拓展:

  • 动多态的过程
    使用指针或者引用调用虚函数
    在对象中找到vfptr
    找到vftable 在表中找到对应的函数

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

  • 构造函数能不能写成虚函数
    不能
    构造函数无法通过指针或者引用调用,所以写成虚函数没有意义
    vfptr是在构造时候写入对象,而动态调用虚函数需要用到vfptr

  • 静态函数能不能写成虚函数
    不能,静态函数不依赖于对象,无法产生动多态

  • 析构函数能不能写成虚函数

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

  • 什么情况下析构函数必须写成虚函数
    当存在父类指针指向堆上的子类对象的时候,
    就必须把父类的析构函数写成虚函数

  • 父类指针能不能指向子类对象?子类指针能不能指向父类对象?
    能,不能

  • 父子类/组合类 的构造顺序
    类的编译顺序
    先编译类名
    再编译成员名
    最后编译成员函数体

  • 什么是RTTI,RTTI在什么时候产生?RTTI信息存储在哪里?
    运行时期的类型信息,是一个指向类型信息的指针。
    编译期产生,RTTI指针放在vftable里面,类型信息放在rodata段

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

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

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

此代码后续再进行解释:

class base
{
public:
	base()
	{
		//this->fun();
	}
	virtual ~base()
	{
		cout << "~base()" << endl;
	}

	void fun()
	{
		cout << "base::fun()" << endl;
	}

	void operator delete(void* p)
	{
		cout << p << endl;
		free(p);
	}

};

class Derive : public base
{
public:
	Derive()
	{
		//this->fun();
	}
	virtual ~Derive()
	{
		cout << "~Derive()" << endl;
	}

	virtual void fun()
	{
		cout << "Derive::fun()" << endl;
	}

	void* operator new(size_t size)
	{
		void* p = malloc(size);

		cout << p << endl;
		return p;
	}
};

int main()
{
	Derive d;
	base* p = new base();//new Derive();
	//delete (base*)((char*)p - 4);
	cout << typeid(p).name() << endl;
	cout << typeid(*p).name() << endl;

	Derive* pd =  dynamic_cast(p);
	cout << pd << endl;
	pd->fun();
	delete p;
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/587998.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号