栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

(C/C++必学)深入剖析类和其内存结构

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

(C/C++必学)深入剖析类和其内存结构

目录

 

一、前言

二、什么是类

1、类的组成        

2、类的继承

3、虚的概念

虚函数

虚继承

三、类的内存结构

1、类成员变量

2、类函数

情况一:不含虚函数,不存在虚继承

情况二:存在虚继承,不存在虚函数

情况三:存在虚函数,不存在虚继承

情况四:既存在虚函数,又存在虚继承


一、前言

        作为一个不断进步的程序员,在我们会使用一项技能后,不能只知其然不知其所以然,针对非科班或非系统成才的野生程序员,更应该在熟练使用后进行深入剖析和理解。要秉承看懂,会用,理解,复现的步骤进行学习,在使用这项技能后别人问到为什么要这么使用或者面试问到深层次的问题时不至于无所应对。有种“我知道怎么用,但是我不知道为什么”的无力感。

二、什么是类

1、类的组成        

        一个类由类名,类成员函数,类成员变量以及其他(静态变量,友元等)组成,以下面的 Demo 类为例来介绍。

class Demo
{
public:
    Demo()
    {
        n1 = 0;
        n2 = 0;
        n3 = 0;
    }
    ~Demo(){}
    void fun1()
    {
        fun2();
    }
    inline void infun()
    {
        std::cout<<"n1 ="<2、类的继承 

        继承的字面意思是继续前人遗留下来的事业,把前人的知识等接过来。类继承可以理解为把基类(原有类)的结构接过来,在基类的基础上继续开发类的事业,在一个良好的基类上进行派生能够很方便的进行代码维护,且达到了代码复用的目的。继承类型分为:公有继承(public)、保护继承(protected),私有继承(private)。

class base
{
public:
    base(){}
    ~base(){}
    void DisplayName()
    {
        std::cout<<"This is base"< 

公有继承:派生类可见的继承基类的公有成员,保护成员,不可见的继承私有成员(无法访问)。公有继承后,派生类继承的公有成员和保护成员保持基类原有的访问状态。保护继承:派生类可见的继承基类的公有成员,保护成员,不可见的继承私有成员(无法访问)。保护继承后,派生类继承的公有成员和保护成员变为保护的访问状态,只对派生后的子类和友元可见(能够访问)。私有继承: 派生类可见的继承基类的公有成员,保护成员,不可见的继承私有成员(无法调用)。私有继承后,派生类继承的公有成员和保护成员变为私有的访问状态,对派生后的子类不可见(无法访问)。

3、虚的概念

虚函数

        虚函数:基类中使用 [关键字] virtual 声明的能够在一个或多个派生类中被重新定义的函数。

        示例:virtual [class name]::virtualfun();

        纯虚函数:基类中使用 [关键字] virtual 声明的且不进行实现的能够在一个或多个派生类中必须被重新定义的函数。

        示例:virtual [class name]::virtualfun() = 0;

        虚函数表:使用虚函数的类其自己维护的一份虚函数指针的指针数组,这个数组通常被称为虚函数表。

虚继承

        C++这类具有多重继承的语言,实现类的继承时会遇到以下场景:

        类D公有继承于类B和类C,类B和类C公有继承于类A,若类D调用类A的属性或方法,在多重继承的前提下会导致二义性。示例如下: 

class A:
{
public:
    void DisplayClass() { qDebug("this is A"); }
};

class B:public A
{
    
};

class C:public A
{
    
};

class D:public B,public C
{

};

int main()
{
    D d;
    d.DisplayClass();
    return 0;
}

        错误信息 :

        解决方法1:指定由某个继承类调用

class A:
{
public:
    void DisplayClass() { qDebug("this is A"); }
};

class B:public A
{
    
};

class C:public A
{
    
};

class D:public B,public C
{

};

int main()
{
    D d;
    d.B::DisplayClass();
    return 0;
}

        解决方法2:使用虚继承

class A:
{
public:
    void DisplayClass() { qDebug("this is A"); }
};

class B:virtual public A
{
    
};

class C:virtual public A
{
    
};

class D:public B,public C
{

};

int main()
{
    D d;
    d.DisplayClass();
    return 0;
}

三、类的内存结构

1、类成员变量

        测试类如下:

#include 

class TEST
{
public:
    int a;
    char b;
    double c;
};

int main()
{
    qDebug()<<"size of class TEST is:"< 

        根据 double c; 所处的位置不同,类占用的空间也不同,遵循按最大数据结构的变量字节补齐的策略:

    默认排序:首先找到类内最大占用空间的变量 double,占用 8 字节,若变量占用小于 8 字节时应进行字节补齐。例如本类,int 占用 4 字节,后续补齐 4 字节,此时占用 8 字节,紧跟的 char 占用 1 字节,补齐的 4 字节可以容纳,不新增字节,此时占用 8 字节,最后 double 占用 8字节,输出应为 “size of class TEST is:16”。char 在第一位,int 在第二位,double 在第三位,char 占用 1 字节,后续补齐 7 字节,此时占用 8 字节,紧跟的 int 占用 4 字节,补齐的 7 字节可以容纳,不新增字节,此时占用 8 字节,最后 double 占用 8 字节,输出应为 “size of class TEST is:16”。char 在第一位,double 在第二位,int 在第三位(int 位置与 char 位置互换时也一样),char 占用 1 字节,后续补齐 7 字节,此时占用 8 字节,紧跟的 double 占用 8 字节,补齐的 7 字节不可以容纳,新增 8 字节,此时占用 16 字节,最后 int占用 4 字节,后续补齐 4 字节,此时占用 24 字节,输出应为 “size of class TEST is:24”。

        由上可知,在构建一个类时,合理分配补齐的内存能够压缩该类在实际使用中占用的内存。

2、类函数

情况一:不含虚函数,不存在虚继承

        测试类如下:

#include 

class TEST
{
public:
    TEST() {}
    ~TEST(){}
    int getval() {return 1;}
    QString getstr() {return "1";}
};

int main()
{
    qDebug()<<"size of class TEST is:"< 

        若一个类中,只有成员函数(包括构造,析构等函数) ,则这个类应占用空间为 0 字节,实际占用空间为 1 字节(由于 0 字节不好在内存去定位它的地址,就规定了占用 0 字节的类实际要象征性的占用 1 个字节)。

情况二:存在虚继承,不存在虚函数
#include 

class TEST
{
public:
    TEST() {}
    ~TEST(){}
    int getval() {return 1;}
    QString getstr() {return "1";}
};

class X:virtual public TEST
{
public:
    X() {}
    ~X() {}
    int getval() {return 2;}
};

int main()
{
    qDebug()<<"size of class TEST is:"< 

        在 Linux64 位操作系统上,输出为:

 “size of class TEST is:1”。 “size of class X is:8”。

        由此可知,虚继承后的子类内部的虚基类指针大小为 8 字节。(由机器平台以及操作系统位数而定,非固定)

情况三:存在虚函数,不存在虚继承
#include 

class TEST
{
public:
    TEST() {}
    ~TEST(){}
    int getval() {return 1;}
    QString getstr() {return "1";}
    virtual int setval() = 0;
    virtual QString setstr() { return "1";}
};

class X1: public TEST
{
public:
    X1() {}
    ~X1() {}
    virtual int setval() override  { return 3;}
    virtual QString setstr() override {return "3";}
};

int main()
{
    qDebug()<<"size of class TEST is:"< 

        在 Linux64 位操作系统上,输出为:

 “size of class TEST is:8”。 “size of class X1 is:8”。

        由此可知,虚函数的虚表指针大小为 8 字节,且子类继承后重写的虚函数或纯虚函数共用一个虚函数的虚表指针。(由机器平台以及操作系统位数而定,非固定)

情况四:既存在虚函数,又存在虚继承
#include 

class TEST
{
public:
    TEST() {}
    ~TEST(){}
    int getval() {return 1;}
    QString getstr() {return "1";}
    virtual int setval() = 0;
    virtual QString setstr() { return "1";}
};

class X:virtual public TEST
{
public:
    X() {}
    ~X() {}
    int getval() {return 2;}
    virtual int setval() override { return 2;}
};

class X1: public TEST
{
public:
    X1() {}
    ~X1() {}
    virtual int setval() override { return 3;}
    virtual QString setstr() override {return "3";}
};

int main()
{
    qDebug()<<"size of class TEST is:"< 

        在 Linux64 位操作系统上,输出为:

 “size of class TEST is:8”。 “size of class X is:8”。 “size of class X1 is:8”。

        由此可知,虚函数的虚表指针和虚基类指针共用一个指针,大小为 8 字节。(由机器平台以及操作系统位数而定,非固定)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/718191.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号