虚函数
为了实现运行时多态,通过指向派生类的基类指针或引用调用派生类中同名覆盖函数。虚函数用法格式为:virtual 函数返回类型 函数名(参数表) {函数体}
虚函数表
虚函数调用是通过虚函数表简介访问的,就是说基类指针或引用指向派生类时,基类的虚函数表被重写。所以调用函数被覆盖。
含虚函数类的实例的第一个指针变量指向虚函数表,
class Base
{
virtual void Show(void) {cout << “Base::show” << endl;}
};
查看Base的实例会在栈中占用的空间
64位系统中 cout<<sizeof(Base)<<endl;输出8;这是一个指针需要的空间,指向虚函数表,虚函数表中存储有函数指针Show;
内存
只有实例才占用内存,抽象是不占用内存的。所以函数函数类的对象的首地址指向的空间放的是虚函数表地址。由于一个类的所有对象共用函数,所以含虚函数类的N+个实例对象指向同一个虚函数表。
我们可以通过指针调用函数。
class Base
{
public:
virtual void Show(void) {cout << "Base::show()" << endl;}
};
//函数指针类型fun
typedef void (*fun)(void);
Base base;
//base首地址做两次指针运算,指向第一个虚函数,指针运算前要强制转换类型
fun pf = ((fun)(&base));
pf();
输出Base::Show();
和base.show();输出一样。
函数没有访问类的属性成员,如果改为
````C++
class Base
{
public:
Base(int i) Id(i) {}
virtual void Show(void) {cout << "Base::show()" << "ID" << Id << endl;}
private:
int Id;
};
//函数指针类型fun
typedef void (*fun)(void);
Base base(32);
//base首地址做两次指针运算,指向第一个虚函数,指针运算前要强制转换类型
fun pf = **((fun**)(&base));
pf();
输出ID值不正确,这是因为调用类的函数时,隐式的把对象指针this传递进去。
所以修改函数指针
typedef void (*fun)(Base & base);
调用
pf(base);
完整代码如下
#include<iostream>
using namespace std;
class Base
{
public:
Base(int i):Id(i) {}
virtual void show() {cout<<"Base::show()"<<endl;}
virtual void GetId() {cout<<"Base::Id:"<<Id<<endl;}
private:
int Id;
};
class Child:public Base
{
public:
Child(int i):Base(i),Id(i) {}
void show() {cout<<"Child::show()"<<endl;}
void GetId() {cout<<"Child::Id:"<<Id<<endl;}
private:
int Id;
};
struct nullStruct//空类有一个占位
{
};
int main()
{
Base base(23);
Child child(34);
Child child2(22);
cout<<"sizeof(int):"<<sizeof(int)<<endl;
cout<<"sizeof(child):"<<sizeof(Child)<<endl;
out<<"sizeof(Base):"<<sizeof(Base)<<endl; //结果为16,内存必须为最长变量的整数倍,即8的整数倍,
cout<<"sizeof(nullStruct:"<<sizeof(nullStruct)<<endl;
cout<<"*(long*)(&child )"<<*((long*)(&child))<<endl; //虚表指针
cout<<"*(long*)(&child2)"<<*((long*)(&child2))<<endl;
cout<<"*(&base)"<<*((long*)(&base))<<endl; //虚表指针
typedef void (*pf_t)(Child &ch);
cout<<"通过指针调用child.show()"<<endl;
pf_t f =**((pf_t**)(&child));
f(child);
cout<<"通过对象调用GetId()"<<endl;
child.GetId();
cout<<"通过指针调用第二个虚函数GetId()"<<endl;
pf_t g = *(*(pf_t**)(&child) + 1);
g(child);
return 0;
}
输出:

虚基类继承,子类继承了父类的虚函数表指针成员,但是,和继承的父对象普通成员变量由父类构造初始化不同,当定义子对象时,这个虚表指针变量的值被子类的虚表指针覆盖。
另外,函数指针的值类似PC值。变量的指针加1则指针的值加变量长度大小,例如
int a;
int *pa = &a;
a的长度是固定的,4个字节
所以(long)(pa + 1) - (long)(pa)结果是4;
而函数的长度是不定的,所以(long)(pf + 1) - (long)(pf)结果是1;