C++天使的灵动心跳代码:类和对象(上)
面向过程与面向对象
🚩面向过程
C语言所学的内容主要都是面向过程的,核心是过程(函数),将程序看作是一系列步骤的组合。它强调的是 “怎么做”,重点在于设计函数和函数之间的调用顺序来完成任务,整个过程是围绕操作步骤(函数)展开的
例如:在一个文件复制程序中,面向过程的思路是先打开源文件,然后读取源文件内容,接着打开目标文件,再将读取的内容写入目标文件,最后关闭文件
🚩面向对象
核心是对象,把现实世界中的事物抽象成对象,对象包含数据(属性)和操作数据的方法。它强调的是 “有什么”,重点在于定义对象的属性和行为,通过对象之间的相互作用来完成任务
例如:对于文件复制程序,面向对象的思路是创建文件对象,这些文件对象有自己的属性(如文件名、文件路径、文件内容等)和方法(如打开、读取、写入、关闭等),通过操作这些文件对象来实现文件复制
类
类的引入
类是一种数据类型,在C语言
中类指的是结构体,但他只能包含变量
;在C++
中的类能包括变量和函数
,清晰对一个对象进行属性行为说明,所以类和结构体十分相似
1 | //以C语言环境 |
上面结构体的定义,在 C++ 中喜欢用class
来代替,实际上在 C++ 中 class 和 struct 都可以用,只是 C++ 里更喜欢用 class 与 C 语言做区分
类的定义
其语法形式为:
1 | class className |
🔥值得注意的是:
• class
为定义类的关键字
,ClassName
为类的名字
,{}中为类的主体
,注意类定义结束时后面分号不能省略
• 类体中内容称为类的成员
:类中的变量
称为类的属性
或成员变量
; 类中的函数
称为类的方法
或者成员函数
类中的声明与定义
假如我们要描述一个学生:
🚩定义与声明全都放在类中
1 | class student |
一般我们平常没有做项目时就把成员函数的声明定义都放在一起
🚩定义与声明分开
1 | //test.h |
一般情况下,更期望采用定义与声明分开
方式,因为以后在写项目通常会分多个文件。由于是在类域外访问
成员函数,所以要加上作用域限定符
类的访问限定符及封装
类的封装:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用
访问限定符分为:
• public(公有)
• protected(保护)
• private(私有)
⌨️举个例子:
1 | class Date |
通常我们把成员变量放在私有(成员函数放在私有的情况后面会讲),成员函数放在公有,私有的是无法直接访问的,放在私有的变量一般通过成员函数访问(前提是成员函数在类域内,不在类域内要使用友元函数,后面会讲)
🔥值得注意的是:
- public 修饰的成员在
类外
可以直接被访问
- protected 和 private 修饰的成员在
类外不能直接被访问
- protected 和 private
目前是一样的
,都是私有,其区别到了继承才体现出来 - 访问权限
作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,
作用域就到 } 即类结束
为什么在私有成员变量会在变量名前加个 _ ?
1 | // 我们看看这个函数,是不是很僵硬? |
通常私有成员变量会在变量名前加个 _ ,这是为了和传参的时候的名字做区分,避免不好区分变量,导致私有变量错误访问
🔥值得注意的是:私有成员变量在这里是声明
,就算写成int _year = 0
也只叫作给缺省参数
,真正区分变量是否为定义还是声明在于变量有没有开空间
C++ 中 struct 和 class 的区别是什么?
1 | class MyClass |
如果不写访问限定符,在 MyClass 中,myVariable 外部不能直接访问
,而在 MyStruct 中,myVariable 外部可以直接访问
类的作用域
在C++命运石之门代码抉择:C++入门(上)中我们详细介绍了域的概念
类定义了一个新的作用域,叫类域
,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域
1 | class Person |
这里需要指定 PrintPersonInfo 是属于Person 这个类域
类的实例化
用类类型创建对象的过程
,称为类的实例化
1 | int main() |
还是上面的类,这种实例化方式是错误的
打个比方:
类实例化
出对象就像现实中使用建筑设计图建造出房子
,类
就像是设计图
,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
正确的实例化:
1 | int main() |
先创建对象,才能对类中的成员进行操作
类的存储与大小
类是如何存储的?
如果要把所有成员都包括在类的存储中,按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?
⌨️所以在存储方式上类只保存成员变量,成员函数存放在公共的代码段
,这就有效的减少了空间浪费,每个对象中成员变量是不同的,但是调用同一份函数,函数只在使用的使用的时候去公共部分调用
通过对下面的不同对象分别获取大小来分析:
1 | // 类中既有成员变量,又有成员函数 |
• sizeof(A1) : 4
• sizeof(A2) : 1
• sizeof(A3) : 1
🔥值得注意的是:类的大小计算也遵循内存对齐规则;为什么类中仅有成员函数
或空类
的内存为大小为 1 byte,内存计算不是只考虑变量吗?原因其实非常简单,如果对这两种情况的类取地址,没有空间的话就会程序报错,但是这两个类又是真实存在的,所以定义这两种类的大小为 1 byte ,是为了占位,表示对象存在,不存储有效数据
this指针
this 指针是一个
隐含的指针
,它指向当前对象的实例
。它在类的成员函数内部使用,用于区分成员函数的参数和对象的成员变量,特别是当它们名称相同时
举个日期类的例子:
1 | class Date |
当 d1 调用 Init 函数时,该函数是如何知道应该设置 d1 对象,而不是设置 d2 对象呢?
C++编译器给每个
非静态的成员函数
增加了一个隐藏的指针参数
,让该指针指向当前对象(函数运行时调用该函数的对象)
,在函数体中所有成员变量
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
如图所示揭露了编译器的隐藏操作,其实在调用函数时传递了对象的地址
this指针可以为空吗?
如果 this 指针没有访问对象的成员,那么可以置空;如果 this 指针需要访问对象的成员,就不能为空,不然会导致程序报错,非法访问内存地址
🔥值得注意的是:
- this 指针的类型:类类型* const,即成员函数中,
不能给 this 指针赋值
- 只能在
成员函数内部使用
- this 指针本质上是
成员函数的形参
,当对象调用成员函数时,将对象地址作为实参传递给 this 形参,所以对象中不存储 this 指针
- this 指针是
成员函数第一个隐含的指针形参
,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递 - this 指针
不能在实参和形参显式传递
,但是可以在函数内部使用