# 纲要
> 主干纲要、Hint/线索/路标
C++ 类相关:
- **类的基本概念**
- 类的定义
- 类的成员
- 类的访问说明符
- 类中使用别名
- 类的声明(前向声明)
- 类对象的大小
- 类的编译时名称查找
- **类的数据成员**
- **类数据成员的初始化**:成员初始化列表、类内初始化项
- **类的特殊数据成员**
- **静态数据成员** static
- **可变数据成员** mutable
- **类的成员函数**
- 类成员函数的基本说明
- `this` 指针
- **类成员函数的定义方式**
- **类成员函数的说明符与修饰符**
- 类的特殊成员函数
- **构造函数**
- 拷贝构造函数、移动构造函数
- 析构函数
- 重载运算符
- 拷贝赋值运算符、移动赋值运算符 √
- 转换函数
- **类的友元**
%%
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
<br>
# 类的基本思想
类的基本思想是 "**数据抽象**" 与 "**封装**"(encapsulation)。
- **数据抽象**:定义**抽象数据类型**(Abstract Data Type,ADT)
- **封装**:实现了类的"**接口**"(interface)与 "**实现**"(implementation)的分离
- 类的接口:类的用户所能使用的 "类的功能",即**类对外暴露的功能**。
- 类的实现:类的数据成员、负责接口实现的函数体、定义类所需的各种私有函数。
类类型是 C++面向对象编程 (OOP) 的基础,支持关键的 OOP 特性:**封装、继承、多态**。
- **封装**:将 "数据"(成员变量)和 "方法"(成员函数)封装为一个**抽象数据类型**,隐藏具体实现细节和私有属性,而只对类的用户暴露公共接口。
- **继承**:一个类可以从其他类派生,**继承其父类的属性和方法**。这支持代码的重用和扩展。
- **多态**:C++中的类通过**虚函数**来支持多态,可以在运行时**根据对象的实际类型来决定调用哪个成员函数**。
<br><br>
# 类与类的实例对象
- **类类型**(classtype):C++ 中通过 `struct` 或 `class` 关键字来定义一个 "**用户自定义类型**",称之 **类类型**。
- **类的实例对象**(class object):根据类定义而创建的**实体**。每个对象都拥有类中定义的属性和方法。
> [!NOTE] "类" 是创建对象的模板,"对象" 是根据类定义而生成的实例(instance)。
>
> [!NOTE] **类名**可直接作为"**类型名 typename**"使用,也可以在类名前加上 `class` 或 `struct` 关键字。
>
> ```cpp
> MyClass item1; // 调用默认构造函数创建一个MyClass类型的对象
> class MyClass item2; // 等价声明
> struct MyClass item3; // 等价声明
> ```
>
<br><br>
# 类的声明与定义
**类的声明**:向编译器指明某个"类类型"的**名称和存在**。(即 "**前向声明**")
```c++
class MyClass; // 类的前向声明 (forward declaration)
```
**类的定义**:提供**类的完整信息**,包括其**成员变量、成员函数、访问说明符**等。类的定义必须是完整的,以便编译器知道如何构造和操作该类型的对象
```c++
class MyClass {
public:
MyClass(); // 构造函数
void myFunction(); // 成员函数
private:
int myVariable; // 成员变量
};
```
> [!NOTE] **一个类就是一个作用域**
>
> 因此,在类的外部**定义成员函数**或**初始化静态数据成员**时,**必须在变量名或函数名之前通过 `类名::` 指定类作用域**
<br><br><br>
## 类的定义
C++中,一个类类型即是一个 "**作用域**",其定义中可包括以下部分:
- 数据成员(data member):
- **static 数据成员**
- **non-static 数据成员**
- 成员函数(member function):
- **static 静态成员函数**
- **non-static 非静态成员函数**
- **virtual 虚函数**
- **特殊成员函数**:
- 构造函数
- 析构函数
- 运算符重载函数
- 转换函数
- **嵌套类**:类内部嵌套定义的 `enum`,`class`,`struct`,`union` 等。
- **类型成员**:使用 `using` 或 `typedef` 声明的**类型别名**
- **友元**
### 类的访问说明符
C++中通过**访问说明符(access specifiers)** 指定 **类外部对类成员的访问权限**:
- `public`:可被整个程序内的其它任何代码访问。
- 通常用于定义类的接口部分,即暴露给类的用户所使用的属性或方法。
- `protected`:**类的成员和派生类可以访问**,但是其他外部代码不能直接访问。
- 通常用于那些需要在派生类中访问,但不希望暴露给类的用户的成员。
- `private`:**只能被该类的成员函数(包括友元函数)访问**,不会继承给派生类。
- 通常用于封装类的实现细节。
示例:
```c++
class MyClass { // `class`的默认访问权限为private
public:
// operations ...
protected:
// protected stuff
private:
// private stuff
};
```
> [!NOTE] `private` 禁止 "**类外部**" 访问类的私有成员,但 "**类作用域内均可任意访问**"。
>
> 以 C++中单例模式的实现为例,
>
> 场景一:"**类内定义一个同类型的==静态成员对象==**" or "**==类成员函数内==构造一个同类型的局部静态对象**" 时,均属于 "**类作用域下**",故 `private` 的构造/析构函数均可被访问。
>
> ![[_attachment/02-开发笔记/01-cpp/类与对象/cpp-类的基本概念.assets/IMG-cpp-类的基本概念-5615455DC2128F7E32A6D0E123439419.png|668]]
>
>
> 场景二:**==类外==定义一个全局对象 or 局部对象 or 全局/局部静态对象**时,编译无法通过,因为**无法访问 `private` 的构造/析构函数**。
>
> ![[_attachment/02-开发笔记/01-cpp/类与对象/cpp-类的基本概念.assets/IMG-cpp-类的基本概念-D986A672D988233121B39C435FFF0F47.png|669]]
>
### 类中使用别名
> [!NOTE] 类中使用的别名受 "访问说明符" 的影响
```c++
class Screen {
public:
// 使用pos作为别名, 隐藏了Screen的细节(使用了string对象)
using pos = std::string::size_type;
// typedef std::string::size_type pos; // 等价声明
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
```
<br><br>
## 类的前向声明(forwading declaration)
"**类的声明(前向声明)**" 仅仅向编译器**声明存在一个"类类型"** [^1]。
类在其声明之后、定义之前是一个"**==不完全类型==**"(incomplete type):
**编译器不知道该类型的大小及内存布局**,不知道该分配多少内存、如何初始化对象,因此**不能构造该类的实例对象**。
> [!caution] 对不完全类型的使用存在限制
>
> - **可以定义指向该不完全类型的==指针或引用==** ;(指针和引用的大小在编译时已知,不依赖于类的完整定义)
> - **可以==声明(但不能定义)==以该不完全类型(或其指针/引用) 作为==参数或返回类型==的函数**;
>
> [!caution] 仅进行前向声明,而未引入类完整定义的情况下,任何**尝试"创建"或"使用"不完整类型的实例的行为都会导致编译错误**,因为此时**类的大小和布局信息**对编译器来说还是未知的。
#### 前向声明的作用
1. **==减少头文件的包含==**
- 如果一个类仅**以指针或引用的形式**作为另一个类的成员或函数参数,可以使用前向声明而不是包含整个头文件,减少编译依赖,有助于加快编译速度。
2. **==解决循环依赖问题==**
- 当两个或多个**类相互引用对方**时,在头文件使用**前向声明替代 `#include`** 而 **==只在实现文件(`.cpp` 文件)中包含必要的头文件==**,可避免循环依赖报错。
<br>
##### 示例:减少头文件包含
> [!note] **如果需要 "使用前向声明类的成员函数" 或需要 "创建该类的实例",则必须引入包含该类类型定义的头文件**。
示例说明: 假设有两个类 `ClassA `和 `ClassB` 分别在两个头文件中定义
```c++ title:ClassA.h
class ClassA {...};
```
```cpp title:ClassB.h
// 注意: 这里是没有通过`#inclue 'ClassA.h'`来引入包含ClassA定义的头文件的情况, 如果引入了就不需要再用前向声明了.
class ClassA; // 前向声明(forward declaration), 该类类型是不完整类型
class ClassB {
public:
ClassA *a; // 可以, 因为是指针
// ClassA myA; // 错误, 不能为"不完整类型"创建实例
// 可以声明以不完全类型作为参数或返回类型的函数, 但不能定义这些函数
// 如果要定义, 必须在函数定义所在文件中引入包含ClassA定义的头文件.
ClassA someFunc1();
ClassA &someFunc1();
ClassA *someFunc2();
void someFunc4(ClassA a, ClassA *ptr, ClassA &aa);
}
```
<br>
##### 示例:解决循环依赖
**(1) 在头文件使用 "前向声明" 替代 `#include`** :
```cpp title:ClassA.h
// ClassA.h
// 无需引用"ClassB.h", 而只对ClaasB做前向声明 避免循环依赖问题
class ClassB; // 前向声明
class ClassA {
ClassB *b; // 使用B的指针
public:
void Func(ClassB b);
};
```
```cpp title:ClassB.h
// ClassB.h
// 无需引用"ClassA.h", 而只对ClaasA做前向声明 避免循环依赖问题
class ClassA;
class ClassB {
ClassA *a;
public:
void Func(ClassA a);
};
```
**(2) 只在实现文件(`.cpp` 文件)中包含必要的头文件**:
```cpp title:ClassA.cpp
#include "ClassA.h"
#include "ClassB.h // 包含ClassB的定义
void ClassA::Func(ClassB b) {
return;
}
```
```cpp title:ClassB.cpp
#include "ClassA.h" // 包含ClassA的定义
#include "ClassB.h"
void ClassB::Func(ClassA a) {
return ;
}
```
**(3) 使用上述两个类对象**
```c++ title:main.cpp
#include "ClassA.h"
#include "ClassB.h"
int main() {
ClassA obj_a;
ClassB obj_b;
obj_b.Func(obj_a);
obj_a.Func(obj_b);
}
```
<br><br>
# 类实例对象的大小
参见 [[02-开发笔记/01-cpp/类与对象/cpp-对象模型|cpp-对象模型]]
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P279