前言
不同平台下的字节宽度,以下的示例均在x64的平台上测试,编译器vs2015。
数据类型 | 说明 | 32位字节数 | 64位字节数 | 取值范围 |
---|---|---|---|---|
bool | 布尔型 | 1 | 1 | true,false |
char | 字符型 | 1 | 1 | -128~127 |
unsigned char | 无符号字符型 | 1 | 1 | 0~255 |
short | 短整型 | 2 | 2 | -32768~32767 |
unsigned short | 无符号短整型 | 2 | 2 | 0~65535 |
int | 整型 | 4 | 4 | -2147483648~2147483647 |
unsigned int | 无符号整型 | 4 | 4 | 0~4294967295 |
long | 长整型 | 4 | 8 | – |
unsigned long | 无符号长整型 | 4 | 8 | – |
long long | 长整型 | 8 | 8 | -264~264-1 |
float | 单精度浮点数 | 4 | 4 | 范围-2128~2128 精度为6~7位有效数字 |
double | 双精度浮点数 | 8 | 8 | 范围-21024~21024 精度为15~16位 |
long double | 扩展精度浮点数 | 8 | 8 | 范围-21024~21024 精度为15~16位 |
* | 地址 | 4 | 8 | – |
一、单个的类
1、不包含虚函数的类
1.1 代码示例
注意类CBase0
中的析构函数,不是虚函数。类中不存在任何的虚函数。
#include <iostream>
#include <string>
using namespace std;
class CBase0 {
public:
CBase0() :m_n1(0), m_l1(1) {}
~CBase0() {}
void print() { cout << "CBase0::print" << endl; }
public:
short m_n1;
long long m_l1;
};
int main()
{
CBase0*pBase = new CBase0;
return 0;
}
1.2 调试结果
gdb调试下的结果,因为没有虚汗数,所以不包含虚表:
1.3 小结
没有虚函数的类,不存在虚表。内存分布只有成员变量,成员变量根据声明的先后顺序排列。占用的字节空间根据平台的不同不一样,且会内存对齐到最大的占用字节数。上图黄色表示short m_n1
绿色表示long long
的m_l1
2、包含虚函数
类中包含虚函数(虚析构函数或者普通的虚函数)
2.1 在1.1源码的基础上给析构函数增加了virtual
关键字,声明为虚函数
2.2 调试结果的截图
2.3 小结
包含有虚函数的类,存在虚表,2.2中监视1中_vfptr
的内容。虚表地址0x00007ff77955ebe0
,是对象的内存中前8个字节(2.2内存1第一组框表示),后续的2个框为类的成员变量的空间,同样的是内存对齐。
当包含有多个虚函数的时候,在虚表中多个虚函数的地址根据声明的顺序依次排列。将2.2中类的成员函数print
加上了virtual
关键字修饰,如下图:
二、单层继承的类
1、无多态重载的虚函数
1.1 代码示例——基类CBase
和派生类CDriver
,派生类中没有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
1.1.1 调试的结构
1.1.1.1 派生类实例化对象的内存分布
上图中pDriver
为派生类的对象,和一大节2.1小结一样,包含有同样的结构:前8个字节为虚表(虚表中有两个虚函数的地址,虚析构函数和继承自基类的虚函数);虚表后面紧随的是基类的成员函数,同样是根据声明顺序一次排列的。在虚表描述结束后是派生类CDriver
的成员函数空间(注意:当前派生类是没有虚函数的)。
1.1.1.2 基类指针
将实例化的对象指针强制转到基类指针上就是pBase
指针,他的结构如下描述:
红色部分是派生类CDriver的实例化对象的内存分布,和上面1.2.1描述一致。蓝色部分是基类CBase
自己的内存分布,因为这里的CBase
并不是一个纯接口类(虚函数为纯虚函数的基类),所以蓝色部分__vfptr
是CBase
的虚表。可以通过观察成员变量很好的区分基类和派生类的关系。
因为是将派生类的指针指向了基类,右边的内存分布和1.2.1描述一致。
1.2 代码示例——基类CBase
和派生类CDriver
,派生类中有除析构函数外的虚函数
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
virtual void driver_print() { cout << "CDriver::driver_print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
1.2.1 调试的结构
1.2.1.1 派生类对象内存结构分布
从上图看,使用vs2015编译器 蓝色部分未显示出CDriver
的虚函数driver_print
,【留疑问,使用gdb验证】。按理说是根据声明顺序依次排列的。
1.2.1.2 基类对象的内存分布结构
注意图中第6行 00.exe!CBase::print(void)
,这时候是基类的函数。
2、有多态重载的虚函数
2.1 代码示例——派生类中有多态结构
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver* pDriver = new CDriver;
CBase*pBase = pDriver;
return 0;
}
上面的代码和1.1中的代码差别就是CDriver
中使用多态重载了基类的虚函数print
,派生类中打印了子类的字符串。
2.2 调试的结果
2.2.1 派生类的内存对象
红的部分已经替换成了派生类的函数,这其实就是多态的真正实现。
三、多层继承
1、示例代码
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_n1(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_n1;
};
class CDriver :public CBase {
public:
CDriver() :m_n2(2) {}
virtual ~CDriver() {}
//virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_n2;
};
class CDriver1 :public CDriver {
public:
CDriver1() :m_n2(2) {}
virtual ~CDriver1() {}
//virtual void print() { cout << "CDriver1::print" << endl; }
private:
int m_n2;
};
int main()
{
CDriver1* pDriver = new CDriver1;
CBase*pBase = pDriver;
return 0;
}
2、不同层级派生类重载虚函数时
2.1 派生类1和派生类2不重载基类虚函数
代码同1描述,调试的结果:
2.2 派生类1重载基类的虚函数
代码1描述中,解开屏蔽的第16行代码,调试的结果:
2.1中的CBase::print()
变成了CDriver::print()
,已经发生了替换,运行时体现多态。
2.3 派生类2中重载基类的虚函数
代码1描述中,解开屏蔽的第24行,调试的结果:
红色部分已经变成了CDriver1::print()
,同样已经发生了替换,运行时体现多态。
3、多基类继承的派生类
派生类CDriver继承两个基类,实例化的对象指针指向两个不同的基类指针。
3.1 示例代码
#include <iostream>
#include <string>
using namespace std;
class CBase {
public:
CBase() :m_nBase(1) {}
virtual ~CBase() {}
virtual void print() { cout << "CBase::print" << endl; }
public:
short m_nBase;
};
class CBase1 {
public:
CBase1() :m_nBase1(2) {}
virtual ~CBase1() {}
virtual void print1() { cout << "CBase1::print" << endl; }
public:
short m_nBase1;
};
class CDriver :public CBase, public CBase1 {
public:
CDriver() :m_nDriver(3) {}
virtual ~CDriver() {}
//virtual void print() { cout << "CDriver::print" << endl; }
private:
int m_nDriver;
};
int main()
{
CBase*pBase = new CDriver;
CBase1*pBase1 = new CDriver;
return 0;
}
3.2 调试结果
3.2.1 当派生类中不重载基类的虚函数时,基类指针CBase所指向的对象内存分布:
红色部分是基类CBase的虚表(虚函数指针)和成员变量的内存分布,绿色部分是实例对象CDriver类的分布,两个基类的虚表按继承的顺序分布,展开如下:
注意看基类的虚函数CBase::print
和CBase1::print
,并未重载,都是基类的实现,另一个基类的对象内存分布如下:
3.2.2 派生类重载基类的虚函数时
去掉源码中的第25行屏蔽,重载CBase
的虚函数实现,不对基类CBase1
重载,如下:
从上面不难发现,派生类重载了哪一个基类的虚函数,则派生类的虚表中的哪一个基类的虚函数会被替换成派生类的,这就是多态的实现了。