【温故而知新】C++中类的大小与其继承关系

简介: 【本文参考微信公众号:“程序猿”,账号:imkuqin,原文链接】 1、空类: //NullClass.h#pragma onceclass CNullClass{}; 查看CNullClass实例的大小: int...

【本文参考微信公众号:“程序猿”,账号:imkuqin,原文链接

1、空类:

//NullClass.h
#pragma once
class CNullClass
{
};

查看CNullClass实例的大小:

int _tmain(int argc, _TCHAR* argv[])
{
	CNullClass *pNullClass;
	pNullClass = new CNullClass;
	printf("size of CNullClass:%d\n",sizeof(*pNullClass));
	delete pNullClass;
	return 0;
}

输出结果显示,CNullClass实例的大小为1,即空类的实例只占一个字节,且该字节的内容并无实质意义。


2、只包含方法成员的类

//SimpleClass.h
#pragma once
class CSimpleClass
{
public:
	CSimpleClass();
	~CSimpleClass();

	void memthod1();
};

//SimpleClass.cpp
#include "stdafx.h"
#include "SimpleClass.h"


CSimpleClass::CSimpleClass()
{
	printf("CSimpleClass constructed.\n");
}


CSimpleClass::~CSimpleClass()
{
	printf("CSimpleClass destructed.\n");
}

void CSimpleClass::memthod1()
{
	printf("memthod1 called.\n");
}

//main.cpp
#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
	CSimpleClass *pSimpleClass;
	pSimpleClass = new CSimpleClass;
	printf("size of <span style="font-family: 'Microsoft YaHei';">CSimpleClass </span>:%d\n", sizeof(*pSimpleClass));
	delete pSimpleClass;
	return 0;
}

运行结果:


由此可知,一个类的成员函数并不影响类的实际大小。

3、包含数据成员的类

class COneMemberClass
{
public:
	COneMemberClass(int data = 0)
	{
		m_nData = data;
		m_nData2 = 0;
	}
	~COneMemberClass(){};
	void foo();
	void setData2(int data2);
private:
	int m_nData;
	int m_nData2;
};
void COneMemberClass::foo()
{
	printf("data: %d.\ndata2: %d.\n",m_nData,m_nData2);
}

void COneMemberClass::setData2(int data2)
{
	m_nData2 = data2;
}
int _tmain(int argc, _TCHAR* argv[])
{
	COneMemberClass OneMemClass(1);
	OneMemClass.setData2(5);

	OneMemClass.foo();

	printf("Size of COneMemberClass is %d.\n", sizeof(OneMemClass));

	return 0;
}

运行结果:


内存结构如下:


可见,这种情况下,对象的保存内容为数据成员的值,按照声明中的顺序。

4、简单继承类:

class CSonClass :
	public COneMemberClass
{
public:
	CSonClass();
	~CSonClass();
	void foo();
private:
	int m_nSonData;
};
CSonClass::CSonClass()
{
	m_nSonData = 3;
}


CSonClass::~CSonClass()
{
}

void CSonClass::foo()
{
	printf("Son Class member - data:%d\n",m_nSonData);
}
int _tmain(int argc, _TCHAR* argv[])
{
	CSonClass sonObj;
	sonObj.setData2(2);
	sonObj.foo();
	return 0;
}

运行结果:

Son Class member - data : 3
内存结构为:


在内存中,首先存放的是基类的成员,然后是派生类的成员。如果派生类继续向下派生,则其子类的数据继续存放在后续地址中。


5、虚继承的类:

#include "OneMemberClass.h"
class VirtualInheritClass : virtual public COneMemberClass
{
private:
	int nData;
};

运行结果:


内存结构:


可见,在虚继承的情况下,类的最前面多了8个字节的数据,这些字节中保存了一个指针变量,指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。

另外对于某一个类继承自多个父类(B和C),这些父类又虚拟继承自一个共同的父类(A)的情况,内存结构如:(偏移量指针)(B的数据成员)(偏移量指针)(C的数据成员)(本对象的数据成员)(基类A的数据成员)。虚拟继承利用虚基类偏移量表指针,使得即使某各类被重复继承,基类的成员也只存在一次。


