C++类和对象进阶 —— 与数据结构的结合

微软技术分享
优质创作者: 编程框架技术领域
领域专家: 操作系统技术领域
2025-06-07 07:58:17

C++类和对象(上)

一、类的定义

1.1类定义格式

  • class 为定义类的关键字,以栈为例:Stack 为类的名字,{} 中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数
  • 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加 _ 或者 m 开头,注意 C++ 中这个并不是强制的,只是⼀些惯例,具体看公司的要求
  • 定义在类中的成员函数默认为 inline(内联)
#include<iostream>
using namespace std;
#include<cassert>

typedef int STDataType;
class Stack
{
public:
    //成员函数
    //1、对栈初始化
    void StackInit(int n = 4)
    {
        array = (STDataType*)malloc(sizeof(STDataType) * n);
        if (array == nullptr)
        {
            perror("malloc fail!");
            exit(1);
        }
        capacity = 4;
        top = 0;
    }

    //2、入栈操作
    void StackPush(STDataType x)
    {
        //增容操作
        if (top == capacity)
        {
            int newCapacity = capacity == 0 ? 4 : 2 * capacity;
            STDataRtpe* temp = (STDataType*)realloc(array, newCapacity * sizeof(STDataType));
        
            if (temp == nullptr)
            {
                perror("realloc fail!");
                exit(1);
            }
            array = temp;
            capacity = newCapacity;
        }

        array[top++] = x;
    }

    //3、检擦栈顶元素
    STDataRtpe StackTop()
    {
        assert(!StackEmpty());
        return array[top - 1];
    }

    //4、判空操作
    bool StackEmpty()
    {
        return top == 0;
    }

    //5、出栈操作
    void StackPop()
    {
        assert(!StackEmpty());
        /*
        断言:
            条件为ture,程序继续执行
            条件为false,程序停止运行
        */
        top--;
    }

    //6、销毁栈
    void StackDestroy()
    {
        if(array)
            free(array);

        array = nullptr;
        top = capacity = 0;
    }

private:

    //成员变量(多个变量的符合)
    STDataType* array;
    size_t capacity;
    size_t top;
};

int main()
{
    Stack st;
    st.StackInit();
    return 0;
}

数据结构 ** 中的栈数据写在 结构体中 ,栈操作实现方法在 结构体外,而在类中将数据(成员变量)与实现方法(成员函数)统一写在类内**


1.2兼容结构体

  • C++struct 也可以定义类,**C++** 兼容C中 struct 的⽤法,同时 struct 升级成了类,明显的变化是 struct 中可以定义函数,⼀般情况下我们还是推荐⽤ class 定义类
  • C++ 中,**struct** 的结构体成员默认持有公共权限 public,而 class 的成员对象默认持有私有权限 private

以链表为例:

/*
    C++ 将 struct 升级为了类
    1、结构体类中可以定义函数
    2、struct 名称就可以代表数据类型
*/

//C++兼容 C 中 struct 的用法
typedef int LTDataType;

typedef struct ListNodeC
{
    LTDataType val;
    struct ListNodeC* next;//不可以直接在结构体中 ListNodeC* next;

}LTNode;

//不需要 typedef 修饰,结构体名 ListNodeCPP 就是数据类型
struct ListNodeCPP
{
    //不仅可以定义变量,也可定义函数
    void LTInit(LTDataType x)
    {
        val = x;
        next = nullptr;
    }

private:

    LTDataType val;
    ListNodeCPP* next;
};

int main()
{
    LTNode* node1 = NULL;
    struct ListNodeC* node2 = NULL;

    ListNodeCPP* node = nullptr;
    node->LTInit(1);

    return 0;
}

本质上:

  • 结构体是数据的集合
  • 类是数据和方法的集合

二、实例化

2.1实例化概念

  • ⽤类类型在物理内存中创建对象的过程,称为类实例化出对象
  • 类是对象进行的⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,⽤类实例化出对象时,才会分配空间
  • ⼀个类可以实例化出多个对象,实例化出的对象 占⽤实际的物理空间,存储类成员变量。打个⽐ ⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多 少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房 ⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据

