# 纲要 > 主干纲要、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