📋 个人简介
我们今天学习C++中比较重要的一部分:类和对象,因为有了类和对象的概念,我们才可以从C语言的面向过程编程到C++中的面向对象编程
面向过程和面向对象
对于一个待解决的问题,我们可以从不同的角度去解决它。
比如点外卖这件事,它包含用户点餐,商家出餐,骑士送餐,用户取餐等过程。我们可以从头到尾,为每一个过程编写一段程序来解决问题。
我们也可以换一种角度来看待点外卖这个问题。它由三个对象组成:商家, 骑手,用户。三个对象每个人都有自己的方法:商家要接单,出餐。骑手要取餐,送餐。用户要点餐,取餐。那么点外卖这件事情就可以变成这三个对象的交互。
C语言是一种面向过程的语言,它解决问题就像我们处理点外卖问题的第一种办法,自上而下一步一步的去解决。而C++就是第二种办法,将问题抽象成对象之间的交互。
类
类的概念
类是对现实生活中一类具有共同特征的事物的抽象。我们都知道狗有各种各样的种类,比如牧羊犬,柯基,哈士奇等等。我们可以将他们进行抽象成一个类叫做Dog类。它有名字,年龄,性别。
类的定义
那么在C++中该如何创建这个类呢???
这个时候我们就可以用到C++中新引入的关键字class
class Dog{
char _name[20];
int _age;
char _sex[10];
};
注意:_并无特殊含义,只是在类中我们通常会对类中的变量作出标记而已
现在我们成功定义了一个Dog类,但是一只狗拥有的可不止有名字,年龄,性别这么简单。它还有吃饭,睡觉等等方法。
为了表示这些方法,C++中允许我们在类中书写函数,比如向下面这样
class Dog{
void Init(){
cin >> _name >> _age >> _sex;
}
void eat(){
cout << _name << "正在吃饭" << endl;
}
void sleep(){
cout << _name << "正在睡觉" << endl;
}
char _name[20];
int _age;
char _sex[10];
};
这样我们的Dog类就更加完善了,但是还有一些问题,目前的方法代码量都比较少。当方法比较多的时候,我们可能需要分文件进行书写,那该怎么操作呢???
我们可以在头文件中声明,在源文件中用我们之前学习到的::(域作用限定符)来为让我们的方法与类进行绑定。
假如下面有两个文件一个是Dog.h, 一个是Dog.cpp
//Dog.h
#include <iostream>
using namespace std;
class{
void Init()
void eat();
void sleep();
char _name[20];
int _age;
char _sex[10];
}
//Dog.cpp
#include "Dog.h"
void Dog::Init(){
cin << _name << _age << _sex;
}
void Dog::eat(){
cout << _name << "正在吃饭" << endl;
}
void Dog::sleep(){
cout << _name << "正在睡觉" << endl;
}
类的访问限定符及封装
现在我们已经创建了一个小狗类,它既有属性:名字、年龄、性别,也有了方法:初始化、吃饭、睡觉。那么我们来试试这个Dog类可不可以被创建运行。
#include <iostream>
using namespace std;
class Dog{
void Init(){
cin >> _name >> _age >> _sex;
}
void eat(){
cout << _name << "正在吃饭" << endl;
}
void sleep(){
cout << _name << "正在睡觉" << endl;
}
char _name[20];
int _age;
char _sex[10];
};
int main(){
Dog a;
a.Init(); //类中元素的访问类似于我们C语言中的结构体,使用"."去访问
return 0;
}
运行结果:
报错出现在a.Inti() 这一行,既然程序可以运行到a.Inti() 就说明我们的Dog类的创建没有问题,问题出现在访问类的方法上。那么为什么会有问题呢???
要解决这个问题,我们首先需要了解一下C++中的访问限定符
访问限定符
在C++中有3种访问限定符,protected(保护),private(私有),public(公有)。
- public修饰的成员在类外可以直接访问。
- protected和private修饰的成员在类外不能被直接访问。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止。
- class默认权限为private,struct的默认权限为public。
正是因为class的默认权限为private,所以才导致了我们刚刚无法直接调用Dog类中的Inti方法。为了外界可以调用这个函数,我们应该将它暴露在外界。
class Dog{
public:
void Init(){
cin >> _name >> _age >> _sex;
}
void eat(){
cout << _name << "正在吃饭" << endl;
}
void sleep(){
cout << _name << "正在睡觉" << endl;
}
private:
char _name[20];
int _age;
char _sex[10];
};
这样我们就可以正常的访问Dog类中的Inti方法了。
封装
C++为什么要引入这三个访问限定符呢❓
这就不得不提到面向对象的三大特点之一:封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
实际上封装可以理解成一种管理方式:比如我们疫情的出入管理。如果我们什么都不管,任由小区内外的人随意出入,那么就有可能导致疫情越来越严重。封装就是将整个小区封起来,里面的成员只能从规定的出入口出入。这样就能有效的管理小区,控制住小区中的疫情。
在类中,我们将数据和方法都封装到一起,不想其他人直接进行操作的,我们用private/protected把成员进行封装,开放一些共有的成员函数对成员合理的访问。
类的实例化及其大小计算
类的实例化
类只是一个模板,它只规定了类中有哪些成员,就像我们在C语言中学习到的结构体一样,类本身不占据内存大小,只有当我们创建对象之后,对象占据内存的大小。
还是以我们的Dog类来举例子吧。
这个我们可以把他类比成函数的声明,它只是告诉编译器类中有哪些东西,并不会开辟内存给这个类。只有当我们在函数中定义了对象,才会开辟空间。
int main(){
Dog a; //此时为a这个对象开辟空间
return 0;
}
类的大小
那么对于一个类,它里面既有成员函数又有成员变量,类的大小该怎么计算呢???
我们假设一下,应该只有两种情况:
-
对象中包含类的所有成员
-
对象中只有成员变量,无成员函数,成员函数放在其他区域。
接下来我们来实验一下,哪一种才是正确的结果呢?
int main(){
Dog a;
cout << sizeof(a) << endl;
return 0;
}
(不知道36是怎么计算出来的同学,可以去看看这个: C语言自定义类型梳理)
结论:一个类的大小,实际就是该类中“成员变量”之和,同时还要对成员变量进行内存对齐
this指针
现在我们知道了,对象里面只有成员变量的大小,而无成员函数,那么程序是怎么知道是哪一个对象在使用这个方法呢?
如果我们现在有两个对象,一个Dog a,一个Dog b,a和b都去调用同一个方法,是什么效果呢?
我们给Dog a取名为:小花,年龄:5,性别:女。
Dog b取名为:小黄,年龄:3,性别:男。
接下来我们看看输出结果是什么:
我们发现程序可以很好的识别出是哪一个对象在调用这个方法,那么这是怎么实现的呢?
这就不得不提到C++中的this指针了,其实我们在调用类中的方法时,有一个隐含的指针参数,这个指针指向的就是我们当前的对象。
我们写的程序是左边的样子,但实际的样子是右边。
this指针指向调用这个方法的对象,所以不同对象才能准确访问不同的成员变量。
结语
欢迎各位参考与指导!!!