虚函数
虚函数的作用是什么?
主要是为了实现多态机制,是指在继承层次中基类的指针可以具有多种不同指向,即当基类指针指向某个派生类对象时,通过基类指针可以调用到派生类的函数,而不是基类的函数。
代码演示
class Base { virtual void print(void); }
class Drive1 :public Base{ virtual void print(void); }
class Drive2 :public Base{ virtual void print(void); }
Base * ptr1 = new Base;
Base * ptr2 = new Drive1;
Base * ptr3 = new Drive2;
ptr1->print(); //调用Base::print()
prt2->print();//调用Drive1::print()
prt3->print();//调用Drive2::print()
这是一种运行时多态,基类指针只有在运行时,才知道指向的对象是什么,这种运行时多态是通过虚函数表实现的。
虚函数指针
什么是虚函数指针
当一个类本身定义了虚函数,或者基类有虚函数时,为了支持多态机制,编译器会为该类添加一个虚函数指针vptr。虚函数指针一般放在对象内存布局的第一个位置,对象的地址就是虚函数指针的地址。
代码演示
class Base
{
public:
Base(int i) :baseI(i) {};
virtual void print(void) { cout << "调用了第一个虚函数Base::print()"<<endl; }
virtual void setI() { cout << "调用了第二个虚函数Base::setI()"<<endl; }
virtual ~Base(){ }
private:
int baseI;
};
int main() {
Base b(1000);
int* vptrAdree = (int*)(&b);
typedef void(*Fun)(void);
cout << "----------------------------------------------" << endl;
cout << "对象b的地址" << &b << endl;
cout << "虚函数指针vptr的地址" << (int*)(&b) << endl;
cout << "虚函数vptr加上解引用*结果是虚函数表的地址" << *(int*)(&b) << endl;
cout << "虚函数表的地址转为int类型" << (int*)*(int*)(&b) << endl;
cout << "虚函数表的int类型的地址再转为FUN函数指针类型" << (Fun)*(int*)*(int*)(&b) << endl;
Fun vfunc = (Fun) * (int*)*(int*)(&b);
vfunc();
cout << "虚函数表中第二个虚函数的地址为" << (Fun) * ((int*)*(int*)(&b) + 1) << endl;
vfunc = (Fun) * ((int*)*(int*)(&b) + 1);
vfunc();
}
----------------------------------------------
对象b的地址010FFA94
虚函数指针vptr的地址010FFA94
虚函数vptr加上解引用*结果是虚函数表的地址432948
虚函数表的地址转为int类型00069B34
虚函数表的int类型的地址再转为FUN函数指针类型00061299
调用了第一个虚函数Base::print()
虚函数表中第二个虚函数的地址为000611E0
调用了第二个虚函数Base::setI()
虚函数指针的地址与对象的地址是一致的,虚函数指针指向的内容就是虚函数表的地址(通过加解引用运算法*得到),再将虚函数表的地址转换为int类型,方便以后移动指向虚函数表中的slot。虚函数表中的slot储存的就是每个虚函数的地址,在此基础上增加地址就可以指向不同的slot,所以将对应的地址转化为函数指针的形式,就能通过赋值给函数指针变量来调用对应虚函数。
####typedef void(*Fun)(void);
代码中涉及到这样一句代码,这里需要解释一下。
typedef的用法
- 最常见的用法
int i;
typedef int myint;
myint j;
使用myint来替代关键字int,以后使用myint的位置相对于在用int
- 函数指针
方法1:函数指针的形式是返回类型(*函数名)(参数表)
char (*pFun)(int);//定义一个函数指针,他指向一个返回值类型为char,参数为一个int类型的函数,pFun为一个函数指针
方法2:typedef可以让函数指针更加直观typedef 返回类型(*新类型)(参数表)
typedef char(*PTRFUN)(int);//定义了一种PTRFUN类型,这种类型是某种指向函数的指针,这种函数以int为参数返回值为char
PTRFUN pFun;//使用PTRFUN定义变量pFun以后就可以像方法1一样正常使用这个变量
char gFun(int a){return;}
int main(){
pFun=gfun;
(*pfun)(2);
}
所以typedef void(*Fun)(void);
表示定义了一种Fun类型,这种类型指向返回值为void参数为void的函数,可以使用Fun定义函数指针变量。
总结
编译器在编译的时候,发现基类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表,该表是一个存放每个虚函数地址的一维数组。由于基类和派生类都有虚函数,所以编译器为这两个类都建立一个虚表。
派生类调用虚函数时如何找到正确的函数
编译器为每个带有虚函数的类的对象自动创建一个虚表指针即vptr,这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,所以在调用虚函数的时候能找到正确的函数。