类和对象(1)
一.类的基本概念
1.类的定义
以上图为例①class为定义类的关键字,Date为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
②为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_ 或者 m开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求。
③ C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,一般情况下我们还是推荐用class定义类。
④定义在类内的成员函数默认为inline。
2.访问限定符
C++实现封装的一种方式是用类将对象的属性与方法结合在一起,让对象更完善。通过访问权限(如 public / protected / private ),有选择性地将接口提供给外部用户使用,既隐藏内部细节,又确保数据安全。
以下是三种访问限定符的区别:
注:class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
3.类域
C++类域指类定义的范围,类成员(属性、方法等)均位于其中,特点如下:
①作用域范围:成员仅在类内可见,类外需通过对象/指针/引用访问(受访问权限限制)。
②成员访问规则:类内可直接访问成员;类外需通过对象名/指针/引用 + 运算符( . / -> )访问,且受public/protect/private权限控制。
③作用域解析运算符( :: ):类外定义成员函数时,需用 :: 指明所属类域。
④嵌套类:嵌套类有独立类域,外层类与内层类成员无法直接互访(除非声明友元)。
⑤与全局域区别:全局域成员全局可见,类域成员仅在类内或通过对象访问,实现数据隐藏与封装。
二.类的实例化
1.实例化
类的实例化是通过类创建具体对象的过程,对象是类的实例,实例化时调用构造函数初始化对象。
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类如同设计图,规划了成员变量的类型、功能等,但本身没有实体也不能存储数据;而实例化出的对象则像按设计图修建的房子,拥有真实的物理空间,可存储具体数据。
以上代码:
1. 对象创建: main 函数中 Date d1; 和 Date d2; 分别实例化出 d1 和 d2 两个 Date 类对象,为对象分配内存空间存储私有成员变量( _year 、 _month 、 _day )。
2. 初始化:通过 d1.Init(2025, 5, 2); 和 d2.Init(2024, 2, 14); 调用公有成员函数 Init ,对实例化对象的成员变量进行初始化。
3. 成员访问:利用公有成员函数 Print 访问并输出对象的私有成员变量值,符合类的封装特性,外部不能直接访问私有成员。
2.对象的大小
分析一下类对象中包含哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量。那么成员函数是否包含其中呢?
首先,函数被编译后是一段指令,对象中无法存储这些指令,它们存储在一个单独的区域(代码段)。若对象非要存储成员函数相关内容,只能是成员函数的指针。
进一步分析,对象中是否有存储指针的必要呢?以 Date 类为例,实例化出 d1 和 d2 两个对象, d1 和 d2 都有各自独立的成员变量 _year 、 _month 、 _day 来存储各自的数据,但 d1 和 d2 的成员函数 Init 、 Print 的指针却是一样的。如果在对象中存储成员函数指针,就会造成浪费。要是用 Date 类实例化100个对象,成员函数指针就会重复存储100次,这显然是极大的浪费。
这张图展示了两种对象存储方式设计。设计一将类成员变量(如 _name 、 _gender 、 _age )和类成员函数(如 SetPersonInfo ,PrintPersonInfo )统一存储。设计二则把类成员变量单独存储,不同对象的成员变量各自独立;类成员函数存于公共代码区的类成员函数表中,被所有对象共享。对比两种设计,凸显不同存储策略对对象内存管理的差异。
内存对齐规则:
第一个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
• 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
• VS中默认的对齐数为8。
• 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
以上代码,定义了 A 、 B 、 C 三个类。 A 类包含私有成员变量 _c ( char 类型 )和 _i ( int 类型 )以及公有成员函数 print ; B 类仅有公有成员函数 print ; C 类为空类。在 main 函数中实例化了这三个类的对象,并使用 sizeof 运算符输出对象大小。调试控制台显示了相应结果,展示了不同类对象大小在内存中的体现。
值得注意的是:
上面的程序运行后,我们看到没有成员变量的 B 和 C 类对象的大小是 1。为什么没有成员变量还要给 1 个字节呢?因为如果一个字节都不给,就无法表示对象存在过。所以这里给 1 字节,纯粹是为了占位标识对象存在。
三.this指针
1.this指针的基本概念
定义
this 指针是C++ 中一个隐含的指针,它指向当前被调用成员函数所属的对象。
作用
- 区分成员变量和局部变量:当成员函数中局部变量与成员变量同名时,可通过 this 指针明确访问成员变量。
- 返回当前对象:在成员函数中可利用 this 指针返回当前对象(常返回 *this ),支持链式调用等操作。
特性
- 存在于非静态成员函数中,静态成员函数没有 this 指针,因为静态成员函数不属于某个具体对象。
- 由编译器自动传递,无需程序员手动传递。
在这段C++代码的 Date 类中, Init 成员函数里使用了 this 指针。 this 指针是一个隐含于非静态成员函数中的指针,它指向当前调用该成员函数的对象。
具体到这里:
①区分变量:函数参数 year 、 month 、 day 与类的私有成员变量 _year 、 _month 、 _day 名称不同,但使用 this-> 明确表示是在操作对象自身的成员变量。若参数名与成员变量名相同,比如 void Init(int _year, int _month, int _day) ,就更能体现 this-> 的必要性,用 this->_year = _year; 才能准确将参数值赋给对象的成员变量。
②隐含存在: this 指针由编译器自动传递,无需手动传入。当对象调用 Init 函数时,编译器会把该对象的地址传给 this 指针,在函数内部就能通过它访问和操作对象的成员。
2.this相关的两段代码
以上代码中的指针 p 虽指向 nullptr ,但 Print 函数是普通成员函数且未访问对象的成员变量 ,没有用到 this 指针指向的对象数据,仅执行输出语句,所以不会引发空指针解引用错误,能正常运行。
以上代码中的Print 函数未访问对象成员变量,仅执行输出语句,没用到 this 指针指向的对象数据,所以即便指针 p 为 nullptr ,也不会引发空指针解引用错误,能正常运行。
四.对C++封装的基本认识
C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的一种体现,这个是最重要的变化。这里的封装的本质是一种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去学习。
• C++中有一些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多,使用类型不再需要typedef用类名就很方便