6、带有虚函数的情况

第一种,一个空类中声明了一个虚函数:

class CVirtualNull
{
public:
	CVirtualNull();
	~CVirtualNull();
	virtual void foo();
};
CVirtualNull::CVirtualNull()
{
	printf("VirtualNull Class constructed.\n");
}


CVirtualNull::~CVirtualNull()
{
}

void CVirtualNull::foo()
{
	printf("Foo.\n");
}

在这种情况下,CVirtualNull对象的大小为4,其内容只有一个指针,指向该类的虚函数表。

第二种,某个子类继承了一个声明了虚函数的父类:

class CSubVirtualClass :
	public CVirtualNull
{
public:
	CSubVirtualClass();
	~CSubVirtualClass();

private:
	int m_nubVirtualNull;
};
CSubVirtualClass::CSubVirtualClass()
{
	m_nubVirtualNull = 5;
}


CSubVirtualClass::~CSubVirtualClass()
{
}

此时,CSubVirtualClass对象的大小为8字节。CSubVirtualClass类虽然没有直接声明虚函数,但是依然包含了基类的虚函数表(地址不同,指向内存的内容)。内存结构如下:


在单继承情况下,当子类自己定义了虚函数时,依然只有一个虚函数表(继承自父类),子类的虚函数指针将添加到这个虚函数表的后面。但当多继承时,子类中可能包含多个虚函数表,分别对应每一个父类。

第三种,包含虚函数类的虚继承

class VirtualInheritance
{
private:
	int m_men1;
public:
	virtual void aa(){};
};

class sonClass1 : public virtual VirtualInheritance
{
private:
	int m_mem2;
public:
	virtual void bb(){};
};

class sonClass2 : public virtual sonClass1
{
private:
	int m_mem3;
public:
	virtual void cc(){};
};

在 visual studio中的输出结果为:


对于VirtualInheritance类,其包含一个虚函数表+自身成员,大小为8。

对于sonClass1类,由于是虚拟继承因此包含一个虚函数偏移指针;由于自身存在虚函数,因此包含一个指向虚函数表指针;随后是自身的数据成员,最后是完整的父类对象。sonClass2情况与此类似。


总结:


1,普通单继承,只需将自身成员变量的大小加上父类大小(父类中 有虚函数,子类中不管有没有)若父类没有虚函数,则子类大小需要加上指向虚表的指针大小。


2,普通多继承,若几个父类都有虚表,则子类与第一个父类公用一个虚表指针,其他有几个有虚函数的父类则就有几个虚表指针。


3,虚拟单继承,此时若子类有虚函数则加上一个自身的虚表指针的大小,(若没有则不加)再加上自身的成员变量大小,还要加上一个虚类指针ptr_sonclass_fatherclass,最后加上父类的大小。


4,多重虚拟继承,此时若子类有虚函数则加上一个自身的虚表指针的大小,(若没有则不叫)再加上自身的成员变量大小,还要加上 一个公用的虚类指针(不管有几个虚拟父类,只加一个),在加上所有父类的大小。


5、普通、虚拟混合多继承,此时子类的大小为自身大小(若子类或普通父类有虚函数,则为成员变量+虚表指针大小;若都没虚函数,则就为成员变量大小),加上一个虚类指针大小,在加上虚拟父类的大小,在加上普通父类的大小(除虚表指针,因为它和子类公用一个虚表指针)。





目录
相关文章
|
算法 数据处理 C语言
【C++迭代器深度解析】C++迭代器类型之间的继承关系与反向迭代器的独特性
【C++迭代器深度解析】C++迭代器类型之间的继承关系与反向迭代器的独特性
200 0
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
506 1
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
1039 0
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
|
C++
利用IDA和LLDB探索WebCore的C++类的继承关系
开刀的类名叫 PluginWidgetIOS,利用lldb可以得到: (lldb) image lookup -r -s PluginWidgetIOS 7 symbols match the regular expression 'PluginWidgetIOS' in /Applications/Xcode.
1019 0
|
7月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
3月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
90 0
|
3月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
169 0
|
5月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
176 12
|
6月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
128 16