C++继承中的多态、虚函数、虚析构函数
多态
从C++继承中的兼容原则中我们知道:父类指针可以直接指向子类对象,父类引用可以直接引用子类对象。当父类和子类有相同方法时调用的是父类方法,即是根据指针类型或引用类型来确定调用的方法类型的。如果我们想根据指针实际指向的对象类型(引用实际引用的类型)来决定调用的方法类型,需要把这个函数声明为虚函数,这就是多态。
#include <iostream> using namespace std; class A { public: void print() { cout<<"I am A"<<endl; } virtual void printv() { cout<<"I am vA"<<endl; } }; class B:public A { public: void print() { cout<<"I am B"<<endl; } virtual void printv() { cout<<"I am vB"<<endl; } }; void test(A *pA) { pA->print(); pA->printv(); } int main() { B b; test(&b); return 0; }
执行结果:
I am A I am vB
多态成立的条件 :
1 要有继承
2 要有虚函数重写
3 要有父类指针(父类引用)指向子类对象
虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数。
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象。
#include <iostream> using namespace std; class A { public: A() { p = new char[20]; strcpy(p, "objA"); cout<<"A()"<<endl; } virtual ~A() { delete [] p; cout<<"~A()"<<endl; } private: char *p; }; class B:public A { public: B() { p = new char[20]; strcpy(p, "objB"); cout<<"B()"<<endl; } ~B() { delete [] p; cout<<"~B()"<<endl; } private: char *p; }; class C:public B { public: C() { p = new char[20]; strcpy(p, "objC"); cout<<"C()"<<endl; } ~C() { delete [] p; cout<<"~C()"<<endl; } private: char *p; }; void test(A *pA) { delete pA; } int main() { C *pC = new C; //deletc pC; 直接通过子类指针释放对象,析构函数不需要声明为虚函数 test(pC); //通过父类指针释放对象,析构函数必须声明为虚函数,要不然会只释放父类的成员p return 0; }
执行结果:
A() B() C() ~C() ~B() ~A()
多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表;
虚函数表是一个存储类成员函数指针的数据结构;
虚函数表是由编译器自动生成与维护的;
virtual成员函数会被编译器放入虚函数表中;
存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。
当调用函数func时,编译器首先确定func是不是虚函数:
(1)func不是虚函数:编译器可以直接确定被调用的函数(静态链编,根据指针或引用类型确定)
(2)func是虚函数:编译器根据实际指向的对象的vptr指针指向的虚函数表查找func函数并调用(动态链编,运行时完成)
说明:
1. 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。 而普通成员函数是在编译时就确定了调用的函数。在效率上, 虚函数的效率要低很多。
2.出于效率考虑,没有必要将所有成员函数都声明为虚函数.