【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针

👋 欢迎阅读

📑 目录


一、前置讲解

在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。

🧠 前置知识一:面向过程与面向对象

前者关注过程 (步骤 + 函数),后者关注对象(谁来做 + 对象间交互)。

📦 前置知识二:类(class)

把成员变量 和成员函数 封装在一起,定义时只在逻辑上有结构、不分配物理内存。

🔐 前置知识三:访问限定符

public / protected / private 决定成员类外能否直接访问 ,是封装的语法基础。

🏗️ 前置知识四:实例化与对象模型

用类创建对象 才分配空间;对象里只存成员变量 ,成员函数放在公共代码段。

👉 前置知识五:this 指针

每个非静态成员函数 都隐含一个指向当前对象的指针,由编译器自动传。


二、面向过程与面向对象

C 语言是面向过程 的,关注 的是过程 :分析出求解问题的步骤,通过函数调用逐步解决问题。

C++ 是基于面向对象 的,关注 的是对象 :把一件事情拆分成不同的对象 ,靠对象之间的交互完成。

维度 面向过程 面向对象
关注点 过程(步骤) 对象(谁来做)
组织方式 函数 + 数据结构,两者分离 数据 和操作封装在一起
解决问题 拆成步骤,函数逐个调用 拆成对象 ,靠对象交互完成
典型语言 C C++、Java

三、类的初步认识

C 语言结构体中只能定义变量 ,而在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。

不过 C++ 不常直接用 struct------新事物一般要配新名字。之所以改变 struct 的功能,是因为 C++ 对类的理解也是一种结构体,因此才对 struct 做了升级。

3.1 类的定义格式

格式:class 类名

cpp 复制代码
class className
{
    // 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

class 为定义类的关键字 ,className 为类的名字 ,{} 中为类的主体 。注意类定义结束时后面的分号不能省略。

类中的内容称为类的成员:

成员类型 叫法
类中的变量 类的属性 ,或称成员变量
类中的函数 类的方法 ,或称成员函数

3.2 类的两种定义方式

方式 做法 注意
方式一:全部写在类体中 声明和定义都放在类体里 成员函数如果在类中定义 ,编译器可能会把它当成内联函数处理
方式二:声明与定义分离 声明放 .h,成员函数定义放 .cpp 成员函数名前需要加 类名:: ------这其实就是类域 的使用,和之前的命名空间域是一个道理

一般情况下更期望采用第二种方式 ,因为它看起来分工更明确。

3.3 成员变量命名规则的建议

先看一段"很僵硬"的代码:

cpp 复制代码
// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
    void Init(int year)
    {
        // 这里的 year 到底是成员变量,还是函数形参?
        year = year;
    }
private:
    int year;
};

year = year; 自己给自己赋值,成员变量根本没被改------所以一般都建议给成员变量加个前缀区分:

cpp 复制代码
// 建议写法一:成员变量加下划线前缀
class Date
{
public:
    void Init(int year)
    {
        _year = year;
    }
private:
    int _year;
};
​
// 建议写法二:成员变量加 m 前缀
class Date
{
public:
    void Init(int year)
    {
        myear = year;
    }
private:
    int myear;
};
命名风格 示例 好处
下划线前缀 _year 一眼区分成员变量 与形参
m 前缀 myear 同上,常见于老代码库
不区分 year 形参与成员同名时会自赋值,是真实 bug

四、类的访问限定符及封装

4.1 访问限定符

C++ 实现封装的方式是:用类将对象的属性与方法结合在一块 ,让对象更加完善,并通过访问权限选择性地将其接口提供给外部的用户使用。

【访问限定符说明】

序号 规则
1 public 修饰的成员在类外可以直接被访问
2 protected 和 private 修饰的成员在类外不能直接被访问(此处两者类似,区别后面再讲)
3 访问权限作用域从该访问限定符出现的位置开始,直到下一个访问限定符出现时为止
4 如果后面没有访问限定符,则一直到 } 即类结束
5 class 的默认访问权限为 private ,struct 为 public(因为 struct 要兼容 C)

💡 注意:访问限定符只在编译时有用 。当数据映射到内存后,没有任何访问限定符上的区别。

