通过一道题我们先感受一下编译器针对多态的处理
#include
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
char _c
};
int main()
{
cout << sizeof(Base) << endl;
return 0;
}
在x64环境下,显示的结果是12字节
为什么是12字节呢?一个int就4字节,一个char就1字节,内存对齐一下也是8字节呀!为什么显示的结果是12字节呢? 我们调试代码,看看内部结构
通过调试我们发现Base
创建的对象里面有一个指针_vfptr
,如果我们计算sizeof的时候把这个指针算上的话刚刚好的12字节!
那么疑问来了,这个指针到底是什么?为什么会出现在对象里面?这个指针到底有什么作用?
这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表,。那么子类中这个表放了些什么呢?我们接着往下分析
#include
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{
cout << "Func2()" << endl;
}
void Func3()
{
cout << "Func3()" << endl;
}
private:
int _b = 1;
char _c;
};
int main()
{
cout << sizeof(Base) << endl;
Base b;
return 0;
}
通过监视窗口再来看:
我们发现这个指针指向一个数组(虚函数表),这个数组(表)里面存的就是虚函数的地址
知道了包含虚函数的类里面会出现一个指针,这个指针指向一个指针数组,数组里面保存的是类里面的虚函数,接下来我们看看多态的原理
提示:这里可以添加本文要记录的大概内容:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void func(){}
private:
int _a = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
int _b = 0;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Func(p);
Func(s);
return 0;
}
我们看一下父类的虚表指针
我们看到Person对象里面的_vfptr
指向的指针数组里面有两个指针,说明保存的是两个地址,我们看看这两个地址是什么?
然后我们看一下_vfptr
指针指向的数组里面保存的地址
再看一下子类的虚表指针
虚函数需要被重写,一开始子类会将虚函数进行直接拷贝,重写虚函数之后,虚函数的地址就发生改变了
那为什么多态的原理和指向的对象有关?和虚表有什么关系?
父类对象找到虚函数表的地址,在表里面找到虚函数的地址,因为这里是父类对象,直接调用父类对象的虚函数
子类对象赋值给父类对象,这样会产生“切片”,找到子类中父类的那一部分,寻找到虚函数表的地址,但是虚函数表里存的是子类重写的父类虚函数的地址,所以此时_vfptr指向的其实是子类的虚函数指针数组,自认而然掉用的是子类的虚函数
那为什么不使用指针和引用后,就不构成多态了呢?
#include
#include
#include
using namespace std;
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void func() {}
private:
int _a = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
int _b = 0;
};
void Func(Person p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Func(p);
Func(s);
return 0;
}
因为指针和引用是指向子类对象切割出来的父类对象的那一部分
不使用指针和引用后,直接将子类传递给父类,切割出来的子类对象中父类的那一部分,成员拷贝给父类,但是不会拷贝虚表指针