目录
一、 虚函数
虚函数:
使用 virtual 关键字声明的函数,是动态多态实现的基础。
非类的成员函数不能定义为虚函数。
类的静态成员函数不能定义为虚函数。
构造函数不能定义为虚函数,但可以将析构函数定义为虚函数。
当将基类的某一成员函数声明成虚函数后,派生类的同名函数自动成为虚函数。
二、虚函数原理与虚函数表
c++ 能够在运行时确定调用的函数,是因为引入了虚函数。
一旦类中引入了 虚函数,在程序编译期间(<深度探索c++对象模型 4.2>) 就会创建虚函数表,表中每一项数据都是 虚函数的入口地址。
为了将对象与虚函数表关联起来,编译器会在对象中会增加一个指针成员用于存储虚函数表的位置。
由上图可知当是一个空类的时候,系统会给类一个字节的大小,成员函数不占用类的空间,而图3用virtual关键字声明了虚函数说以类里多了一个指向虚函数表的指针,因为这里是64位的所以指针大小为8个字节;
那么现在用这个指针去访问虚函数
#include <iostream> using namespace std; class A { public: virtual void fun1() { cout << "hello world1" << endl; } virtual void fun2() { cout << "hello world2" << endl; } virtual void fun3() { cout << "hello world3" << endl; } }; typedef void (*P)(); //重定义一下对应函数指针的类型; int main() { A a; cout << sizeof(a) << endl; cout << "指向虚函数表的指针的地址:" << (long *)&a << endl;//由于类里只有一个指针,说以对象的地址与指针的地址相同。 cout << "虚函数表的地址:" << (long *)*(long *)&a << endl; //对指针地址取*就是指针指向的地址,也就是虚函数表的地址。 cout << "第一个虚函数的入口地址:" << (long *)*(long *)*(long *)&a << endl; cout << "第二个虚函数的入口地址:" << (long *)*((long *)*(long *)&a + 1)<< endl; cout << "第二个虚函数的入口地址:" << (long *)*((long *)*(long *)&a + 2)<< endl; P p1; P p2; P p3; p1=(P)(long *)*(long*)*(long *)&a; //将对应的函数指针p指向第一个虚函数的地址; p1();//调用第一个虚函数; p2=(P)(long *)*((long*)*(long *)&a +1); //将对应的函数指针p指向第一个虚函数的地址; p2();//调用第二个虚函数; p3=(P)(long *)*((long*)*(long *)&a +2); //将对应的函数指针p指向第一个虚函数的地址; p3();//调用第三个虚函数; return 0; }
输出结果
基类的指针或引用,指向或引用派生类对象时就是通过虚函数表的指针来找到实际应该调用的函数;
基类与派生类都维护自己的虚函数表,如果派生类重写基类的虚函数,则虚函数表存储的是派生类的函数的地址,没有重写的虚函数则保存的是基类的虚函数表;
#include <iostream> using namespace std; class A //基类 { public: virtual void fun1() { cout << "hello world1" << endl; } virtual void fun2() { cout << "hello world2" << endl; } virtual void fun3() { cout << "hello world3" << endl; } }; class B : public A //派生类 { public: void fun1() //重写基类的虚函数1 { cout << "hello Linux" << endl; } }; typedef void (*P)(); //重定义一下对应函数指针的类型; int main() { B b;//派生类的对象 A &a=b; //基类的引用引用派生类的对象 cout << sizeof(a) << endl; cout << "指向虚函数表的指针的地址:" << (long *)&a << endl;//由于类里只有一个指针,说以对象的地址与指针的地址相同。 cout << "虚函数表的地址:" << (long *)*(long *)&a << endl; //对指针地址取*就是指针指向的地址,也就是虚函数表的地址。 cout << "第一个虚函数的入口地址:" << (long *)*(long *)*(long *)&a << endl; cout << "第二个虚函数的入口地址:" << (long *)*((long *)*(long *)&a + 1)<< endl; cout << "第二个虚函数的入口地址:" << (long *)*((long *)*(long *)&a + 2)<< endl; P p1; P p2; P p3; p1=(P)(long *)*(long*)*(long *)&a; //将对应的函数指针p指向第一个虚函数的地址; p1();//调用第一个虚函数; p2=(P)(long *)*((long*)*(long *)&a +1); //将对应的函数指针p指向第一个虚函数的地址; p2();//调用第二个虚函数; p3=(P)(long *)*((long*)*(long *)&a +2); //将对应的函数指针p指向第一个虚函数的地址; p3();//调用第三个虚函数; return 0; }
输出结果