前面提到C++是面向对象的语言,但不是纯面向对象,因为要兼容C语言,
所以C++可以面向对象和面向过程混编,像Java是纯面向对象的语言,只有面向对象,
就算你想实现一个排序也要写一个类出来……
本章将正式开始学习C++中的面向对象。
1. 面向对象
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象编程(Object Oriented Programming,OOP),关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。举个栗子,比如设计简单的外卖系统:
面向过程:关注实现下单、接单、送餐这些过程。体现到代码层面 -- 方法/函数
面向对象:关注实现类对象及类对象间的关系,用户、商家、骑手以及他们之间的关系。
体现到代码层面 —— 类的设计及类之间的关系。
1.1 类的引入
在C语言中,结构体中只能定义变量,而在C++中,结构体内不仅可以定义变量,还可以定义函数。因为在 C++ 里,struct 也跟着升级成了类。
因为 C++ 兼容 C 里面结构体的用法,所以 C++ 就可以直接使用类名来定义了:
struct Student { char name[10]; int age; int id; }; int main() { struct Student s1; // 兼容C Student s2; // C++就可以直接使用类名,Student类名,也是类型。 strcpy(s1.name, "小明"); s1.id = 10001; s2.age = 20; strcpy(s2.name, "小红"); s2.id = 10002; s2.age = 19; return 0; }
既能用 struct Student s1 来定义,还能直接使用 Student s2,通过使用类名直接定义。
这体现了 C++ 兼容 C 的特点。
但是如果这是在 C语言 里, stuct Student 才是它的类型,
直接使用 Student 定义是不可以的。
它其实就是一个结构,可以理解成和之前学的结构体是 "一样的" ,只是定义的方式既兼容了 C 还兼容了 C++ ,下面我们还会认识到一些它的不同之处。
如果是在C语言里,结构体里只能定义变量,就是一个多个变量的集合。
如果我们想要将 s1 中的变量进行初始化,还得一个个写,很麻烦,
但是在C++里,不仅可以定义变量,还可以定义函数(方法)。
定义一个 "初始化" 函数:
struct Student { /* 成员变量 */ char name[10]; int age; int id; /* 成员方法 */ void Init(const char* name, int age, int id) { ... } };
在 C++ 中一般称这些变量为成员变量,称这些函数为成员方法。
这个时候似乎发现了一些新的问题,这个成员方法的参数名取的好像和成员变量里一样了,
比如访问 name 的时候到底是成员变量里的 name 还是成员方法里的 name 呢?
这就让人区分不开了,为了能够更好的区分哪个是成员变量,我们在定义成员变量名时可以给它们做一些标记:下面是几种常见的表示为成员变量的 "风格" :
① 前面加斜杠 :
char _name[10];
② 后面加斜杠:
char name_[10]
③ 前面加个 m (表示成员 member):
char mname[10]
这个并没有明确的规定,不同的公司也有不同的风格。本博客常用第一种风格
这样就可以区分开来了:
struct Student { /* 成员变量 */ char _name[10]; int _age; int _id; /* 成员函数 */ void Init(const char* name, int age, int id) { strcpy(_name, name); _age = age; _id = id; } };
为了方便测试,再来写一个简单的打印函数,调用它们进行一个打印:
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; struct Student { /* 成员变量 */ char _name[10]; int _age; int _id; /* 成员函数 */ void Init(const char* name, int age, int id) { strcpy(_name, name); _age = age; _id = id; } void Print() { cout << _name << " " << _age << " " << _id << endl; } }; int main() { struct Student s1; Student s2; /* 初始化 */ s1.Init("小明", 20, 10001); s2.Init("小红", 19, 10002); /* 打印 */ s1.Print(); s2.Print(); return 0; }
总结:C++ 对我们的 struct 进行升级了,升级为类了。它兼容以前的用法,又有了新的用法。
1.2 class 关键字
刚才引入部分讲了 struct ,知道了它在 C++ 里升级成了类。其实 C++ 也有自己的亲儿子,就是 class,class语法和struct一样,注意类定义结束时后面要加分号。
但我们把上面代码的struct改成class居然报错了,这又是为什么呢?因为 C++ 讲究 "封装" ……C++ 这里是它把数据和方法都放在了类里面。
这和C语言是不同的,C语言里数据是数据,方法是方法。
这里我们就来提一下 面向对象OOP的三大特性:封装、继承、多态。
我们先来重点看一下这个 封装 :
① 数据和方法都被放在了一起。
② 访问限定符
就是因为这个访问限定符,所以这里我们报错了,下面来学习一下访问限定符。
2. 类的访问限定符及封装
2.1 访问限定符
C++ 实现封装的方式:用类将对象的属性与方法结合在一起,让对象更加完善,
通过访问权限选择性地将其接口提供给外部的用户使用。
一共有三种访问限定符,分别是 public(公有)、protected(保护)、private(私有)。
顾名思义,公有就是随便访问,保护和私有就是不让你随便访问得到。
访问限定符说明
① public 修饰的成员,可以在类外面随便访问(直接访问)。
② protected 和 private 修饰的成员,不能在类外随便访问。
(此处 protected 和 private 是类似的,现在你可以认为他们是一样的,后面我们讲继承的时候才能体现出它们两的区别)
这就分出了两个阵营,一个阵营是可以随便访问的,一个阵营是不能随便访问的。
③ class 的默认访问权限为 private,struct 为 public !
这就是为什么我们刚才编译会报错,因为 class 默认访问权限是 private!
那好,既然知道问题所在了,该如何解决让它成功访问呢?
使用访问限定符,加一个 public :
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Student { /* 成员变量 */ char _name[10]; int _age; int _id; public: /* 成员函数 */ void Init(const char* name, int age, int id) { strcpy(_name, name); _age = age; _id = id; } void Print() { cout << _name << " " << _age << " " << _id << endl; } }; int main() { struct Student s1; Student s2; /* 初始化 */ s1.Init("小明", 20, 10001); s2.Init("小红", 19, 10002); /* 打印 */ s1.Print(); s2.Print(); return 0; }
成功运行,我们再来细说一下刚才加进去的访问限定符。
③ 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
④ 如果后面没有访问限定符,作用域就到 { (最外面花括号)类结束。
也就是说,我们刚才加进去的 public ,
从它开始到下一个访问限定符出现为止的这块范围,都是共有的了,
但是因为后面没有再次出现访问限定符,所以作用域就到类结束为止。
再加一个访问限定符 private 进去看看:
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Student { /* 成员变量 */ char _name[10]; int _age; int _id; public: /* 成员函数 */ void Init(const char* name, int age, int id) { strcpy(_name, name); _age = age; _id = id; } private: void Print() { cout << _name << " " << _age << " " << _id << endl; } }; int main() { struct Student s1; Student s2; /* 初始化 */ s1.Init("小明", 20, 10001); s2.Init("小红", 19, 10002); /* 打印 */ s1.Print(); s2.Print(); return 0; }
现在, public 能影响到的范围就到 private 出现前为止了,然后在main里的打印函数就会报错。
这就是访问限定符在这里起到的一个作用。
注意事项:
① 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
② 一般在定义类的时候,建议明确定义访问限定符,不要用 struct / class 的默认的访问权限
class Student { private: char _name[10]; int _age; int _id; public: void Init(const char* name, int age, int id) { strcpy(_name, name); _age = age; _id = id; } void Print() { cout << _name << " " << _age << " " << _id << endl; } };
虽然不指定会有默认限定,但是还是建议明确写出来,
因为这样能让他人一眼就看出它是共有的还是私有的。
2.2 封装的意义和本质
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来实现对象进行交互。
① 把数据都封装到类里面。
② 可以给你访问定义成公有,不想给你访问的定义成私有或者保护。
封装的意义
封装的意义是什么?
封装是一种更好的严格管理,不封装是一种自由管理。
那么是严格管理好,还是自由管理好呢?
举一个疫情防控的例子:
某国单日新增一百万,所以是自由的管理好呢?还是严格的管理好呢?
我们和某国其实都是在控制疫情的,但是我们是严格的管理,控制疫情。
封装的本质
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
类也是一样,我们使用类数据和方法都封装到了一起,不想让人随意来访的,
就是用 protected / private 把成员封装起来,开放一些共有的成员函数对成员合理的访问。
C语言没办法管理,易出错,全靠个人素质。所以,封装是一种更好、更严格的管理。
从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针(中):https://developer.aliyun.com/article/1513642?spm=a2c6h.13148508.setting.17.5e0d4f0eApSShM