https://i-blog.csdnimg.cn/direct/50467aeb6a694bdfba9f31ebc0dd6b33.png


class Date
{
public:

    void Init(int year, int month, int day)
    {
        this->year = year;
        this->month = month;
        this->day = day;
    }

private:

    //成员变量是类中的声明(没有开辟任何空间)
    int year;
    int month;
    int day;
};
int main()
{
    //通过类所实例化出的对象为成员变量开辟空间
    Date d1, d2;

    return 0;
}

2.2对象大小

  • 实例对象中,只存储成员变量地址,不存储成员函数地址

​ 分析⼀下类对象中哪些成员呢?类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含 成员变量,那么成员函数是否包含呢?

​ ⾸先 函数被编译后是⼀段指令,在实例对象中没办法存储,这些指令 存储在⼀个单独的区域(代码段),那么实例对象中⾮要存储的话,只能通过 成员函数的指针

​ 再分析⼀下,实例对象中是否有存储指针的必要呢,Date 实例化 d1d2两 个对象,d1d2 都有各⾃独⽴的成员变量 year/_month/_day 存储各⾃的数据,但是 d1d2 的成员函数 Init/Print 指针却是⼀样的,存储在对象中就浪费了。

class Date
{
public:

    void Init(int year, int month, int day)
    {
        this->year = year;
        this->month = month;
        this->day = day;
    }

private:

    //成员变量是类中的声明(没有开辟任何空间)
    int year;
    int month;
    int day;
};
int main()
{
    //通过类所实例化出的对象为成员变量开辟空间
    Date d1, d2;

    cout << sizeof(Date) << endl;
    cout << sizeof(d1) << endl;
    return 0;
}


https://i-blog.csdnimg.cn/direct/7aa7829219a24e28bcad00fab2381563.png

汇编角度:函数调用,被定义结束后是一串指令,这串指令会存储到一个单独的区域(常量区,在操作系统的角度上:也称代码段)



https://i-blog.csdnimg.cn/direct/c012416bfe4d48ac924f5fe024645872.png


​ 成员变量需要独立的空间存放各种来自外界传入的值

C++
    //通过不同实例对象调用的 year 需要不同空间存储独立的值(本质:存储地址不同)
    d1.year++;
    d2.year++;

    //成员函数 Print,所有对象共享同一份函数代码,而非单独存储在某一个对象上
    d1.Print();
    d2.Print();

