C++——多态的原理与使用
C++面向对象的三大特性——封装,继承,多态。本文主要说明多态的实现与原理。C++多态中又有三种方式实现,分别是函数重载、模板函数、虚函数。本文着重说明虚函数实现多态的方式和原理。
一、虚函数与多态
定义:虚函数指在派生类中重载同名的基类函数,而且可以通过基类指针或者引用来访问基类或者派生类的对象所对应的上述函数。
PS:在类定义中,前面有virtual关键字的成员函数称为虚函数。
virtual关键字只用在类定义里的函数声明中,写函数体时不用。
class Base{
virtual int Fun();//虚函数
};
int Base::Fun(){}//virtual字段不用再函数体时定义
1、虚函数下的多态表现形式
主要通过基类的指针或者引用来指向对象的时候,调用对象所对应的虚函数。
PS:派生类的指针可以赋值给基类指针(基类指针 = 派生类指针);反之则不行(需要强制装换)。总之,调用的虚函数取决于对象,而不是指针的类型(特殊情况:在非构造函数,非析构函数的成员函数中调用「虚函数」,是多态!!!)。
举例:
class A{
public:
virtual void Print(){ cout<<”A::Print”<<endl;}
};
//继承A类
class B: public A{
public:
virtual void Print(){ cout<<”B::Print”<<endl;}
};
//继承A类
class D: public A{
public:
virtual void Print(){ cout << ”D::Print” << endl;}
};
//继承B类
class E: public B{
Virtual void Print(){ cout<<”E::Print”<<endl;}
};
int main(){
A a; B b; E e; D d;
A* pa = &a;
B* pb = &b;
D* pd = &d;
E* pe = &e;
pa->Print();//a.Print()被调用,输出:A::Print
pa = pb;
pa->Print();//b.Print()被调用,输出:B::Print
pa = pd;
pa->Print();//d.Print()被调用,输出D::Print
pa = pe;
pa->Print();//e.print()被调用,输出:E::Print
return 0;
}
2、动态联编和静态联编
程序调用函数时,将使用哪个可执行代码块呢?编译器负责选择合适的代码块叫做联编。联编又分为静态联编和动态联编。
静态联编:在编译的时候决定选择哪个函数;
动态联编:在运行的时候决定选择哪个虚函数。
总的来说,编译器会对虚函数使用动态联编,而其他情况下会使用静态联编。
二、虚函数的使用原理
1、虚函数表
虚函数的实现细节是由编译器决定的,通常,编译器处理虚函数的方法是给每个对象添加一个隐藏成员。隐藏成员保存了一个指向地址数组的指针。这种数组被称为虚函数表。每个对象都会有额外的空间来存储指针。在类中定义虚函数的顺序则是虚函数表的顺序,第一个虚函数是数组的第一个元素,以此类推。
2、构造函数与析构函数
构造函数一般不是虚函数,而析构函数往往是虚函数。
在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。
如果在多态的情景下,通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数,就会存在派生类对象的析构函数没有调用到,存在资源泄露的情况。
// 基类
class A
{
public:
A() // 构造函数
{
cout << "construct A" << endl;
}
~A() // 析构函数
{
cout << "Destructor A" << endl;
}
};
// 派生类
class B : public A
{
public:
B() // 构造函数
{
cout << "construct B" << endl;
}
~B()// 析构函数
{
cout << "Destructor B" << endl;
}
};
int main()
{
A *pa = new B();
delete pa;
return 0;
}
结果为:
construct A
construct B
Destructor A
解决办法:把基类的析构函数声明为virtual,派生类的析构函数可以不进行virtual声明;通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数,遵循先构造、再析构的规则。
3、纯虚函数与抽象类
纯虚函数:没有函数体的虚函数
class A
{
public:
virtual void Print( ) = 0 ; //纯虚函数
private:
int a;
};
包含纯虚函数的类叫抽象类,抽象类不能创建对象,只能做基类。抽象类的指针和引用可以指向抽象类派生出来的类的对象。