以下代码:
问类实例化出的对象占几个字节?
#include
using namespace std;
class A {
int m_a;
public:
void func() {
cout << "调用类A的func()函数" << endl;
}
};
int main() {
A a;
cout <<"sizeof(a):"<<sizeof(a) << endl;
system("pause");
return 0;
}
结果显而易见 sizeof(a)=4,因为成员函数存放在公共的代码段, 所以只计算成员变量m_a(int型)所占字节的大小。
当我们将成员函数定义为虚函数时,结果却出现了不同的情况:
#include
using namespace std;
class A {
int m_a;
public:
virtual void func() {
cout << "调用类A的func()函数" << endl;
}
};
int main() {
A a;
cout <<"sizeof(a):" <<sizeof(a) << endl;
system("pause");
return 0;
}
我们注意到当成员函数定义为虚函数时,同一个类的实例化对象大小变为了8个字节。多出来的4个字节是怎么回事呢?
另外在对象a中还多出了一个void**类型名为_vfptr的变量。它是一个二级指针, 指针在32位平台中占4字节, 所以这里的结果是8(m_a的4字节+_vfptr的4字节), 那么_vfptr到底是个什么东西? 类中有了虚函数之后才有了_vfptr, 它们之间到底有着什么联系?
当一个类中有虚函数时,编译期间就会为这个类分配一片连续的内存 (虚表vftable),来存放虚函数的地址。类中只保存着指向虚表的指针 (虚函数表指针_vfptr) ,当这个类实例出对象时,每个对象都会有一个虚函数表指针_vfptr 。虚函数其实和普通函数一样,存放在代码段。
一个含有虚函数的类中都至少都有一个虚函数表,因为虚函数的地址要被放到虚函数表中,那么派生类中这个表放了些什么呢?我们接着往下分析。
针对上面的代码我们做出以下改造:
1.我们增加一个派生类去继承基类
2.基类中重写Func1
3.派生类再增加一个虚函数Func2和一个普通函数Func3
#include
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
总结:
当一个类中有虚函数时, 在编译期间,就会为这个类分配一片连续的内存 (虚表vftable), 来存放虚函数的地址, 类中只保存着指向虚表的指针 (虚函数指针_vfptr) , 虚函数其实和普通函数一样, 存放在代码段。当这个类实例出对象时, 每个对象都会有一个虚函数表指针_vfptr 。虚表本质上是一个在编译时就已经确定好了的void* 类型的指针数组 。
注意 : 虚函数表为了标志结尾, 会在虚表最后一个元素位置保存一个空指针。所以看到的虚表元素个数比实际虚函数个数多一个。
在有虚函数的类被继承后, 虚表也会被拷贝给派生类。编译器会给派生类新分配一片空间来拷贝基类的虚表, 将这个虚表的指针给派生类, 而并不是沿用基类的虚表。在发生虚函数的重写时, 重写的是派生类为了拷贝基类虚表新创建的虚表中的虚函数地址。 虚表为所有这个类的对象所共享,是通过给每个对象一个虚表指针_vfptr共享到的虚表。
所以, 重写实际上就是在继承基类虚表时, 把基类的虚函数地址修改为派生类虚函数的地址。
#include
using namespace std;
class Base {
int m_a;
public:
virtual void func() {
cout << "类A的func" << endl;
}
virtual int func1() {
cout << "类A的func1" << endl;
return 0;
}
};
class Derive :public Base {
public:
virtual void func() {
cout << "类B的func" << endl;
}
virtual void func2() {
cout << "类B的func2" << endl;
}
};
int main() {
Base a1;
Base a2;
Derive b;
system("pause");
return 0;
}
基类对象a1,a2中,虚表中的地址相同(虚函数func()和func1()的地址),是因为虚表为类的所有对象共享,是通过给每个对象一个虚表指针_vfptr共享到的虚表。
派生类对象b,继承了基类的虚表,虚函数指针_vptr却和a1,a2的不同,这是因为编译器新分配了一片空间来拷贝基类的虚表。派生类中重写了虚函数func(),由于被重写的虚函数地址会在继承虚表时被修改为派生类函数的地址。所以派生类的虚表中func()的地址被改变了。
我们还发现,派生类中的虚函数func2()却没有出现在派生类中的虚表中。按理来说, 如果派生类中新增了虚函数, 则会加继承的虚表后面。其实这个虚函数地址是存在的,我们可以发现箭头所指的虚函数表vftable[4],其中应该有四个元素,除去虚表中多出的一个空指针,还有另外三个func(),func1(),func2(),只不过这里没有显示func2()。我们可以通过调用监视窗口来查看func2()。
#include
using namespace std;
class Base {
int m_a;
public:
virtual void funcA() {
cout << "基类Base的funcA()" << endl;
}
virtual void func() {
cout << "基类Base的func()" << endl;
}
};
class Base2 {
public:
virtual void funcB() {
cout << "基类Base2的funcB()" << endl;
}
virtual void func() {
cout << "基类Base2的func()" << endl;
}
};
class Derive :public Base, public Base2 {
public:
virtual void func() {
cout << "派生类重写的func()" << endl;
}
virtual void funcC() {
cout << "派生类中新增的虚函数funcC()" << endl;
}
};
int main() {
Derive d;
Base2 c;
Base a;
system("pause");
return 0;
}
派生类继承了两张虚表。派生类中重写了func()函数,派生类自己拷贝基类虚表中含func()的地址都被改变了。
第一张虚表vftable[4],说明其中含有四个元素,除了funcA()、func()、多出来的一个空指针外,还有派生类中新的虚函数funcC()。调用监视窗口可以看到,派生类中新增的虚函数funcC()被加在了派生类拷贝基类的第一张虚表的后面。
多态的构成条件:
原理: 利用虚函数可以重写的特性, 当一个有虚函数的基类有多个派生类时, 通过各个派生类对基类虚函数的不同重写, 实现指向派生类对象的基类指针或基类引用调用同一个虚函数, 去实现不同功能的特性。抽象来说就是, 为了完成某个行为, 不同的对象去完成时会产生多种不同的状态。