4.2 封装

面向对象的三大特性是:封装、继承、多态 。在类和对象阶段,主要研究封装。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

封装本质上是一种管理,让用户更方便使用类 。库提供的函数都是一种接口,我们不需要知道它怎么实现,只需要知道它有什么作用------就像买电视,你不需要知道它怎么运行,只需要知道怎么用。

关键词 含义
有机结合 数据和操作数据的方法放在同一个类里
隐藏细节 属性和实现细节对外不可见 (private / protected)
公开接口 只把必要的成员函数 用 public 暴露出去
本质 一种管理 手段,让使用者更省心、更安全

五、类的作用域

类定义了一个新的作用域 ,类的所有成员都在类的作用域 中。在类体外定义成员时,需要使用 :: 作用域操作符 指明成员属于哪个类域 ------这一点在讲命名空间的时候也提过一次。

cpp 复制代码
class Person
{
public:
    void PrintPersonInfo();
private:
    char _name[20];
    char _gender[3];
    int  _age;
};
​
// 这里需要指定 PrintPersonInfo 是属于 Person 这个类域
void Person::PrintPersonInfo()
{
    cout << _name << " " << _gender << " " << _age << endl;
}

💡 类外定义成员函数时,类名:: 不只是"格式要求",而是告诉编译器这个 PrintPersonInfo 属于 Person 类域 ------少了它,编译器会把它当成一个普通的全局函数,而全局函数是访问不到私有成员 _name 的。


六、类的实例化

用类这个类型创建对象的过程,称为类的实例化。

要点 说明
类是描述 类是对对象进行描述的,是一个模型 ,限定了类有哪些成员,但并没有分配实际的内存空间来存储它(就像入学时填的学生信息表,表格本身描述学生,但不是学生)
对象才占空间 一个类可以实例化出多个对象 ,实例化出的对象占用实际的物理空间,存储类成员变量
cpp 复制代码
int main()
{
    Person._age = 100;   // 编译失败:error C2059: 语法错误:"."
    Person study;
    study._age = 100;
    return 0;
}

运行结果:

复制代码
编译失败:error C2059: 语法错误:"."

因为类没有分配实际的内存空间存储数据,所以不能直接给成员赋值 ;只有类的实例化对象才分配了实际内存,才能给成员赋值。

再打个比方:类实例化出对象,就像用建筑设计图建造出房子 。类就像设计图 ,只设计出需要什么东西,但并没有实体建筑存在;实例化出的对象才能实际存储数据、占用物理空间。

💡 这里有个容易混的点:"类不分配物理内存"不等于"类没有大小" 。sizeof(类) 是逻辑层面 的答案(比如成员变量按内存对齐算出 8 字节),空类的 sizeof 是 1 字节(占位) ;但类本身不会分配物理空间,只有实例化出的对象才真正占那几字节。


七、类对象模型

要回答的问题:如何计算类对象的大小? 类中既可以有成员变量,又可以有成员函数,那么一个类的对象中到底包含了什么?

从图里可以看清:对象只保存成员变量,成员函数存放在公共的代码段。因此:

结论 说明
类的大小 实际就是该类中成员变量之和
注意点 要算上内存对齐,对齐后才是真实大小
成员函数 不占对象内存,所有对象共享同一份代码

八、this 指针

8.1 this 指针的引出

