虚函数表就是通过一块连续内存来保存虚函数的地址
单继承虚函数的对象模型
class
A
{
public
:
virtual
void
func1()
{
printf(
"A::func1\n"
);
}
virtual
void
func2()
{
printf(
"A::func2\n"
);
}
public
:
int
_a;
};
class
B
{
public
:
virtual
void
func1()
{
printf(
"B::func1\n"
);
}
virtual
void
func3()
{
printf(
"B::func3\n"
);
}
virtual
void
func4()
{
printf(
"B::func4\n"
);
}
public
:
int
_b;
};
typedef
void
(*
FUNC
) ();
void
PrintVTable(
int
*
VTable
)
{
size_t
i = 0;
for
(;
VTable
[i] !=
NULL
; i++)
{
printf(
"第%d个虚函数:0x%p---->"
, i,
VTable
[i]);
//将取出来的地址强转为函数指针调用函数
FUNC
f = (
FUNC
)
VTable
[i];
f();
printf(
"\n"
);
}
}
int
main()
{
A
a;
B
b;
//打印两个类的虚表
PrintVTable((
int
*)*((
int
*)(&a)));
PrintVTable((
int
*)*((
int
*)(&b)));
system(
"pause"
);
return
0;
}
因为虚表里存的是一个一个的函数指针,而虚标的地址又存在类的最上面的四个字节,所以呀只要我们将最上面的四个字节里的地址取出来然后去根据这个地址来找到虚表,然后我门可以将虚表看做一个数组,数组里的每一个元素都是地址(函数指针),那么我们将这个数组打印即可
那么这个打印虚表的难点为这句
PrintVTable((
int
*)*((
int
*)(&a)))
此句就时将类的地址取出来然后强转为(int*),只将头上4个字节取出,在解引用,但是解引用了之后的值是一个int型的数据,那我们再将它强转为(int *)然后根据地址找虚表即可
那么我们可以发现此代码是在32位下运行的,那么我们怎么写一份代码既可以在32位下运行也可以在64位运行呢
PrintVTable((
int
**)*((
int
**)(&a)))
int**解引用了之后,在多少位下指针就是多少位
我们来一下内存和打印的对应
我们可以看到在原来A类的func1函数的地方B进行了虚函数的重写

我门并没有看到func3和func4 在虚表中这个编译器的一个bug,但其实它是真实存在的,通过打印我们也能看出来
我们再来手动的梳理一下来画张图

多继承虚函数的对象模型
class
A
{
public
:
virtual
void
func1()
{
printf(
"A::func1\n"
);
}
virtual
void
func2()
{
printf(
"A::func2\n"
);
}
public
:
int
_a;
};
class
B
{
public
:
virtual
void
func1()
{
printf(
"B::func1\n"
);
}
virtual
void
func2()
{
printf(
"B::func2\n"
);
}
public
:
int
_b;
};
class
C
:
public
A
,
public
B
{
virtual
void
func1()
{
printf(
"C::func1\n"
);
}
virtual
void
func3()
{
printf(
"C::func3\n"
);
}
public
:
int
_c;
};
typedef
void
(*
FUNC
) ();
void
PrintVTable(
int
*
VTable
)
{
size_t
i = 0;
printf(
"虚表地址:%p\n"
,
VTable
);
for
(;
VTable
[i] !=
NULL
; i++)
{
printf(
"第%d个虚函数:0x%p---->"
, i,
VTable
[i]);
//将取出来的地址强转为函数指针调用函数
FUNC
f = (
FUNC
)
VTable
[i];
f();
}
printf(
"\n\n"
);
}
int
main()
{
C
c;
//打印两个类的虚表
PrintVTable((
int
*)*((
int
*)(&c)));
PrintVTable((
int
*)*((
int
*)(&c) +
sizeof
(
A
) / 4));
system(
"pause"
);
return
0;
}
然后我们来看一下多继承的模型

以下几点需注意
-
当子类里有对两个父类都重写的虚函数时,都会进行覆盖重写
-
子类会继承父类的虚表
-
子类的没有重写的函数放在(先继承哪个父类)就放在那个父类的虚表中,(以上代码子类先继承了A,所以子类里其他的函数都放在A的虚表里)