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

【本文参考微信公众号:“程序猿”,账号: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、普通、虚拟混合多继承,此时子类的大小为自身大小(若子类或普通父类有虚函数,则为成员变量+虚表指针大小;若都没虚函数,则就为成员变量大小),加上一个虚类指针大小,在加上虚拟父类的大小,在加上普通父类的大小(除虚表指针,因为它和子类公用一个虚表指针)。

时间: 2024-09-12 18:07:14

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

PHP中类的继承关系

原文:PHP中类的继承关系 在PHP中,我时常会写一个类,类写了一个共用方法,然后让子类去继承就能得到相应的功能.假设大致有这么一个父类: 1 <?php 2 class Father{ 3 4 public function __construct(){ 5 echo '我是父类的构造方法!'; 6 } 7 8 protected function say($str = 'Hello World!'){ 9 echo '说了一句话:' . $str; 10 } 11 } 12 ?> 然后呢,

Java中类与类之间的关系

Java中类与类之间的关系存在以下关系: 1.泛化(Generalization) 很简单,就是我们常说的继承.是说子类获得父类的功能的同时,还可以扩展自己的功能. 如图: Java代码中表现为:extends和implements 2.依赖(Dependency) 两个相对独立的咚咚(A和B),当A负责构造B时,A与B形成依赖关系,即A使用B. 如图: Java代码中的表现为局部变量,方法的参数,以及对静态方法的调用 3.关联(Association) 两个相对独立的咚咚(A和B),当A对象持

《C++编程风格(修订版)》——3.3 继承关系

3.3 继承关系 C++编程风格(修订版)现在让我们来看看程序中的继承关系.Stack通过一个保护数据成员vec来为每个派生类提供索引服务,其中vec指向的是一个void类型指针数组.当在派生类中执行入栈和出栈的操作时,Stack将调节私有数据成员top以在数组中移动.在派生类中又分配了一个数组来存储堆栈中的值,并将数组的地址保存在数据成员data中.此外,在派生类中还对vec进行了初始化,这样对于每一个i(只要在数组边界内),vec[i]将指向data[i].从Stack::push()和St

浅谈UML中类之间的五种关系及其在代码中的表现形式

本文转载:http://www.cnblogs.com/DebugLZQ/archive/2013/05/13/3066715.html 什么是类? 将某类东西归纳在一起,可以成为一个类. 类有很多种提炼角度,需要根据系统地目标.业务的场景,选取合适的角度对事物进行归纳. 什么是类图? 类图可能是UML中使用的最多的一种图. 和其他图一样,类图的基本语法并不复杂,可能一两天就能掌握,但是真正做到灵活的使用类图,可能需呀多年的功力. 类图是锻炼OOA(OO Analysis)和OOD(OO Des

对象的继承关系在数据库中的实现方式和PowerDesigner设计

在面向对象的编程中,使用对象的继承是一个非常普遍的做法,但是在关系数据库管理系统RDBMS中,使用的是外键表示实体(表)之间的关系,那么对于继承关系,该怎么在RDBMS中表示呢?一般来说有3种实现方式: Concrete Table Inheritance(具体表继承) Single Table Inheritance(单表继承) Class Table Inheritance(类表继承) 比如在一个教务系统中,有老师学生2个对象,这两个对象都是"人"对象的子类,所以我们可以建立一个P

浅析Hibernate继承关系树的三种映射方式

在向大家详细介绍Hibernate继承关系树的三种映射方式之前,首先让大家了解下Employee类为抽象类,然后全面介绍. 在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,Company类和Employee类之间为一对多的双向关联关系(假定不允许雇员同时在多个公司兼职),Employee类为抽象类,因此它不能被实例化,它有两个具体的子类:HourlyEmployee类和 SalariedEmployee类.由于Java只允许一个类最多有一个直接的父类,因此Employee类.H

ORM中的继承关系映射全解——单表继承体系、一实体一具体表、一实体一扩展表

ORM中的继承关系映射全解--单表继承体系.一实体一具体表.一实体一扩展表.接口映射 本文涉及的内容包括: 1.单表继承体系 2.一实体一具体表 3.一实体一扩展表 4.接口实现映射vs基类继承映射 1.单表继承体系 所谓单表继承体系就是用一张数据库表存储整个继承体系中的所有实体的数据.单表继承体系适合那种继承体系中实体数目相对较少,总记录数相对较少,子类对父类的属性扩展也相对较少的情形. 单表继承体系优点是读/写继承体系中的每个实体的数据,都只需操作一张表,性能较好,并且,新增继承类,或扩展实

精通Hibernate之映射继承关系八

由于关系数据模型不允许一个表的外键同时参照两个表的主键,因此无法对TABLE_D表的A_ID字段定义外键参照约束,而应该通过其他方式,如触发器,来保证A_ID字段的参照完整性.由于TABLE_D表的A_ID字段既可能参照TABLE_B表的ID主键,也可能参照TABLE_C表的ID主键,要求TABLE_B表和TALBE_C表的ID主键具有相同的SQL类型. 在ClassD.hbm.xml文件中,用元素来映射ClassD的a属性: <any name="a" meta-type=&q

精通Hibernate之映射继承关系七

Company与Employee类之间为一对多多态关联关系,如果继承关系树的根类对应一个表,或者每个类对应一个表,那么就能映射Company类的employees集合.本节介绍如何映射多对一多态关联.如图14-11所示,ClassD与ClassA为多对一多态关联关系. 假定与ClassD对应的表为TABLE_D,与ClassA对应的表为TABLE_A,在TABLE_D中定义了外键A_ID,它参照TABLE_A表的主键. ClassD对象的a属性既可以引用ClassB对象,也可以引用ClassC对