cpp 复制代码
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;     // 年
    int _month;    // 月
    int _day;      // 日
};
​
int main()
{
    Date d1, d2;
    d1.Init(2022, 1, 11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

运行结果:

复制代码
2022-1-11
2022-1-12

问题来了 :Date 类中有 Init 与 Print 两个成员函数,函数体里没有任何区分对象的代码 ,那么当 d1 调用 Init 时,该函数是怎么知道该设置 d1 而不是 d2 的呢?

C++ 通过引入 this 指针解决该问题:

C++ 编译器给每个"非静态成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),函数体中所有"成员变量"的操作,都是通过该指针去访问。只不过这些操作对用户是透明的------用户不需要传递,编译器自动完成。

8.2 this 指针的特性

序号 特性
1 this 指针的类型是 类名* const ------即 this 指针本身不能被赋值(不能让它指向别的对象)
2 只能在"成员函数"内部使用
3 this 指针本质上是"成员函数"的形参 :对象调用成员函数时,把对象地址作为实参 传给 this 形参。所以对象中不存储 this 指针
4 this 指针是"成员函数"第一个隐含的指针形参 ,一般由编译器通过 ecx 寄存器自动传递,不需要用户传递(不同编译器、不同调用约定下的传递方式可能不同)

💡 记住这一条就能解释很多面试题:this 是形参,不是成员 。所以它不占对象大小 ;也正因为它是形参,静态成员函数没有它(静态函数不属于任何对象)。


九、复盘(附答案)

💡 思考题

  1. 面向过程和面向对象 :两者的核心区别 是什么?C++ 兼容 C 语言,如何在 C++ 中同时使用两种编程范式?

  2. 类的初步认识 :C++ 中 struct 和 class 的唯一区别 是什么?为什么 C++ 要保留 struct 而不是完全用 class 替代?

  3. 访问限定符及封装 :public、protected、private 的访问范围 分别是什么?封装的意义是什么?

  4. 类的实例化 :类实例化对象的内存分配时机 是什么?栈对象和堆对象的生命周期有何不同?

  5. 类对象模型 :空类的大小是多少 ?为什么?成员函数是否占用对象的内存空间?

  6. this 指针 :this 指针的本质是什么 ?为什么 this 指针不能被修改 ?静态成员函数为什么没有 this 指针?

📝 答案

  1. 面向过程 以函数 为核心,数据与操作分离 ;面向对象 以对象 为核心,将数据和方法封装在一起。C++ 兼容 C,可以在 C++ 中继续写 C 风格代码,同时使用类、继承、多态等面向对象特性。

  2. C++ 中 struct 和 class 的唯一区别 是默认访问权限 :struct 默认 public,class 默认 private。保留 struct 是为了兼容 C 语言 ,同时也用于定义纯数据聚合体。

  3. public 类内外均可访问 ,protected 类内和派生类可访问 ,private 仅类内可访问 。封装 隐藏实现细节、暴露接口,提高代码安全性 和可维护性。

  4. 类定义时不分配内存 ,实例化(创建对象)时才分配 。栈对象 在作用域结束时自动销毁 ;堆对象 需要手动 delete 释放,否则内存泄漏。

  5. 空类的大小为 1 字节 ,因为 C++ 标准要求任何对象必须有唯一地址 ,1 字节是占位 用的。成员函数不占用对象内存空间 ,它们存放在代码段,被所有对象共享。

  6. this 是指向当前对象的常量指针 (类名* const this),不能被修改 (不能改指向,但可以通过它改对象内容)。静态成员函数没有 this 指针 ,因为它们属于类而不是对象 ,没有"当前对象"可指,也就无法访问非静态成员。


结语

如果本文对你有帮助,欢迎:

👍 点赞 | ⭐ 收藏 | 👤 关注作者 | 💬 留言交流你的疑问或补充

你的每一次互动都是我继续更新的动力,我们下一篇见!🚀

相关推荐
揽星河..2 小时前
【C语言】结构体
c语言·开发语言
ACP广源盛139246256732 小时前
选型笔记:Type‑C 扩展坞方案评估|DP1.2 转 HDMI2.0 桥接芯片 GSV2201D@ACP
笔记·硬件架构·硬件工程·视频·国产芯片
2601_966949652 小时前
如何封装 qd.klines.batch(),让批量 K 线查询函数在多个策略中复用?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
小超人诞生日记2 小时前
C++ 智能指针的使用及其原理详解
c++·算法
WEI_YANG_JU2 小时前
初中数学教学中合作学习的研究与实践
学习
码农小韩2 小时前
FPGA学习(一)——LED点灯与开发流程
学习·fpga开发·入门·环境配置·仿真·点灯
神仙别闹3 小时前
基于Python实现英词语自动对齐系统
开发语言·python
akinggw3 小时前
libagentcore - 一个基于c++的AI多agent系统升级到2.0版本
开发语言·jvm·c++
李长渊哦3 小时前
Qoder 自定义子智能体(Subagent)学习笔记
chrome·笔记·学习