​ 如果⽤ Date 实例化100个对象,那么 成员函数指针 就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下,其实 函数指针 是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指 令[call 地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找, 只有动态多态是在运⾏时找,就需要存储函数地址,这个我们以后会讲解

  • 函数在本文件中有定义 - 编译阶段确定函数地址
  • 函数定义在外文件 - 链接阶段确定函数

https://i-blog.csdnimg.cn/direct/e4a3a85c5fe64401aff34262473dea9c.png


上面我们分析了 实例对象只存储成员变量C++ 规定类实例化的对象也要符合 **内存对齐 **的规则


2.3内存对齐

设计思路:以空间换时间,每个对象从对齐数的位置开始存放

  • 第⼀个成员在与结构体偏移量为 0 的地址处
  • 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
  • 注意:对⻬数=编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值
  • VS中默认的对⻬数为 8
  • 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩ 就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍

class A
{
public:
    void Print()
    {
        cout << _ch << endl;
    }
private:
    char _ch;
    int _i;
};
class B
{
public:
    void Print()
    {
        //...
    }
};
class C
{ };
int main()
{
    cout << sizeof(A) << endl;//8
    cout << sizeof(B) << endl;//1
    cout << sizeof(C) << endl;//1

    return 0;
}
  • 上⾯的程序运⾏后,我们看到没有成员变量的B和C类对象的⼤⼩是 1,为什么没有成员变量还要给1个 字节呢?因为如果⼀个字节都不给,怎么表⽰对象存在过呢!所以这⾥给1字节,纯粹是为了 占位标识 对象存在

面试经典问题:为什么需要内存对齐?

https://i-blog.csdnimg.cn/direct/1615c1318a074624ac7154f0c1b6523b.png


https://i-blog.csdnimg.cn/direct/8f8f2c653d114c3fbb663611391d9a46.png


https://i-blog.csdnimg.cn/direct/959c185ef2a54b95b6a5f4fb74aaec28.png


三、this指针

  • Date 类中有 InitPrint 两个成员函数,函数体中没有关于不同对象的区分,那当 d1 调⽤ InitPrint 函数时,该函数是如何知道应该访问的是 d1 对象还是 d2 对象呢?那么这⾥就要看到 C++ 给了 ⼀个隐含的 this 指针解决这⾥的问题
  • C++ 规定不能在实参和 形参 的位置显⽰的写 this指针 (编译时编译器会处理),但是可以在函数体内显 ⽰使⽤ this指针
class Date
{
public:

    void Init(int year, int month, int day)
    {
        this->year = year;
        this->month = month;
        this->day = day;
    }

    void Print()
    {
        cout << year << "/" << month << "/" << day << endl;
    }

private:

    int year;
    int month;
    int day;
};
int main()
{
    Date d1, d2;

    d1.Init(2025, 5, 1);
    d2.Init(2025, 5, 2);

    d1.Print();
    d2.Print();
    
    return 0;
}

思考:d1、d2 调用的是同一个 Print,执行的是同一个函数,参数相同,那为什么输出的值不同?

我们先前调用同一个函数,执行出的结果不同,是因为我们传入了不同的参数

编译阶段,编译器为我们处理隐藏过程。实际上,我们调用同一个函数 Print() 执行出了不同结果,还是因为我们传入了不同参数

https://i-blog.csdnimg.cn/direct/b62fabe9c9d94320bfeebfddb35fe3a2.png


同样地,

https://i-blog.csdnimg.cn/direct/4483de818c3a4a75a3a6f669caa2ced1.png

this 本身无法被修改:this = nullptr(false)


3.1常量指针与指针常量

  • 常量指针与指向常量的指针

https://i-blog.csdnimg.cn/direct/c3dd93caae64477886884af7f387022b.png


https://i-blog.csdnimg.cn/direct/cb6a84a7b4674131bcfa7bc853818572.png


https://i-blog.csdnimg.cn/direct/24263ed0b78841b3bfeb3be89d8af1dd.png


验证 this 指针指向 d1/d2对象地址:

https://i-blog.csdnimg.cn/direct/3b8c99f935954519962b709434408d65.png


3.2面试真题

https://i-blog.csdnimg.cn/direct/a766139e8fb34ddc92b925d1ab57fec9.png


答案:

正常运行

https://i-blog.csdnimg.cn/direct/b58b178ec847417390e99cdd746f34e4.png

Print() 编译时确定地址,不存在 对空指针进行解引用的行为


https://i-blog.csdnimg.cn/direct/ade9f5371b874d9399b4bb9d6d3d04cd.png


对象 p 的作用:

  • 搜查、核对成员函数的出处
  • 指针所指向的对象(或对象)传递 this指针

https://i-blog.csdnimg.cn/direct/bdb703d0e6244be380866b47aff5d0af.png


  • 编译错误:语法问题,因此对于空指针的解引用(运行错误)一定不会触及语法编译的问题
  • 链接错误:语法规则符合,但存在函数或变量只声明,未定义行为
  • 运行错误:行为/功能错误

https://i-blog.csdnimg.cn/direct/5ca0cfcdaba64eb5909c95d859eae57f.png


答案:

运行崩溃

https://i-blog.csdnimg.cn/direct/678c9f05ceb545a29cfd462e304da7e7.png

(*p).Print(); 同理


https://i-blog.csdnimg.cn/direct/f6f1d65e92ee470a8428140c0a8487a9.png


解决方法:使 指针p 指向一个有效对象

C++
    A aa;
    A* p = &aa;//或 A* p = new A();

https://i-blog.csdnimg.cn/direct/25cfce3e2754464db9e6bf53eb1ff4e1.png


​ 常量区(语言层面) - 代码段(或叫数据段 - 操作系统角度)(函数被编译成指令后,存储在常量区)

​ 代码段:将函数编译好的代码指令(因此代码指令存储在常量区/代码段)

this指针 所存放的区域中,this指针 存放在对象中这一选项或许会有些许疑虑,但很简单,如果其存放在对象中,那么 sizoef(指针) == 4;这就与我们前面提到的 sizeof(p)(或sizeof(Person))== 1; 背道而驰了,因此,可论证: this指针 并不存储在实例对象中


https://i-blog.csdnimg.cn/direct/2a4efd79e83c4dfabcbc5e4cc6cf8732.png


this指针 是形式参数,严格一点是存储在 栈和寄存器

3.3浅识寄存器 esp/ebp

https://i-blog.csdnimg.cn/direct/e4f83cef05dc4d208da9ca253880e994.png


四、C++与C语言实现Stack对比

⾯向对象三⼤特性:封装、继承、多态,下⾯的对⽐我们可以初步了解⼀下封装。 通过下⾯两份代码对⽐,我们发现 C++ 实现 Stack 形态上还是发⽣了挺多的变化,底层和逻辑上没啥变化

以栈为例,C++ 对栈的各项操作进行了严格封装(减少了 C语言 多样化,但易错误的代码形态),而 C语言 则更依赖于程序员应对形式多样化代码能力的素质

C
    STack st;
    //访问栈顶元素,方式一(直接调用函数 - 推荐):
    st.STop();
    
    //方式二:
    st.arr[st.top - 1];
    方式二:存在一定的不安全性,比如数组越界、0 - 1 == -1 等情况 

  • C++ 中数据和函数都放到了类⾥⾯,通过 访问限定符 进⾏了限制,不能再随意通过对象直接修改数 据,这是 C++封装 的⼀种体现,这个是最重要的变化。这⾥的 封装 的本质是⼀种更严格规范的管理,避免出现乱访问修改的问题 。当然 封装 不仅仅是这样的,我们后⾯还需要不断的去学习
  • C++ 中有⼀些相对⽅便的语法,⽐如 Init 给的缺省参数会⽅便很多,成员函数每次不需要传对象地址,因为 this指针 隐含的传递了,⽅便了很多,使⽤类型不再需要 typedef ⽤类名就很⽅便


文章来源: https://blog.csdn.net/2401_87692970/article/details/147749689
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
156 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
《Cocos2d-x实战:C++卷》[1] 系统论述了Cocos2d-x游戏开发理论与实践。全书内容涵盖了Cocos2d-x的核心类、瓦片地图、物理引擎、音乐音效、数据持久化、网络通信、数据交换格式、内存管理、性能优化、平台移植、程序代码管理、三大应用商店发布产品等。本书共29章,按内容结构可分为六篇: 第一篇开发基础,即第2章~第8章,内容包括Cocos2d-x简介、环境搭建、字符串、标签、菜单、精灵、场景、层、动作、特效、动画和Cocos2d-x用户事件。 第二篇开发进阶,即第9章~第12章,内容包括游戏音乐与音效、粒子系统、瓦片地图和物理引擎。 第三篇数据与网络,即第13章~第17章,内容包括Cocos2d-x中使用的数据容器类、数据持久化、数据交换格式、基于HTTP网络通信和基于Node.js的Socket.IO网络通信。 第四篇设计与优化,即第18章~第20章,内容包括Cocos2d-x中的常用设计模式、Cocos2d-x中的内存管理和性能优化。 第五篇平台移植,即第21章~第23章,内容包括从Win32到Android平台的移植、从Win32到WindowsPhone8平台的移植和从Win32到iOS平台的移植。 第六篇开发实战,即第24章~第29章,内容包括使用Git管理程序代码和多个项目实战——迷失航线手机游戏项目开发、为迷失航线游戏添加广告、发布放到Googleplay应用商店、发布放到WindowsPhone应用商店和发布放到苹果AppStore。

6,368

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