本笔记主要记录运行时多态——虚函数实现。如下,代码可以先略过直接看下面结论。
多态:根据实际的对象类型决定函数调用语句的具体调用目标
typedef void(*VFUNC)(void);
/*
typedef void (*)() VE;
int arr[i] int []*/
void PrintTable(VFUNC table[])//VFUNC * table,数组里面都是指针,以0结束
{
cout << "虚表指针" << table << endl;
for (size_t i = 0; table[i] != nullptr; ++i)
{
cout << "vatable" << i<<" : "<< table[i];
VFUNC vf = table[i];
vf();
}
}
//多态使用虚函数表来实现
//虚函数在编译阶段生成,存储在静态区,推荐把基类的析构函数定义为虚函数,
//虚函数表指针是在构造函数初始化列表阶段初始化的
class Base1
{
public:
virtual void func1() { cout << "我是base1函数1" << endl; }
virtual void func2() { cout << "我是base1函数2" << endl; }
int a;
};
class Base2
{
public:
virtual void func1() { cout << "我是base2函数1" << endl; }
virtual void func2() { cout << "我是base2函数2" << endl; }
int a;
};
class Base3
{
public:
virtual void func1() { cout << "我是base2函数1" << endl; }
virtual void func2() { cout << "我是base2函数2" << endl; }
int aaa;
};
class Derive : public Base2,public Base1,public Base3
{
public:
virtual void func1() { cout << "我是派生类函数1" << endl; }
virtual void func3() { cout << "我是派生类函数3" << endl; }
virtual void func4(){}
int a;
};
int main()
{
Base1 b;
PrintTable((VFUNC*)(*(int*)&b));//取对象b的头四个字节。即强转为int* 。再解引用,然后强转为函数参数类型VFUNC*
cout << "_________________________________________" << endl;
Derive D;
PrintTable((VFUNC*)(*(int*)&D));
PrintTable
(
(VFUNC*)
(*(int*)
(
(char*)
&D+sizeof(Base2)
)
)
); //取对象D的头四个字节。即强转为int* 。再解引用,然后强转为函数参数类型VFUNC*,但要加上偏移量才能取到
cout << "_________________________________________" << endl;
D.Base1::a = 1;
D.Base2::a = 0;
cout << D.Base1::a <<" "<< D.Base2::a<< endl;
D.a = 2;
cout << D.a << endl;
Base3 b3;
cout <<"派生类大小:"<<sizeof(D)<<endl <<"基类大小:"<<sizeof(b)<<sizeof(b3)<< endl;
//在此处打断点
}
打印结果如图,利用监控内存得到3个内存图
内存图1
内存图2
内存图3
(另外,我将base3类中的打印信息改成 virtual void func2() { cout << "我是base3函数2" << endl; }依然会打印“我是base2函数2”,这是因为D是先继承base2函数的,同名函数使用的是先继承的
)
对以上监视窗口和控制台打印信息,我们很容易得出虚函数在多态中的性质。以及虚函数是如何实现多态的。
1,一个有趣现象,基类和派生类中都有int a,通过作用域赋值,可以发现同名变量可以不同赋值,但这样是不好的写法,应该使用虚继承。如图,基类的a并没有初始化,我们初始化的只是D
2,基类base1对象b为8个字节,包含一个虚表指针和一个int,虚表指针存储的地址指向一段空间(虚函数表vtable),虚函数表存储2个base1函数指针,利用函数指针调用函数确定为其基类函数。(看内存图1)
派生类Derive对象D为28字节(三个基类的int外加自己的int 是4x4=16字节,剩余12个字节是3个基类函数指针vptr)。既然有3个基类指针指向3个虚函数表,我们就可以得到3张表,不过为了方便,我们就拿前2张表举例子,第一张虚基类表的在类的前四个字节,第二张表在[5,8]字节处。(利用指针我们取得了2张表,原理在代码注释中给出)内存图2和内存图3都是派生类虚函数表。有了这些虚表,我们在使用中,就可以依据不同的条件使用不同函数。
Base1有基类函数fun1,fun2;Base2也有基类函数fun1,fun2;(Base3可以忽略,是我用来验证类大小和基类大小关系的),
我们的派生类Derive 重写虚函数(Override virtual function)fun1,并添加自己的fun3,fun4。
Base1的fun1,Base2的fun1,自己的fun1 三个同名函数三个函数内容可以不一样。
同时继承Base1的虚表,和Base2的虚表上有对应的fun2,虽然Derive没有实现fun2,但是也可以多态调用基类的fun2。Derive自己重写虚函数fun1,虚函数表中就改为派生类fun1(不影响基类的虚表,二者存储空间不同,是2张表)。
实际中的多态一般是一个基类,多个派生类,比如基类定义游乐园买票,设置为纯虚函数(virtual void BuyTicket()=0),强制派生类实现,一个派生类实现为买水上乐园票(限白天),一个派生类设置为买陆上区晚场票,一个派生类设置为陆上区早场票。实现了一个接口多个实现。用父类的指针去指向子类。
//上面基础上添加一些代码
class Derive2 :public Base1
{
public:
virtual void func1() { cout << "我是第二个派生类函数1" << endl; }
};
void test_duotai(Base1 &base)
{
base.func1();
}
//现在是Derive2继承Base1,并重写fun1
//下面这些是主函数中的测试,不同方式访问基类和派生类的func1,1111111下面的就是多态的表现,当然基类设置为纯虚函数更加符合设计
D.func1();
D.Base1::func1();
Base1* pb=&D;
pb->func1();
b.func1();
Base1* pb2 = new Derive();
Base1* pb3 = new Derive2();
pb2->func1();
pb3->func1();
cout << "1111111111111111111111111" << endl;
test_duotai(b);
test_duotai(D);
Derive2 dd;
test_duotai( dd);