C++波澜壮阔40年| 类与对象(上)篇:类的封装、实例化与 this 指针详解
🔥@雾忱星: 个人主页
👀专栏:《数据结构与算法入门指南》、《C++学习之旅》
💪学习阶段:C语言、数据结构与算法初学者
⏳“人理解迭代,神理解递归。”
文章目录
引言
类是 C++ 面向对象编程的核心,实现了数据与方法的封装,其定义、访问限定与类域规则构成了基础框架。
实例化让抽象的类转化为实际内存中的对象,对象大小计算与成员函数存储特性,体现了 C++ 的内存设计逻辑。 this
指针解决了成员函数区分对象的关键问题,而 C 与 C++ 实现 Stack 的对比,直观展现了封装带来的优势。
一、类的定义
1.1 基础:说明类的格式
classStack{//...};class为定义类的关键字,Stack为类的名字,{}中为类的主体,最后面加上分号" "。类主体中的内容成为类的成员:类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。- 为了区分成员变量,一般会养成在成员变量加一个特殊的标识的习惯,比如:在变量的前面或者后面加上
_或者m—>_capacity等。但不是C++强制的,只是为了方便操作,不同公司有着自己的书写规范。 - C++中
struct也可以定义类,因为C++兼容C语言的struct用法,而且C++将其升级成了类。一个明显的变化是C++在struct可以定义函数,注意还是推荐class定义类。 - 定义在类中的函数默认为
inline。(当然,最终还是取决于编译器)
classStack{//成员函数voidInit(){//...}voidPush(){//...}//成员变量int a;int top;int capacity;};//与结构体类似,对一种事物的描述--属性intmain(){ Stack s1; Stack s2;return0;}在 C++,类与结构体高度相似。其中明显的一个差异是:结构体名称不代表类型,只有当加上struct关键字后才是(或者typedef简化操作);而类可以直接使用名称,不需要加class关键字。
(后面会介绍类和结构体其它的不同之处。)
解释第2条:
对于命名规范,一般有一下几种:驼峰法StackInit自定义类型、函数–>开头单词的首字母大写开头+后续每个单词的首字母都大写;initCapacity变量–>开头单词的首字母小写+后面的每个单词的首字母都大写。GoogelC++风格stack_init函数、init_capacity变量 --> 单词之间都用_隔开。
1.2 关键:访问限定符(class、struct)
- C++一种实现封装的方式,用类将对象的属性与方法结合在一起,使对象更完善,通过访问权限选择性的将其接口提供给外部用户使用。
- 访问权限:
public修饰的成员在类外面可以直接访问;protected、private修饰的成员只能在类内访问,对于二者具体的区别,等到继承部分就会体现,现在默认作用相同,但一般使用private。 - 访问权限的作用域从该访问限定符出现位置开始到下一个访问限定符出现为止,如果后面没有访问限定符,作用域就到
}即类结束。 class定义成员在没有被访问限定符修饰时默认为private,但struct定义成员默认为public。- 类的成员变量通常会被限制为
private / protected访问权限,而需要提供给外部使用的成员函数会设置为public访问权限。
class中
//C++将数据和方法封装在一起,放在类中;//封装的本质体现了更严格的规范管理;classStack{public://公有//成员函数voidInit(int capacity =4){ _a =nullptr;//这里要malloc _top =0; _capacity =0;}voidPush(int x){}private://私有:不希望别人修改我定义的的成员变量//成员变量int*_a;int _top;int _capacity;};intmain(){ Stack s1; Stack s2; s1.Init(); s2.Push(1);//s1.top++;//无法访问return0;}struct中
//对于结构体,C++兼容其用法typedefstructA{voidfunc(){}int a1;int a2;}A;//当然C++也将其升级为类structB{public://公有//成员函数voidInit(){ _a =nullptr; _top =0; _capacity =0;}private://私有//成员变量int* _a;int _top;int _capacity;};intmain(){//C语言写法structA a1;//加关键字才是类型 A a2;//或者重命名//类写法 B b1;//名称就是类型return0;}在这里体现了类和结构体第2个不同的地:
小贴士:
C++中类访问权限默认为私有。
C++中结构体访问权限默认为公有
对于这两者,一般还是用类。当然,如果一开始就希望类的权限是公开,比如定义链表节点继续用struct。
1.3 类域(作用域)
- 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时(函数定义),就需要
::域操作符说明改成员属于哪个类域; - 类域影响了编译查找的规则,当定义函数却不指明是哪个类域,编译器就将其视为普通的全局函数,导致编译时找不到成员变量的声明/定义,报错。指定了类域就知道函数是类的成员函数,在当前域找不到成员变量,就会去类域找。
–Stack.h文件
#pragmaonce#include<iostream>classStack{public://成员函数声明voidInit(int capacity =4);voidPush(int x);private://成员变量int* _a;int _top;int _capacity;};–Stack.cpp文件
#include"stack.h"//类域成员函数定义//指明类域voidStack::Init(int capacity){ _a =nullptr; _top =0; _capacity = capacity;}voidStack::Push(int x){//...}–进行函数的声明定义分离:定义类的成员函数需要指明类域。
(搜索变量顺序:现在局部局,后全局域以及类域)
–test.cpp文件
#include"stack.h"intmain(){ Stack s1; s1.Init();return0;}二、实例化
2.1 解密:实例化的重要“身份”
- 用类这种类型在物理内存中创建对象的过程,称为类实例化出对象;
- 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量。这些成员变量只是声明,没有分配空间,只有用类实例化出对象时,才会分配空间;
- 一个类可以实例化出多个对象,实例化出的对象占用实际物理空间,存储类成员变量。比如:类实例化出对象就像用建筑设计图造房子,类就像设计图,规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子·,房子才能住人。同样类就像设计图,不能存储数据,实例化出的对象分配物理内存存储数据。
//类的实例化classStack{public:voidInit(int capacity =4){ _a =nullptr; _top =0; _capacity = _capacity;}voidPush(int x){//...}private://成员变量在这里只是声明,不开辟空间int*_a;int _top;int _capacity;};intmain(){//Stack类实例化出对象s1、s2 Stack s1; s1.Init(); Stack s2; s2.Push(2);//这时,成员变量才会占空间return0;}2.2 探究:对象的大小
类实例化出的对象,都有独立的数据空间,所以肯定包含成员变量,但是对于成员函数却不包含。
首先函数被编译后是段指令,被存储在代码段(单独区域)中,这样对象也只能包含函数的指针,但是没有这个必要。
实例化出的对象调用的都是同一个函数,如果对象包含成员函数,空间就浪费了。在实际中,函数指针是一个地址,调用函数被编译成指令[call 地址],编译器在编译链接只需要找到函数地址,不用再运行时找。(动态多态是在运行时找,这就需要存储函数地址,以后会学到)
知道了类包含着谁,就要开始计算对象的大小,这里,C++规定类实例化的对象符合内存对齐规则。
classA{public:// 成员函数voidInit(int n =4){}private:// 成员变量,声明char a;int y;};intmain(){// 定义,类实例化对象 A s1; s1.Init(); A s2; s2.Init(100); cout <<sizeof(s1)<< endl; cout <<sizeof(A)<< endl;return0;}可以看到,根据对齐规则,内存大小为8,没有包含成员函数。
【那么对于下面两个类的大小就很有意思了!】
classB{voidInit(){//...}};classC{//...};intmain(){ cout <<sizeof(B)<<'\n'; cout <<sizeof(C)<< endl;return0;}
疑问?既然类不包含成员函数,为什么大小还是1呢?
这里就纯粹是为了占位标识对象存在,要不然谁知道对象存在过呢?!
三. this指针
- 在上面
Stack类中由成员函数Init、Push,当类实例化两个对象时,二者调用同一个函数,是怎么区分的?这就是C++隐含的this指针。 - 编译器编译后,类的成员函数默认会在形参的第一的位置增加一个当前类型的指针–
this指针。比如:void Init(Stack* const this,...)。 - 类的成员函数访问成员变量,本质都是有
this指针访问,比如:this->_capacity = capacity;。 - C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
#include<iostream>usingnamespace std;classDate{public://void Init(Data* const this,int year,int month,int day)--但不能显式的写出来// const保护this不能修改voidInit(int year,int month,int day){//这里的this是可以写的,或者混着this->_year = year;this->_month = month; _day = day;}voidPrint(){ cout << _year <<"/"<< _month <<"/"<< _day <<'\n';}private://成员变量声明,没有开空间int _year;int _month;int _day;};intmain(){ Date d1; Date d2;//this指针,在函数实参和形参的位置不可以显式出现//但在函数体内可以出现//d1.Init(&d1,2025,7,31);不能这样写出来 d1.Init(2025,7,31);//d2.Init(&d2,2025,9,1);不能写出来 d2.Init(2025,7,9); d1.Print(); d2.Print();return0;}(对于使用this指针的场景后面会见到)
3.1 综合小测试
- 题目1: 下面的程序编译运行后的结果是(C),修改后的结果是(C)
A、编译报错;B、运行崩溃;C、正常运行
#include<iostream>usingnamespace std;classA{public:voidPrint()//实际上为void Print(A* this){ cout <<this<< endl;//输出this指针的值 cout <<"A::Print()"<< endl;}private:int _a;};intmain(){ A* p =nullptr;//p是空指针 p->Print();//等价于A::Print(p),把p作为this参数传入//修改后(*p).Print();//// 同样等价于:A::Print(p)return0;}在这里可能很多人会选B!首先看到是对空指针指针p进行解引用(不会报错),但是编译器执行的就是注释中所示,而且对象不包含成员函数。
- 题目2:下面程序编译运行结果是(B)
A、编译报错;B、运行崩溃;C、正常运行
#include<iostream>usingnamespace std;classA{public:voidPrint(){ cout <<this<< endl; cout <<"A::Print()"<< endl; cout << _a << endl;//_a是成员变量,存在对象里的,this->_a 所以会出现问题。}private:int _a;};intmain(){ A* p =nullptr; p->Print();return0;}这里,对成员变量进行了访问,在前面已经说了编译器通过this指针访问成员函数的成员变量,所以发生空指针解引用!- 题目3:this指针存在内存哪个区域的 (A)
A. 栈 B.堆 C.静态区 D.常量区 E.对象里面
this是存在栈里面的,像形参一样,类似局部变量。但是this可能会被高频地访问,就会放在寄存器中。
四、C++和C语言实现Stack的对比
面向对象的三大特性:封装、继承、多态,通过对比C++与C语言的代码风格进一步了解封装的概念。
- C++中的数据和函数都放在类里面,通过访问限定符进行限制,无法通过对象直接修改数据,这就是封装的一种体现。
- C++中有一些相对方便的语法,比如
Init给的缺省参数会很方便,并且因为隐式的this指针,不再需要传递对象的地址,使用类型不用再typedef。 - 虽然在C++的入门阶段,感到实现
Stack相较于C语言变化很多,但实际不大,等学到适配器实现Stack就会有新的体验。
总结
🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 结语:
类、实例化与 this 指针,共同构成 C++ 面向对象编程的核心基础。这些机制实现了数据与方法的封装,让代码更具规范性与可读性。 通过与
C 语言的对比,C++ 面向对象的设计优势得以直观体现。掌握这些知识,也为后续学习继承、多态等高级特性,打下了坚实基础。