
目录
[3.1 声明和定义分离](#3.1 声明和定义分离)
[3.2 声明和定义放在一块](#3.2 声明和定义放在一块)
[6.1 类大小计算](#6.1 类大小计算)
[6.2 结构体内存对齐规则](#6.2 结构体内存对齐规则)
[7.1 this指针的引入](#7.1 this指针的引入)
[7.2 this的注意事项](#7.2 this的注意事项)
这次算是真正踏入 C++ 的门槛了,之前都是大部分可以和 C 兼容,而类和对象是 C++ 的特性,需要认真学习,这一 part 也是比较难的。这次要学习的内容是把面向对象和面向过程有详细的认识,然后去了解什么是类,类的定义,如何去比喻一个类,类的访问限定符等等。什么是封装,什么是类的实例化,以及类的实例化怎么用等等,这些都是去学习讨论的,一点点来,C++ 就是如此,前期学习的会比较困难,稍微后面就会相对轻松一点。那么话不多说,立马开始吧。
**1.**面向过程和面向对象初步认识
明确一点,C 是面向过程的,它更在意的是过程 ,C++ 是面向对象,关注的是对象。举个例子,我们平常生活中肯定会有点外卖吧,从过程上来看是这样,app 上架商品,客户打开 app 在上架下单,然后系统派单,骑手接单,送餐,客户签收。这些过程有哪些呢,上架,下单,派单,接单,送餐,签收等等过程就是,而 C 语言就是面向过程的,注重步骤。

而 C++,是重点面向对象,即谁下单,谁送货,谁出货,谁搭建沟通平台。那就是商户,客户,骑手,企业(app)。将一个过程分成不同的对象,靠对象之间的交互来完成,人们不需要知道餐从哪里来,怎么炒的菜,系统派单逻辑,花了多久等等过程,这就是面向对象。类就是现实世界映射到虚拟计算机系统里。
2.类的引入
C++ 里类除了放着变量还可以放置函数,C 语言中的结构体中只能存放变量,而在 C++ 中不仅可以存放变量,还能存放函数体。比如 C 语言中的栈,结构体中只放变量;而在 C++ 中,不但能放变量,还能放函数。如下就是 C++ 里的类,会晓得原来 struct 里面也可以定义函数,而不是单单定义变量那么单一。
cpp
struct stack
{
void init(size_t capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("defeat");
return;
}
_size = 0;
_capacity = capacity;
}
int* _a;
size_t _capacity;
size_t _size;
void push(int& x)
{
_a[_size] = x;
_size++;
}
int top()
{
return _a[_size - 1];
}
void Destroy()
{
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
};
cpp
int main()
{
stack s1;
s1.init(10);
s1.push(2);
s1.push(3);
s1.push(8);
s1.push(9);
s1.top();
s1.Destroy();
}

当然要注意,现在你是可以运行的,编译可以通过,但是如果你把 struct 变成 class,就会报错。原因在后面会提,其实在 C++ 中,更喜欢用 class 来代替,啥是 class,马上就说。

3.类的定义
这里正式介绍一下什么是类。类有个关键字,还要有类名,{}里的是主体,可以包含函数,变量定义,类定义后面}有个分号不能省。类体中内容称为类的成员: 变量 称为成员变量 ; 函数 称为成员函数。
cpp
class wj
{
public:
void Print(const int&a,const int&b)
{
cout << a << b;
}
int x;
int y;
char c;
};
这就是一个很普通的类,首先类体中要用成员函数和成员变量组成,最后有一个分号。然后需要注意的是要类可以两种定义方式,一种是声明和定义分离 ,一种是直接在类里面定义。
3.1 声明和定义分离
即类体中成员函数的声明放在h文件中,类的成员函数放在cpp文件中,注意这样子成员函数前需要类名:

声明和定义分离
3.2 声明和定义放在一块

声明和定义合并
这是两种不同的实现方式,不过一般的我会采取第二种更为方便管理。
4.类的访问限定符
类的访问限定符有public,protected,private,看中文意思能够理解是公开的、受保护的以及私有的。这也涉及到封装的概念,C++ 就是把类将对象的属性和实现方法结合在一起,用封装的方式决定哪些部分对用户开放,这个封装的方式就叫访问限定符。
- public 是公共区域,它所修饰的成员可以在类外直接被访问。
- 而 protected 和 private 所修饰的成员是不能被类外直接访问的。这两个类似。
- 访问限定符的作用域从该限定符出现开始到另一个限定符出现为止,如果没有就是以 } 结束为标志代表类的结束,比如 public:开始.....到 private:出现,那么中间的就是 public 的区域。
- class 默认为私有(private),struct 默认是公开(public),因为要兼容 C,所以也解释了上面为何用 struct 会编译通过,而 class 就报错。
这里提问一个经典问题:C++中class和struct的区别
答:C++ 需要兼容 C 语言,所以 C++ 中 struct 可以当成结构体使用,还可以用来定义类,这个和 class 定义类是一样的,区别是 struct 定义的类默认访问权限是 public,class 定义的类默认访问权限是 private。
4.1封装
封装是指通过类将数据以及操作数据的方法有机结合,把对象的具体属性和实现方式关起门来,只让你调用接口来实现用户的目的。
封装的好处是减少不必要的麻烦,为了更好的服务于开发商和用户。比如玩一个游戏,所有的游戏实现逻辑你是看不到的,玩家只需要鼠标、键盘、耳机、一个显示屏就可以了,而里面的代码运行、CPU 处理玩家的鼠标键盘、服务器传输数据、传回画面进行反馈,这些都不是我们需要考虑的。再举个例子,对于计算机使用者而言,不用关心内部核心部件,用户只需要知道怎么开机、怎么通过键盘和鼠标与计算机进行交互即可**。不然使用者出于好奇心动了一些部件导致损坏,那就会造成很多麻烦,专业的事由专业的人来做。**
4.类的作用域
类域是新的作用域,在类里面,类的所有函数成员、成员变量都在类的作用域里 ,在类外定义成员,需要说明这是我这个类的成员,就是要类名 + :: 来表示主权,:: 是域作用操作符。
cpp
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
5.类的实例化
实例化是用类创建实体对象的过程,称之为类的实例化,很形象的比喻就是图纸到实物的过程,图纸就是类,而实物:枪械零件,手办,汽车的实体,这些具象化的东西是实例化,我们的一切操作都是在实物上完成。
cpp
class wj
{
public:
void Print(const int&a,const int&b)
{
cout << a << b;
}
int x;
int y;
char c;
};
int main()
{
wj Wj;
wj.Print(1,4);//错误,没有空间,编译失败,语法错误
Wj.Print(1,4);//√
}
主函数的第一个代码就是类的实例化,就是要给我一个对象,其次在这上面进行一系列操作。时刻记住:类是对对象进行描述的,是一个图纸,但是你说图纸能不能放东西,能不能去实际使用,那显然是不能。但是实例化之后是可以的,就是你把图纸里的东西造出来的,那么就可以使用了。可以进行装饰,拆卸,加装零件等等;
在类里面,类实例化出的对象可以访问,可以存放类成员变量,占用实际的物理空间。
做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设 计出需要什么东西,但是并没有实体的建筑存在。类可以有多个实例化

ps:注意一个点,我们已经知道不能直接用类去访问,因为那个是声明,无法存储数据。那么我们实例化出来的对象1,对象2,他们的地址一样吗,我们每个对象的成员函数地址一样吗,即我们栈1要压栈和栈2要压栈操作,是用的同一个函数代码进行压栈操作吗,是的!push是公共区域
cpp
stack s1;
stack s2;
s1.init(10);
s2.init(10);
cout <<"s1对象地址:"<< & s1 << endl;
cout <<"s2对象地址:"<<&s2<< endl;
void(stack:: * ppush)( const int&) = &stack::push;
cout << "push函数成员地址" << (void*&)ppush << endl;

6.类对象模型大小
6.1 类大小计算
类里面有类的成员变量,还有类的成员函数,那么如何计算类的大小呢,要不要把类的成员函数算进去呢,下面这个类大小是多少?其实就是在想到底是都算进去还是只算成员变量,把成员函数放在公共代码段不去计算。
cpp
class A1
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
char _a;
};
class A2
{
public:
void PrintA(){ }
}
class A3
{
}

结论是:一个类的大小是该类中的成员变量的总和。(只保存成员变量,成员函数)当然你得注意一个规则,这个在c语言中struct一样规则,当有多个不同类型的变量时,需要遵照内存对齐规则。注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。为啥,这就是规则,也可以理解为人没来,给book一个位置。
6.2 结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到对齐数的整数倍地址数
- 对齐数:编译器默认的对齐数与该成员的较小值(vs默认对齐数是8)
- 结构体总大小:最大对齐数(结构体内最大变量类型的大小和默认对齐数取最小值)的整数倍
既然复习了一下结构体的内存对齐规则,就来看看类A1为什么是8:
首先char类型的字节是1,这个是毫无疑问的,int类型的字节大小是4,这个也没有意外。先是char,再是int的话。根据结构体内存对齐规则,char就直接占一个字节,然后int要对齐到对齐数(8和4比,较小值是4,所以对齐数是4)的整数倍,4或者8,那就是从5开始,占据4个字节,前面为了对齐就要浪费3个字节。加起来就是8

那么有个核心问题:为什么要进行内存对齐?
答:首先内存对齐是指在内存中按照硬件访问数据的要求,把数据放在特定的边界上。
如果不进行内存对齐,读取数据会很麻烦,对齐也许只要读1次,读2次就会读取完的数据,没有内存对齐后会增加读取次数,浪费时间。这在计算机喜欢以空间换时间的性格是相违背的。而现在cpu的寄存器决定了内存内访问的基本粒度。例如,64位cpu寄存器通常为8字节,这表示cpu会以8字节为单位进行内存访问,这是基于对硬件效率的考虑,是为了降低访问延迟。提高cpu的访问效率,如果不对齐,就要进行额外的读取操作,浪费时间
7.this指针
7.1 this指针的引入
来看一个date类的
cpp
class Date
{
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, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
这里产生一个疑问,为什么我们调用的init和print是同一个函数,那编译器是怎么区分他们的数据,打印结果完全不同。函数调用都是同一个代码片段,你怎么知道一个要打印d1的数据,d2的数据?答案是:它会传递一个我们用户看不到也不需要看到的隐藏指针:this指针。是编译器自己改的,而我们看不到,让指针去指向调用函数时的对象。
这是我们看到的:

这是编译器做的:

但是this指针并不能在形参和实参中显示传递:

7.2 this的注意事项
- this指针存在栈里,因为它是隐式的作为形参和实参传递到函数。所以this指针在栈里,不在堆区也不在常量区
- 最多只能在成员内部使用,不能在别的地方显示。
- (vs中)对象地址存放在ecx寄存器中,而寄存器存储thsi指针的值。
- 针对第1点和3点,就是this指针是形参,类的实例化对象调用成员函数时,对象地址会作为参数传给this的形参,即对象本身不存this指针。this就是对象的地址。
d1和d2的对象地址不一样
再有一个问题就是this指针可以为空吗?this可以为空,但是不能去访问this的内容,会出错。
预告:后面会单开一个部分,专门学习一个比较难的地方,是类的默认成员函数,其中最主要构造函数和析构函数,然后拷贝构造和赋值重载,这里是块硬骨头,学会了这里,其实又是一个小进步。