C++之类与对象
C++之类与对象
- 1. 面向过程和面向对象初识
- 2. 类的基础知识
- 2.1 类的引入
- 2.2 类的定义
- 2.2.1 类的定义方式
- 2.3 类的访问限定符及封装
- 2.3.1 访问限定符使用说明
- 2.3.2 常考面试题
- 2.3.3 封装
- 2.3.4 常考面试题
- 2.4 类的作用域
- 2.5 类的实例化
- 2.5.1 类的实例化具体说明
- 2.6 类对象模型
- 2.6.1 类对象的存储方式猜测
- 2.6.2 结构体内存对齐规则
- 2.6.3 类对象大小的计算
- 2.7 类成员函数的this指针
- 2.7.1 this指针的使用说明
- 3. 类的6个默认成员函数
上篇文章详细讲述了C++入门相关知识,相信小伙伴们现在对于C++这门编程语言兴趣浓厚,求知若渴了吧o(  ̄▽ ̄)ブ 本篇文章我们将深入学习C++中最重要,面试最常考的知识—类和对象。文章内容丰富,知识点较多,希望小伙伴们能够深度学习耐心学完,触类旁通之后未来终会在C++编程领域乘风破浪,大放光彩(ง •_•)ง
1. 面向过程和面向对象初识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
对比维度 | 面向过程 | 面向对象 |
---|---|---|
代码组织方式 | 按功能 / 步骤顺序组织,以函数为核心,数据与操作分离,数据通过参数在函数间传递 | 围绕类和对象组织,类封装数据(成员变量)和操作(成员函数),通过对象交互完成功能 |
数据处理方式 | 数据被动接受函数操作,修改数据需调用特定函数 | 数据与操作紧密结合,对象通过自身方法主动处理数据,增强安全性和封装性 |
可维护性与拓展性 | 耦合度高,需求变化时可能需修改多个函数,维护和扩展难度较大 | 借助继承、多态等特性,可通过新增类或重写方法扩展功能,维护性和扩展性更好 |
典型应用场景 | 适合简单、线性、步骤明确的程序(如简单算法、小型工具类程序) | 适合复杂、交互性强、需长期维护的大型系统(如企业应用、游戏开发、图形界面) |
2. 类的基础知识
类的基础知识主要由8个模块组成:类的引入,类的定义,类的访问限定符及封装,类的作用域,类的实例化,类对象模型,类成员函数的this指针和类的6个默认成员函数。下面我们将一 一介绍各个模块的详细内容。
2.1 类的引入
在软件开发中,随着程序规模增大,面向过程的编程方式暴露出代码组织混乱、数据安全性差等问题。为了更好地管理数据和操作,以及提高代码的可维护性与复用性 ,引入了类的概念,它是面向对象编程的核心。
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。结构体的定义,在C++中更喜欢用class来代替。
2.2 类的定义
类是一种自定义的数据类型,它将数据(成员变量)和处理这些数据的函数(成员函数)封装在一起。在 C++ 中,使用class关键字来定义类。
class className
{
// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
2.2.1 类的定义方式
- 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
- 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
注:类的定义方式一般情况下,更期望采用第二种方式。
成员变量命名规则:一般都是加个前缀或者后缀标识区分就行。
2.3 类的访问限定符及封装
C++ 中有public(公有)、private(私有)、protected(保护)三种访问限定符。public修饰的成员在类外可以直接访问;private修饰的成员只能在类内部被访问,protected修饰的成员在类内部以及派生类中可以访问。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
2.3.1 访问限定符使用说明
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class默认访问权限为private,struct为public(因为struct要兼容C)
注:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
2.3.2 常考面试题
C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。在继承时,struct
默认继承方式是public
,而class
默认继承方式是private
;在模板参数列表位置,声明模板类型参数时,class
和struct
含义相同可互换,声明模板非类型参数时,struct
可用于定义复杂的非类型参数结构,class
一般不用于此场景 。
2.3.3 封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装就是利用访问限定符,将数据和实现细节隐藏起来,只对外提供接口,保证数据的安全性和完整性。 封装本质上是一种管理,让用户更方便使用类。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
2.3.4 常考面试题
面向对象的三大特性是什么?
封装、继承、多态。
2.4 类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
在类外访问类的成员时,需要通过对象名(或者对象指针)和作用域解析运算符(.或->)来访问。
2.5 类的实例化
用类类型创建对象的过程,称为类的实例化。类只是一种抽象的描述,实例化就是根据类创建对象的过程,对象是类的具体实体。
2.5.1 类的实例化具体说明
- 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。
- 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,对象就像是房子。类只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
2.6 类对象模型
类对象模型由三部分构成:类对象的存储方式猜测,结构体内存对齐规则,类对象大小的计算。下面我们将分别详细介绍这三部分。
2.6.1 类对象的存储方式猜测
-
对象中包含类的各个成员
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。 -
代码只保存一份,在对象中保存存放代码的地址
-
只保存成员变量,成员函数存放在公共的代码段
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐。
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
2.6.2 结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8 - 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注:结构体内存对齐规则就是以空间换时间,容易浪费空间。
2.6.3 类对象大小的计算
类的对象大小主要取决于类中成员变量占用的空间大小,遵循内存对齐原则(为了提高内存访问效率,编译器会按照一定规则对成员变量进行内存对齐)。一般不计算成员函数占用的空间,因为成员函数是共享的。
2.7 类成员函数的this指针
在类的成员函数中,this指针指向调用该函数的对象,通过this指针可以访问对象的成员变量和其他成员函数。
C++中通过引入this指针,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
2.7.1 this指针的使用说明
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用(this指针不能在形参和实参显示传递,但可在函数内部显示使用)
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
注:this是形参,所以它跟普通参数一样存在于函数调用的栈针里。
3. 类的6个默认成员函数
任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。分别是构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址运算符重载、const 取地址运算符重载。如果程序员没有显式定义,编译器会自动生成默认实现 。
- 构造函数:与类名相同,没有返回值,在创建类对象时自动调用,用于对对象进行初始化,比如给对象的成员变量赋初值,为对象分配必要的资源等。如果没有显式定义,默认构造函数会对内置类型成员变量不做初始化(值是不确定的),对自定义类型成员变量调用其默认构造函数进行初始化。
- 析构函数:名字是在类名前加
~
,没有参数和返回值,在对象生命周期结束时自动调用,用于释放对象在生命周期内分配的资源,比如释放动态分配的内存,关闭打开的文件等,确保资源得到正确回收,避免内存泄漏等问题。 - 拷贝构造函数:函数形式为
类名(const 类名&)
,用于用一个已存在的对象来初始化新创建的对象,会将已存在对象的成员变量值逐个复制到新对象中 。如果没有显式定义,默认的拷贝构造函数进行浅拷贝,对于普通成员变量能正常复制,但对于指针类型成员变量,只是复制指针值,会导致多个对象指向同一块内存,可能引发后续的内存错误。 - 赋值运算符重载:函数形式为
类名& operator=(const 类名&)
,作用是将一个已存在对象的值赋给另一个已存在的对象。默认的赋值运算符重载也是进行浅拷贝,和默认拷贝构造函数类似,对于指针类型成员变量的赋值可能引发问题,在涉及指针成员时,通常需要自定义实现深拷贝。 - 取地址运算符重载:函数形式为
类名* operator&()
,用于获取对象的地址,默认实现返回对象本身的地址,方便获取对象在内存中的存储位置,供其他需要对象地址的操作使用。 - const取地址运算符重载:函数形式为
const 类名* operator&() const
,作用和取地址运算符重载类似,不过是针对const
对象,当对象被声明为const
时,通过该函数获取对象的地址,默认实现返回const
对象本身的地址。
写在最后:类与对象作为C++面向对象编程的核心基石,涵盖的知识远不止文中所讲。从类的定义、封装到默认成员函数,每一部分都为我们打开了面向对象编程世界的一扇窗。希望小伙伴们在后续学习中,能带着对这些基础概念的理解,更深入详细的探索默认成员函数,内部类等更精彩的内容,在C++的编程之路上不断进阶,用代码创造出更多有价值的成果。