虚函数: 实现类的多态性数组
关键字:虚函数;虚函数的做用;多态性;多态公有继承;动态联编函数
C++中的虚函数的做用主要是实现了多态的机制。基类定义虚函数,子类能够重写该函数;在派生类中对基类定义的虚函数进行重写时,须要在派生类中声明该方法为虚方法。spa
当子类从新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,[即B b; A a = &b;] 父类指针根据赋给它的不一样子类指针,动态的调用子类的该函数,而不是父类的函数(若是不使用virtual方法,请看后面★*),且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。而函数的重载能够认为是多态,只不过是静态的。注意,非虚函数静态联编,效率要比虚函数高,可是不具有动态联编能力。.net
★若是使用了virtual关键字,程序将根据引用或指针指向的 对 象 类 型 来选择方法,不然使用引用类型或指针类型来选择方法。指针
下面的例子解释动态联编性:code
class A{ private: int i; public: A(); A(int num) :i(num) {}; virtual void fun1(); virtual void fun2(); }; class B : public A{ private: int j; public: B(int num) :j(num){}; virtual void fun2();// 重写了基类的方法 }; // 为方便解释思想,省略不少代码 A a(1); B b(2); A *a1_ptr = &a; A *a2_ptr = &b; // 当派生类“重写”了基类的虚方法,调用该方法时 // 程序根据 指针或引用 指向的 “对象的类型”来选择使用哪一个方法 a1_ptr->fun2();// call A::fun2(); a2_ptr->fun2();// call B::fun1(); // 不然 // 程序根据“指针或引用的类型”来选择使用哪一个方法 a1_ptr->fun1();// call A::fun1(); a2_ptr->fun1();// call A::fun1();
实现原理:虚函数表+虚表指针对象
关键字:虚函数底层实现机制;虚函数表;虚表指针blog
编译器处理虚函数的方法是:为每一个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每一个类使用一个虚函数表,每一个类对象用一个虚表指针。继承
举个例子:基类对象包含一个虚表指针,指向基类中全部虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。看下面两种状况:图片
若是派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。
若是基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,并且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,若是派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
下面的图片体现了上述的底层实现机制:
编译器处理虚函数的方法是:
给每一个对象添加一个指针,存放了指向虚函数表的地址,虚函数表存储了为类对象进行声明的虚函数地址。好比基类对象包含一个指针,该指针指向基类全部虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,若是派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,若是派生类没有从新定义虚函数,该虚函数表将保存函数原始版本的地址。若是派生类定义了新的虚函数,则该函数的地址将被添加到虚函数表中,注意虚函数不管多少个都只须要在对象中添加一个虚函数表的地址。
调用虚函数时,程序将查看存储在对象中的虚函数表地址,转向相应的虚函数表,使用类声明中定义的第几个虚函数,程序就使用数组的第几个函数地址,并执行该函数。
使用虚函数后的变化:
(1) 对象将增长一个存储地址的空间(32位系统为4字节,64位为8字节)。
(2) 每一个类编译器都建立一个虚函数地址表
(3) 对每一个函数调用都须要增长在表中查找地址的操做。
虚函数的注意事项
总结前面的内容
(1) 基类方法中声明了方法为虚后,该方法在基类派生类中是虚的。
(2) 若使用指向对象的引用或指针调用虚方法,程序将根据对象类型来调用方法,而不是指针的类型。
(3)若是定义的类被用做基类,则应将那些要在派生类中从新定义的类方法声明为虚。
构造函数不能为虚函数。
基类的析构函数应该为虚函数。
友元函数不能为虚,由于友元函数不是类成员,只有类成员才能是虚函数。
若是派生类没有重定义函数,则会使用基类版本。
从新定义继承的方法若和基类的方法不一样(协变除外),会将基类方法隐藏;若是基类声明方法被重载,则派生类也须要对重载的方法从新定义,不然调用的仍是基类的方法。
参考:
http://www.javashuo.com/article/p-yfujlwvp-mt.html
https://blog.csdn.net/HuYingJie_1995/article/details/88085213