目录
面向过程和面向对象的初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
面向过程:
想象你要做一顿晚餐,面向过程的方式就像是按照一系列明确的步骤来进行。
首先,你去买菜,然后洗菜,接着切菜,之后炒菜,最后装盘上桌。这一系列步骤是依次进行的,每个步骤都有明确的顺序和具体的操作。
就好比用 C 语言写程序,你需要明确地定义每个函数来完成这些步骤,按照特定的顺序调用这些函数来完成整个任务。
面向对象:
还是做晚餐这个例子,但是从面向对象的角度来看。
我们把晚餐中的每一个元素都看作一个对象。比如有“菜”这个对象,它有属性如种类(青菜、肉类等)、新鲜度,还有方法如清洗、切割、烹饪。
还有“锅”这个对象,它有属性如尺寸、材质,有方法如加热、翻炒。
“厨师”也是一个对象,有属性如技能水平、经验,有方法如判断食材是否处理得当、控制烹饪时间等。
通过这些对象之间的交互和协作来完成做晚餐这个任务。
这就类似于在 C++中创建类来表示这些对象,通过对象之间的消息传递和方法调用实现功能。
类的引入
C语言中,结构体只能定义变量,在C++中,结构体不仅可以定义变量,还可以定义函数,C++更喜欢用Class代替
类的定义
类的定义
class为定义类的关键字,ClassName为类的名字,{ }中为类的主体,注意类定义结束时后面分号
类中的元素称为类的成员: 类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
定义类的两种方式
1.声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2.声明放在.h文件中,类的定义放在.cpp文件中(通常采用这种方式)
类的访问限定符以封装
访问限定符号
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符的作用:
假设在一个项目中有用户的个人信息或者号码,并且不想让其他人直接修改它,那么在 C++ 中可以将这个号码定义为类的私有成员变量。
通过这种方式,外部的代码无法直接修改这个号码,只能通过您在类中提供的特定公有方法来间接地进行可能的修改操作,并且您可以在这些方法中添加一些条件和逻辑来控制修改的合法性。
这就是利用访问限定符来实现数据的封装和保护,确保数据的安全性和一致性。
还可以这样理解:类就相当于一个房子,房子里面的公有和私有成员都属于房子内部,而外面的人不能够访问私有的成员,但可以访问公有成员,就相当于房子不锁属于房子里面的人
访问限定符的说明
1.public修饰的成员在类外可以直接被访问
2.protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4.class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
问题 :
C++中 struct 和 class 的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
封装
封装的本质就是便于我们进行管理,我们定义一个类有公有和私有的成员变量和成员函数,
这个类就相当于秦兵马俑旅游的区域,把这些兵马俑围起来,我们不能直接进去,而是要通过合法的渠道进去,类中的私有成员也是一样的道理,我们不能直接进行访问,而是要通过公有的函数或者方法进行访问,这就是封装的一种手段。
将类比作秦兵马俑旅游的区域,把私有成员比作被围起来的兵马俑,必须通过合法的渠道(公有函数或方法)来进行访问。
封装的目的就是为了对数据和操作进行有效的组织和管理,提高代码的安全性、可维护性和可扩展性。
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用,作用域解析符指明成员属于哪个类域。因为不仅仅只有Person这个类,我们在一个项目中会定义很多的类,所以外面使用类域中的函数需要指定该函数属于哪个类
类的实例化
比如说,我们定义了一个“汽车”类,它有“颜色”、“品牌”、“速度”等属性,以及“加速”、“刹车”等行为。
当我们进行实例化时,就像是从生产线上按照这个“汽车”类的规格制造出了一辆具体的汽车,比如一辆红色的丰田,速度为 0,这就是“汽车”类的一个实例。
就相对于结构的定义,通过定义出来的结构体可以访问内部的成员,类也是同理。
class Car {
public:
Car() { // 构造函数
color = "red";
brand = "Toyota";
speed = 0;
}
void accelerate() {
// 加速的实现
}
void brake() {
// 刹车的实现
}
private:
string color;
string brand;
int speed;
};
int main() {
Car myCar; // 这里就是对 Car 类进行实例化,创建了一个名为 myCar 的对象
return 0;
}
这样,myCar
就是 Car
类的一个具体的实例,可以对其进行操作,调用其成员函数等。
类对象模型
如何计算类对象的大小
我们都知道成员变量的计算和C语言中的结构体的计算规则一样就,都是按照内存对齐规则计算的,但是这里需要计算函数的大小吗
我们直接代码证明一下
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Stack
{
public:
//初始化
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(int) * n);
_top = 0;
_capacity = n;
}
//插入数据
void Push()
{
//实现...
}
//删除数据
void Pop()
{
//实现..
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
//类实例化对象,相对于定义出了类的成员变量
Stack s1;
Stack s2;
Stack s3;
cout << sizeof(s1) << endl;
cout << sizeof(Stack) << endl;
//计算 s1 就相对于计算 Stack 的大小
//就像 int i = 10; sizeof(int) 和 sizeof(i)计算的都是同一个对象
return 0;
}
输出结果:
为什么计算的结果是 12 个字节,函数的大小为什么没有算进去呢???
原因:成员变量可以存不同的值,但是调用的函数都是一样的,所以函数就放在公共代码区,如果每个对象都放成员函数,就会浪费空间
如何计算出类实例化出对象的大小
计算成员变量之和,并考虑内存对齐规则
为什么没有成员变量的类的大小不是0,而是1
class A1
{
public:
void f1() {}
private:
int _a;
char ch;
};
class A2
{
public:
void f2() {}
};
class A3
{};
int main()
{
cout << sizeof(A1) << endl; // 8
cout << sizeof(A2) << endl; // 1
cout << sizeof(A3) << endl; // 1
return 0;
}
因为不是为了存储有效数据,而是为了占位,表示对象存在,方便通过地址区分类实例化出的对象
结构体内存对齐规则
结构体的内存对齐规则是C语言的基础时候我们所学习过的,必须掌握
偏移量和数组类似,从 0 开始
vs中对齐数默认为 8
1,结构体的第一个成员在结构体变量(起始位置)偏移量为0的地址处开始
2,从第二个成员开始要对齐到对齐数的整数倍数处。
3,结构体的总大小为所有结构体成员中对齐数最大的那个的整数倍
4,如果嵌套了结构体,嵌套结构体对齐到自己的最大对齐数的倍数处,结构体整体大小就是最大对齐数(含嵌套对齐数)的整数倍
对齐数 = 编译器对齐数与该成员大小的较小值
计算步骤:
第一步:计算默认对齐数的大小, char c1的大小是 1 VS中默认对齐数是8,所以取较小的即为1,int i 为 4 4和8比较 取4, char c2 还是1
第二步:在内存中从偏移 0开始 占用一个字节,橙色表示 c1,int占4个对齐数,从第二个成员开始要对齐到对齐数的整数倍数处,所以蓝色是浪费的空间,浪费3个字节,从第4个字节开始4-7为int所占的空间,c2字节为1,都是它的倍数,如图所示
第三步:计算总大小,三个变量中最多的对齐数数 4,三个变量所占的字节数是0-8,9个字节,9不是所有结构体成员中对齐数最大的那个的整数倍(即9不是4的倍数),所以需要浪费2个字节,增加到偏移量为11,即0-11,总的大小是 12个字节,是4的倍数,所以该结构体的大小就是 12
该结构体的计算和上面同理
嵌套结构体,如果嵌套了结构体,嵌套结构体对齐到自己的最大对齐数的倍数处,(嵌套结构体的大小也要算)
结构体整体大小就是最大对齐数(含嵌套对齐数)的整数倍
struct S4 s4 该大小是16个字节,16和8比较去较小的值,对齐数是8,但是空间还是占16个字节,对齐数作用于在空间中所对齐的位置(所处的位置)
以下程序会导致崩溃、还是正常运行吗
this 指针
this指针的引出
隐含的this指针,都是编译器加的,谁调用这个成员函数,this就指向谁
以下的代码中,d1 调用了成员函数,则 this 就指向 d1 这个对象,this就是这个对象的指针,我们可以通过这个 this 指针去访问该对象的成员变量
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2024, 7, 15);
d1.Print();
return 0;
}
当我们调用函数Print 的时候,编译器会将对象d1的地址传给 this 指针,通过this指针我们可以访问私有成员变量,还可以修改里面的值
我们在写代码的时候,可以写函数里面的 this->,通常情况下都不写,但是函数里面的参数我们不能加,以及调用的里面的&d1也不能加
this指针的特性
this指针存在哪里? 也就进程地址中的哪个区域?
void 函数名(Date* this) ,this指针是形参,存在栈上的(ps:VS下是在ecx这个寄存器,来传递)
this指针可以为 NULL 吗
以下代码中的 p->PrintA(); 和 p->Show(); 两个函数调用执行的结果是正常运行,还是崩溃,或者是编译出错呢 ?
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
p->Show();
return 0;
}
答: p->PrintA(); //崩溃
p->Show(); //正常运行
对象访问成员:对象.函数 指向结构体/类的指针访问成员: 指针->函数
所以就需要去指针所指向的空间里面去找,
但是这两个函数没有存在p 这个对象里面,而是存在公共代码段,
所以去公共代码段去找这两个函数,
所以没有发生解引用操作,只是把 p = nullptr传给了函数里面隐含的this指针
PrintA 对 this 指针进行解引用操作,引发崩溃,即为 this->_a;这是对空指针的解引用
Show没有对this指针进行然后解引用操作,所以正常运行