面向对象特征-多态

1、多态的相关概念
多态的意思是:一个事物有多种形态。从系统实现的角度看,多态性分为两类:静态多态和动态的多态。函数的重载和运算符的重载都属于静态的多态,在程序编译时系统就能决定调用的是哪一个函数,因此静态的多态又称为编译时的多态性;动态的多态性是在程序运行过程中才动态的确定操作所针对的对象,因此又称为运行时的多态。动态的多态性是通过虚函数实现的。

2、构成多态的条件:
(1)必须要使用关键字virtual修饰基类的成员函数,指明该函数是虚函数;
(2)并且在派生类里面要重新实现,才能实现动态的多态。

3、重写的条件:(重写是实现动态多态的一种重要的方法)
(1)要形成重写必须声明基类里面的函数是虚函数。
(2)在派生类里面的这个函数必须和基类里面的这个函数是函数名相同,参数列表相同,返回值相同(协变除外)。派生类里面可加也可不加virtual这个关键字,但最好加上。
【例】

#include<iostream>  
using namespace std;

class Base
{
public:
	void virtual FunTest1(int i)
	{
		cout << "Base::FunTest1" << endl;
	}
	void FunTest2(int i)
	{
		cout << "Base:: FunTest2" << endl;
	}
	virtual void FunTest3(int i)
	{
		cout << "Base:: FunTest3" << endl;
	}
	virtual void FunTest4(int i, int j)
	{
		cout << "Base:: FunTest4" << endl;
	}
};
class Derived :public Base
{
public:
	void virtual FunTest1(int i)
	{
		cout << "Derived:: FunTest1" << endl;
	}
	virtual void FunTest2(int i)
	{
		cout << "Derived:: FunTest2" << endl;
	}
	void FunTest3(int i)
	{
		cout << "Derived::FunTest3" << endl;
	}
	virtual void FunTest4(int i)
	{
		cout << "Derived::FunTest4" << endl;
	}
};
void FunTest(Base *b)
{
	b->FunTest1(0);
	b->FunTest2(0);
	b->FunTest3(0);
	b->FunTest4(0, 0);
}
int main()
{
	Base b;
	Derived d;
	Base*pb = &b;
	FunTest(pb);
	pb = &d;
	FunTest(pb);
	system("pause");
	return 0;
}

运行结果为:
这里写图片描述
析:
由上述结果可以分析出:FunTest1()这个函数形成了重写,而FunTest 2()这个函数没有形成重写,FunTest 3()这个函数形成了重写,而FunTest()这个函数没有形成重写。

(3)(该条件独立存在)所谓协变就是基类的虚函数的返回值是指针或者引用,而派生类的返回值是派生类的指针或者引用。
【例】

class Base
{
public:
	virtual Base*pFunTest1()
	{
		cout << "Base::pFunTest1()" << endl;
		return this;
	}

};
class Derived :public Base
{
//与基类的返回值不同,返回的是派生类的指针,并且函数名和参数与基类相同,构成了协变
	virtual Derived*pFunTest1()
	{
		cout << "Derived::pFunTest1()" << endl;
		return this;
	}
};
void pFunTest(Base*p)
{
	p->pFunTest1();
}

4、重写和访问修饰符没有关系。
因为我们知道它是在编译期间就形成了虚表,如果你将基类的虚函数的访问限定符设置成私有的和你实现动态多态是没有关系的,因为这个虚函数的入口地址已经存放进入虚函数表了,你加访问限定符只是为了不让类外的成员去访问它或者说去调用它只能由基类自己的成员函数作为外部接口去调用它。一般实现动态的多态我们将派生类里重写时加访问限定符则不影响因为在调用虚函数的时候我们是要用基类的指针或引用去调用在编译时是将它作为基类的虚函数处理的(即静态的多态)。调用是按基类里面的函数静态调用,执行是按照动态执行的。

5、哪些函数不能作为虚函数?
(1)构造函数不能作为虚函数
分析:调用构造函数是为了创建对象,而如果构造函数作为虚函数那么对象还没有创建成功是不可能将它的虚表指针(也就是一个地址)存放在对象的前四个字节中。
(2)静态成员函数不能作为虚函数
分析:静态成员函数是没有this指针的,它不是特定的指向某一个对象我们可以通过类名就直接可以访问静态成员函数,但是我们知道虚表指针是要存放在对象的前四个字节中的。
(3)赋值运算符可以作为虚函数
分析:赋值运算符作为虚函数是为了实现多态,但是根据赋值兼容规则我们知道派生类的对象可以直接赋值给基类的对象,而基类的对象不能直接赋值给派生类的对象,如果我们将赋值运算符作为虚函数是可以实现子类给父类赋值,但如果我们父类给子类赋值程序就有可能会崩溃。
(4)友元函数不能作为虚函数
分析:友元函数根本就不是类的成员函数,因此它不能作为虚函数。
(5)析构函数可以作为虚函数,并且最好将析构函数写成虚函数。
【例】

#include<iostream>  
using namespace std;
class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	virtual ~Base()
	{
		cout << "~Base()" << endl;
	}
};
class Derived :public Base
{
public:
	Derived()
	{
		cout << "Derived()" << endl;
	}
	virtual  ~Derived()
	{
		cout << "~Derived()" << endl;
	}
};
int main()
{
	Base *pb = new Derived;
	delete pb;
	system("pause");
	return 0;
}

运行结果为:
这里写图片描述
分析:
如果不加上virtual这个关键字,程序就会调用基类里面的析构函数,这样只是释放了一部分的空间。而加上这个关键字,就可以判断出它实际上开辟的是派生类对象的空间,因此会去调用派生类的析构函数,而且又因为继承的关系再去调用基类的析构函数,这样就不会造成内存的泄露。

(6)注意:不要在构造函数和析构函数里调用虚函数,因为对象是不完整的,可能造成未定义的行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值