实验五 C++虚方法表机制
一、实验目的
1.了解面向对象语言的单一继承的翻译
2.加深对面象对象语言中的继承和多态机制的理解
3.理解C++的虚方法表
二、实验说明
本实验通过对C++虚函数表机制的分析和模拟,进一步加深对面向对象语言中的封装、继承和多态机制的理解,为更好地运用好面向对象语言打下更扎实的基础。
三、实验内容
已有以下结构体,请编写一个C语言程序,说明如何实现面向对象语言C++中的虚函数表。假设“父类Base”定义了虚函数base_f和base_g,“子类”只改写了base_g方法。
并编写相应的测试程序来判断是否相应对象是否正确调用了相应的虚方法。
//代表父类Base
typedef structBase{
void *vtr; //存放虚函数地址表的首地址
intb_data;
} Base;
//代表子类Sub
typedef struct Sub{
Base b; //代表子类继承父类
int s_data;
}Sub;
//实现代码///
测试运行环境:VS2012
语言:C语言
备注:代码只是按照我的理解去简单地用C语言去模拟编译器,肯定有不足或者不合理甚至不正确之处。
// -----------by 绿--------------
// -------2013年11月27日12:19:54-------------
// MyPolyTest.cpp : 定义控制台应用程序的入口点。
// 说明如何实现面向对象语言C++中的虚函数表。假设"父类Base"定义了虚函数func_f和func_g,"子类"只改写了func_g方法。
// 并编写相应的测试程序来判断是否相应对象是否正确调用了相应的虚方法。
#include "stdafx.h"
typedef void (*FUNC_BASE_F)(void * obj);
typedef void (*FUNC_BASE_G)(void * obj);
typedef void (*FUNC_SUB_G)(void * obj); // 子类改写了func_g方法
// 模拟根据C++代码编译后,代码段中的函数
void func_base_f(void* obj)
{
printf("func_base_f is called !\n");
}
void func_base_g(void* obj)
{
printf("func_base_g is called !\n");
}
void func_sub_g(void* obj)
{
printf("func_sub_g is called !\n");
}
// 假设根据类声明,模拟编译器的声明编译后Base 类的虚函数表,因为表项要连续地保存在一起,所以用结构体来模拟表格,或许数组也行
typedef struct base_vtable
{
unsigned int rtti;
FUNC_BASE_F base_f;
FUNC_BASE_G base_g;
}base_vtable;
// 假设根据类声明,编译后模拟编译器的声明Sub 类的虚函数表,因为表项要连续地保存在一起,所以用结构体来模拟表格,或许数组也行
typedef struct sub_vtable
{
unsigned int rtti;
FUNC_BASE_F base_f;
FUNC_SUB_G sub_g;
}sub_vtable;
typedef struct Base{
void * vtr; // 存放虚函数地址表的首地址 实际实现上虚函数指针放在后面,为了方便才放置在此。
int b_data;
} Base;
//代表子类Sub
typedef struct Sub{
Base b; // 代表子类继承父类
int s_data;
}Sub;
base_vtable g_base_vtable;
sub_vtable g_sub_vtable;
// 和类有关的编译工作
void compiler_handle()
{
// 初始化基类的虚函数表
g_base_vtable.rtti=0;
g_base_vtable.base_f = func_base_f;
g_base_vtable.base_g = func_base_g;
// 初始化子类的虚函数表
g_sub_vtable.rtti=1;
g_sub_vtable.base_f=func_base_f;
g_sub_vtable.sub_g=func_sub_g;
}
void test_base_obj_call()
{
printf("test_base_obj_call 测试基类对象调用开始\n");
Base b;
b.vtr=&g_base_vtable; // 模拟编译器幕后做的工作,和对象有关的编译器做的额外工作
// ---------b.func_f(); 会被编译器编译成如下代码-------------
unsigned int * ptr = (unsigned int *) (&b);
ptr = (unsigned int * )(*ptr);
FUNC_BASE_F function = (FUNC_BASE_F) (*(ptr+1));
function(&b);
//b.func_g();
function = (FUNC_BASE_G) (*(ptr+2));
function(&b);
printf("本测试结束\n");
}
void test_sub_obj_call()
{
printf("test_sub_obj_call 测试子类对象调用开始\n");
Sub s;
s.b.vtr=&g_sub_vtable; // 模拟编译器幕后做的工作,和对象有关的编译器做的额外工作
// s.func_f(); 会被编译器编译成如下代码
unsigned int * ptr = (unsigned int *) (&s);
ptr = (unsigned int * )(*ptr);
FUNC_BASE_F function = (FUNC_BASE_F) (*(ptr+1));
function(&s);
//s.func_g(); 会被编译器编译成如下代码
function=(FUNC_SUB_G) (*(ptr+2));
function(&s);
printf("本测试结束\n");
}
void test_poly()
{
printf("test_poly 测试基类指针被指向子类,然后调用函数,测试开始\n");
Sub s;
s.b.vtr=&g_sub_vtable; // 模拟编译器幕后做的工作,和对象有关的编译器做的额外工作
Base* pb= (Base*)&s;
// pb->func_f();会被编译器编译成如下代码
unsigned int * ptr = (unsigned int *) (pb); // 现在ptr指向s对象的首地址位置
ptr = (unsigned int * )(*ptr); // 现在ptr指向表项0处的rtti_info
FUNC_BASE_F function = (FUNC_BASE_F) (*(ptr+1));
function(pb);
// pb->func_g() // 实验的关键的地方就在这了,因为func_g被子类给改写了,即子类的虚函数表的对应的表项的
// 函数指针是指向改写后的函数
function = (FUNC_SUB_G) (*(ptr+2));
function(pb);
printf("本测试结束\n");
}
int main()
{
// 模拟编译器编译时,对类所做的有关的编译工作
compiler_handle();
// -----------------模拟基类对象调用2个函数,和多态无关,普通调用-----------------------
test_base_obj_call();
printf("\n");
// -----------------模拟子类对象调用2个函数,和多态无关,普通调用-----------------------
test_sub_obj_call();
printf("\n");
// -----------------模拟基类对象指针指向子类对象首地址后调用2个函数,和多态有关--------------------
test_poly();
}
运行结果
我的理解是:函数调用无非是调用代码段中的函数,虚函数表里的函数运行代码也在代码段,虚函数表里只要保存
代码段中的函数指针。无论是父类的虚函数,还是子类改写父类虚函数后的新函数,都在代码段中有对应。所以才定义这3个不同函数。
typedef void (*FUNC_BASE_F)(void * obj);
typedef void (*FUNC_BASE_G)(void * obj);
typedef void (*FUNC_SUB_G)(void * obj); // 子类改写了func_g方法