首先来说说什么是虚函数?
简单来说,那些被virtual关键字修饰的成员函数,就是虚函数。
我们为什么要使用虚函数呢?它的作用是什么?
虚函数就是用来实现多态性的,多态是将接口与实现分离(实现以共同的方法,但因个体差异,而采用不同的策略)。
纯虚函数
在虚函数后面写上=0,这就是纯虚函数。包含纯虚函数的类叫做抽象类(接口类)。
概念:
1.派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同。(协变除外)
协变返回类型:在c++中,只要原来的返回类型是指向基类的指针或引用,新的返回类型是指向派生类的指针或引用,覆盖的方法就可以改变返回类型。
通过下面这个例子我们来引出虚函数表
#include<iostream>
#include<windows.h>
using namespace std;
class Base
{
public:
virtual void func1()
{}
virtual void func2()
{}
private :
int a;
};
void Test1()
{
Base b1;
int a = 1;
}
int main()
{
Test1();
system("pause");
return 0;
}
我们来看看b1的监视窗口
这里的_vfptr就是虚函数表
一个类的虚函数表就是存在这个类的首地址(最前边的地方)
我们来研究一下单继承的内存格局
//单继承
#include<iostream>
#include<windows.h>
using namespace std;
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1()
{
cout << "Derived::func1" << endl;
}
virtual void func3()
{
cout << "Derived::func3" << endl;
}
virtual void func4()
{
cout << "Derived::func4" << endl;
}
private:
int b;
};
int main()
{
Derive d1;
system("pasue");
return 0;
}
对于Derire来说,它的虚表里会有什么呢?
由于子类的func1重写了父类的func1,所以我们猜想子类的虚表里有子类的func1,父类的func2,子类的func3,子类的func4,现在我们就来调出监视窗口来看看子类的虚表里存的是什么。
但我们发现监视窗口只有func1和func2,这是为什么呢?
下面我们就来用一段代码看看它到底是怎么回事,是我们分析错了吗?
#include<iostream>
#include<windows.h>
using namespace std;
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1()
{
cout << "Derived::func1" << endl;
}
virtual void func3()
{
cout << "Derived::func3" << endl;
}
virtual void func4()
{
cout << "Derived::func4" << endl;
}
private:
int b;
};
//打印虚函数表
typedef void(*FUNC)(void);
void PrintVTable(int *VTable)
{
cout << "虚表地址" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d个虚函数地址:OX%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d1;
PrintVTable((int*)(*(int*)(&d1)));
system("pasue");
return 0;
}
PrintVTable((int*)(*(int*)(&d1)))是挺难理解的
首先我们拿到的是d1的地址,然后把它强转成int*,这里强转成int*是为了让它读取到前四个字节的内容(指向虚表的地址),然后再对这个地址解引用我们现在已经拿到虚表的首地址的内容(虚表里面存储的第一个函数的地址)了,但是此时这个变量的类型解引用后是int,不能传入参数,所以我们需要再对它进行一个int*的强制类型转换,这样我们就可以传入参数了,开始执行函数,我们来看看输出的结果:
那么输出结果到底对不对呢,我们来看看它的内存分布:
我们发现内存里边的地址和我们打印出来的地址一模一样,所以我们前边对单继承输出结果的判断是正确的,那为什么在没有打印地址的函数时,它打印出来的为什么和我们的推测不一样呢,那可能就是VS的bug了吧!
下来,我们来看看多继承的内存格局
#include<iostream>
#include<Windows.h>
using namespace std;
class Base1
{
public:
virtual void func1()
{
cout << "Base1::func1" << endl;
}
virtual void func2()
{
cout << "Base1::func2" << endl;
}
private:
int b1;
};
class Base2
{
public:
virtual void func1()
{
cout << "Base2::func1" << endl;
}
virtual void func2()
{
cout << "Base2::func2" << endl;
}
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual void func2()
{
cout << "Derive::func3" << endl;
}
private:
int d1;
};
typedef void(*FUNC)(void);
void PrintVTable(int *VTable)
{
cout << "虚表地址" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d个虚函数地址:OX%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void Test1()
{
Derive d1;
//Base虚函数表在对象Base1后面
int* VTable = (int*)(*(int*)&d1);
PrintVTable(VTable);
int*VTable2 = (int*)(*((int*)&d1 + sizeof(Base1) / 4));
PrintVTable(VTable2);
}
int main()
{
Test1();
system("pause");
return 0;
}
我们发现它的输出结果是: 在Base1中有Derive::func1,Base1::func2,Derive::func3;
在Base2中有Derive::func1,Base2::func2;
从这组输出结果我们就可以总结出:当涉及多继承时,子类的虚函数会存在先继承的那个类的虚函数表里。
那么为什么静态成员函数不能定义为虚函数?
因为静态成员函数它是一个大家共享的一个资源,但是这个静态成员函数没有this指针而虚函数表只有对象才能调到,但是静态成员不需要对象就可以调用,所以这里是有冲突的。
为什么不要在构造函数和析构函数里面调用虚函数?
构造函数当中不适合用虚函数的原因是:在构造对象的过程中,还没有为“虚函数表”分配内存。所以这个调用也是违背先实例化后调用的准则。
析构函数当中不适用虚函数的原因是:一般析构函数先析构子类的,当你在父类中调用一个重写的fun()函数,虚函数表里面就是子类的fun()函数,这时候子类已经被析构了,当你调用的时候就会调用不到。
但我们要尽量最好把基类的析构函数声明为虚函数
主要这里我用的是声明而不是调用,以防止有的人不仔细看和前边在析构函数中不能调用虚函数搞混了