1、多态的定义
多态指同一个实体同时具有多种形式。它是面向对象程序设计的一个重要特征。如果一个语言只支持类而不支持多态,只能说明它是基于对象的,而不是面向对象的。C++中的多态性具体体现在运行和编译两个方面。运行时多态是动态多态,其具体引用的对象在运行时才能确定。编译时多态是静态多态,在编译时就可以确定对象使用的形式。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
c++中有5种方法来实现虚函数,抽象类,重载,覆盖,模版。
2.对象的类型的确定
首先,前边我们已经提到过多态分为动态多态(运行)与静态动态(编译),对象的类型呢,也分为动态类型和静态类型。
来看一下下边的例子,就会明白他具体的意思是怎么回事啦
3.多态的分类
3.1静态多态(函数重载、泛型编程)
编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
下面是一个函数重载的例子:
3.2动态多态(虚函数)
动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
基类中指定虚函数必须写上virtual关键字,派生类中可以不用写,但最好还是写上,这样看起开更明确。
动态绑定的条件分为以下俩点:
(1)必须是虚函数
(2)通过基类类型的的指针或者引用调用虚函数
看下面的程序:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <stdlib.h>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{}
virtual void FunTest()//虚函数,可以再子类中进行重新定义
{
cout << _a << endl;
}
private:
int _a;
};
class B:public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{}
virtual void FunTest()//重新定义
{
cout << _b << endl;
}
private:
int _b;
};
int main()
{
A a(2);
A* pa;//定义基类指针
B b(3, 4);
pa = &a;//pa指向基类
pa->FunTest();
pa = &b;//pa指向派生类
pa->FunTest();
system("pause");
return 0;
}
结果为:
如果不是虚函数,则每次只是调用基类的FunTest函数。
来看下面一道程序:
class CBase
{
public:
virtual void FunTest1(int _iTest){ cout << "CBase::FunTest1()" << endl; }
void FunTest2(int _iTest){ cout << "CBase::FunTest2()" << endl; }
virtual void FunTest3(int _iTest1){ cout << "CBase::FunTest3()" << endl; }
virtual void FunTest4(int _iTest){ cout << "CBase::FunTest4()" << endl; }
};
class CDerived :public CBase
{
public:
virtual void FunTest1(int _iTest){ cout << "CDerived::FunTest1()" << endl; }
virtual void FunTest2(int _iTest){ cout << "CDerived::FunTest2()" << endl; }
void FunTest3(int _iTest1){ cout << "CDerived::FunTest3()" << endl; }
virtual void FunTest4(int _iTest1, int _iTest2)
{
cout << "CDerived::FunTest4()" << endl;
}
};
int main()
{
CBase* pBase = new CDerived;
pBase->FunTest1(0);
pBase->FunTest2(0);
pBase->FunTest3(0);
pBase->FunTest4(0);
system("pause");
return 0;
}
结果为:
从此,我们引出了继承体系中同名成员函数之间的关系:
3.3纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
抽象类使用规则:
<1>抽象类只能作为其他类的基类来使用,不能建立抽象类对象。
<2>不允许从具体类派生出抽象类。
<3>抽象类不能用作函数的参数类型、函数的返回类型或者显式转换的类型。
<4>可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。
<5>如果派生类中没有定义纯虚函数的实现,而派生类只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类给出了基类纯虚函数的实现,则该派生类不再是抽象类,而是可以建立对象的具体类。
总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容
易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会
出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
8、虚表是所有类对象实例共用的
4、虚表剖析
对于有虚函数的类(即抽象类),编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针,虚表中按照虚函数在类中的声明顺序依次存放了各个虚函数的地址。