什么是虚函数,就是函数名前面加一个 virtual
举例:
class base{
public:
void function1()
{
printf("funtion");
}
virtual viod funtion2()
{
printf("funtion2");
}
}
base a;
a.function1();
a.function2();
用结构体对象调用,他两没有任何区别 都是属于直接调用 call 地址
base* pbase;
pbase=&a;
pbase->function1();
pbase->function2();
用指针调用的时候,function2是属于间接调用 call dword[edx];
指针调用virtual 函数的时候 是属于间接call
sizeof(base)=12;当结构体里有虚函数的时候 类的大小会多出4个字节 ,不管加几个虚函数都是只多4个字节
多出来的4个字节在对象的首地址 在所有元素最前面
这个4个字节是一个地址,指向虚函数表,虚函数表里存着这个结构里所有虚函数的地址
比如第二个虚函数调用地址就是 call dword[edx+4]
虚函数表里只存储虚函数的地址 跟不是virtual的函数没有关系
函数指针调用虚表函数
typedef void(*Pfunction)(void); 定义一个无参无返回值函数指针
Pfunction pf;
pf=(Punfction) *((int*)(*(int*)&base)+0);
pf();执行
1、单继承无函数覆盖(打印Sub对象的虚函数表) :在虚函数表中得出6个虚函数地址
struct Base
{
public:
virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub:Base
{
public: virtual void Function_4()
{
printf("Sub:Function_4...\n");
}
virtual void Function_5()
{
printf("Sub:Function_5...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n");
}
};
红线部分为虚函数表地址
红框中为虚函数表中 虚函数的地址
2、单继承有函数覆盖(打印Sub对象的虚函数表):在虚函数表中得出只有4个虚函数地址,编译器会自动把重复的函数(名字参数返回值都一样的函数) 定义成同一个地址
struct Base
{
public: virtual void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...\n");
}
virtual void Function_3()
{
printf("Base:Function_3...\n");
}
};
struct Sub:Base
{
public: virtual void Function_1()
{
printf("Sub:Function_1...\n");
}
virtual void Function_2()
{
printf("Sub:Function_2...\n");
}
virtual void Function_6()
{
printf("Sub:Function_6...\n");
}
};
只有virtual函数是动态绑定,动态绑定还有一个别名:多态。
动态绑定:当函数被重写(多继承时被相同名字相同返回值相同参数的函数覆盖),那么你当前调用的哪个结构体指针,它的虚表中的重写函数的地址,就会默认是该结构体内的虚函数地址(如果是个子类,那么会覆盖父类的地址 ):
即两个相同的函数,调用的哪个结构体指针,执行时,就会调用当前结构体内的虚函数,而覆盖掉其它子类或父类的相同函数 ,父类一样名字的虚函数(参数与返回值也相同),会被覆盖不被执行.
而当用父类指针时,子类中一样名字(参数与返回值也相同)的虚函数,会被覆盖不执行。.
总结就是:虚函数在有函数覆盖的情况下,用的哪个结构指针,虚函数表中的虚函数地址就会默认是当前结构内的虚函数
非虚函数被覆盖时,在调用时,编译的时候是写的父类的函数地址,子类的会被覆盖。
在多继承时,析构函数需要定义为虚函数.以防它只调用父类的析构函数。
动态绑定是通过虚表来实现的。
多态==动态绑定
前期绑定则是在编译的时候 就把call的地址固定了的直接调用
动态绑定 则是通过虚表间接调用,执行的时候 才会绑定对应的地址
多态实际应用很少,多半是看别人用了相关代码时 能知道别人到底调用的哪个函数.