一、多态中相关概念的解释
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.在实现多态时,用基类操作派生类,为防止只析构基类而不析构派生类的情况发生,所以最好定义析构函数为虚函数。
下面一篇博客我们将谈到:单继承,多继承和菱形继承的有无覆盖的模型。