zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

C++继承 关系 大小 中类 与其 温故而知新
2023-09-27 14:29:20 时间

【本文参考微信公众号:“程序猿”,账号: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"


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;



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


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对象的大小为4,其内容只有一个指针,指向该类的虚函数表。

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

class CSubVirtualClass :

 public CVirtualNull

public:

 CSubVirtualClass();

 ~CSubVirtualClass();

private:

 int m_nubVirtualNull;

CSubVirtualClass::CSubVirtualClass()

 m_nubVirtualNull = 5;


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






一文真正学懂java接口 1.概述 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
jerry.yin 毕业于上海大学通信与信息工程学院,从事流媒体和视频编解码的研究与开发工作; 研究领域包括视频编解码标准、视频处理和流媒体技术、移动互联网技术等。