多态

一、多态中相关概念的解释

A.多态:意思是具有多种形式或形态的情形。

B.多态包括静态多态和动态多态。静态多态包括函数重载和泛型编程,动态多态主要指的是虚函数。

C.静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则出现编译错误。

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

E.使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

二、动态绑定

A.动态绑定的条件:a.必须是虚函数;b.通过基类类型的引用或者指针调用虚函数

我们来看一段代码,看看它们分别最后打印出的结果是什么?

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void Funtest1(int a)
	{
		cout << "Base::Funtest1()" << endl;
	}
	void Funtest2(int a)
	{
		cout << "Base::Funtest2()" << endl;
	}
	virtual void Funtest3(int a)
	{
		cout << "Base::Funtest3()" << endl;
	}
	virtual void Funtest4(int a)
	{
		cout << "Base::Funtest4()" << endl;
	}
};

class Derived :public Base
{
	virtual void Funtest1(int a)//对基类里的Funtest1进行重写
	{
		cout << "Derived::Funtest1()" << endl;
	}
	virtual void Funtest2(int a)//与基类里的Funtest2不构成重写
	{
		cout << "Derived::Funtest2()" << endl;
	}
	void Funtest3(int a)//与基类里的Funtest3构成重写
	{
		cout << "Derived::Funtest3()" << endl;
	}
	virtual void Funtest4(int a, int b)//与基类里的Funtest4不构成重写
	{
		cout << "Derived::Funtest4()" << endl;
	}
};

int main()
{
	Base* pBase = new Derived;
	pBase->Funtest1(0);//因为构成重写,所以调用派生类的Funtest1
	pBase->Funtest2(0);//因为不构成重写,所以调用基类的Funtest2
	pBase->Funtest3(0);//因为构成重写,所以调用派生类的Funtest3
	pBase->Funtest4(0);//因为不构成重写,所以调用基类的Funtest4
	//pBase->Funtest4(0, 0);当加上这条语句时不能通过编译
	system("pause");
	return 0;
}

我们来看看运行结果:

这里面有个重要的概念是重写,在我们解释重写的时候也顺便来看看重载和重定义,比较一下它们之间的不同点:

继承体系同名成员函数的关系:

A.重载:

            a.在同一个作用域;

            b.函数名相同/参数不同

            c.返回值可以不同

B.重写:

            a.不在同一个作用域(分别在基类和派生类中)

            b.函数名相同/参数相同/返回值相同(协变例外)

            c.基类函数必须有virtual关键字

            d.访问修饰符可以不同

C.重定义(隐藏)

            a.在不同作用域中(分别在基类和派生类中)

            b.函数名相同

            c.在基类和派生类中只要不构成重写就是重定义

四、纯虚函数

       在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫做接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

看下面这段代码:

#include<iostream>
using namespace std;

class Person
{
	virtual void Display() = 0;//纯虚函数
protected:
	string _name;
};
class Student :public Person
{};

五、总结

我们来对上述内容先做如下总结:

1.派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)

2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。

3.只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。

4.如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

5.构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。

6.不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。

7.最好将基类的析构函数声明为虚函数。

8.虚表是所有类对象实例共用的。


根据以上总结,我们来提出几个问题:

1.构造函数为什么不能加virtual?

2.static成员函数为什么不能加virtual?

3.赋值运算符重载是否可以用virtual?

4.友元函数为什么不能用virtual?

5.析构函数为什么最好用virtual?


下面我们来一一作答:

1.因为构造函数是用来创建对象的,而虚函数的运行是在对象的基础上运行的,执行构造函数时,对象还未被创建成功,所以不能定义为虚函数。

2.因为静态成员函数属于一个类而不属于一个对象,它没有this指针,所以静态成员函数不能定义为虚函数。

3.赋值运算符重载不建议给成虚函数,因为编译器会自动合成运算符重载函数,会构成覆盖。

4.友元函数不是类的成员函数,它里面没有this指针,所以不能定义为虚函数。

5.在实现多态时,用基类操作派生类,为防止只析构基类而不析构派生类的情况发生,所以最好定义析构函数为虚函数。


下面一篇博客我们将谈到:单继承,多继承和菱形继承的有无覆盖的模型。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值