16. 类

### 类基础概述 1. **自定义类型**:类是用户自定义的类型,由数据成员和成员函数组成。 2. **成员访问**:使用 `.` 访问对象成员,使用 `->` 访问指针成员。 3. **运算符重载**:可以为类定义运算符。 4. **名字空间**:类是一个包含其成员的名字空间。 5. **访问控制**:`public` 成员提供接口,`private` 成员提供实现细节。 6. **结构体与类**:`struct` 默认成员为 `public`,`class` 默认成员为 `private`。 ### 默认拷贝 - 默认情况下,类对象可以通过拷贝初始化。 - 拷贝是通过逐个成员进行的。 ### class 和 struct - `struct` 默认成员为 `public`。 - `class` 默认成员为 `private`。 - 推荐将数据成员放在类的最后,以强调公共接口。 ### 构造函数 - 构造函数用于初始化对象。 - 可以提供默认参数。 - `explicit` 关键字防止隐式转换。 ### 类内初始化器 - 允许在成员声明时直接初始化成员变量,减少重复代码。 ### 可变性 - **常量成员函数**:使用 `const` 修饰成员函数,表示该函数不会修改对象状态。


15. 源文件与程序

分离编译是一种将程序分解为多个独立部分的方法,有助于代码的模块化和重用。在C++中,每个源文件通常包含一个或多个逻辑组件,这些组件在文件系统中的组织方式构成了物理结构。编译器处理源文件时,首先进行预处理,生成编译单元,这是编译器实际处理的内容。 链接器负责将分离编译的多个部分绑定在一起,确保程序的声明在整个程序中保持一致。链接可以在程序运行前完成,也可以在运行中动态添加新代码。 在C++中,函数名、类名等必须在所有编译单元中保持一致,除非显式声明为局部名字。全局变量应尽量避免使用,因为它们可能导致维护问题和多线程数据竞争。如果必须使用全局变量,可以通过无名名字空间或`static`关键字限制其作用范围。 头文件用于包含接口信息,确保不同编译单元的声明一致性。头文件可以包含类型定义、模板声明、函数声明等,但不能包含普通函数定义和数据定义。通过合理使用头文件,可以有效管理代码的接口和实现分离。


14. 名字空间

C++ 通过名字空间、函数、类和源码组织实现模块化。名字空间用于避免名字冲突,形成具名作用域。成员可通过显式限定、`using` 声明或指示引用。全局作用域可用 `::` 显式引用。`using` 声明简化频繁使用的名字空间成员,应用于所有重载版本。`using` 指示允许不加限定使用整个名字空间,但需谨慎避免冲突。参数依赖查找(ADL)根据参数类型自动查找函数定义,适用于运算符重载和模板函数,减少显式限定并避免名字空间污染。


13. 异常处理

异常处理是C++中用于处理错误的关键机制,通过抛出和捕获异常来传递错误信息。异常处理改进了传统的错误处理方法,如终止程序、返回错误值、设置错误状态和调用错误处理函数,提供了更高效、精细且规范的错误处理方式。异常可以携带关于错误的描述信息,帮助程序员更好地理解和处理错误。 异常安全保障确保程序在抛出异常后仍处于有效状态,并释放所有已申请的资源。资源管理推荐使用RAII(资源获取即初始化)技术,通过构造函数和析构函数自动管理资源的生命周期。 C++不支持`finally`块,但可以通过RAII实现类似的效果,确保无论是否发生异常,资源都能被正确释放。


12. 函数

函数声明是C++中定义函数的方式,包括函数名、返回类型和参数列表。它允许在程序的其他部分调用该函数,即使函数的定义在声明之后。函数声明可以包含多种修饰符和限定符,如`inline`、`constexpr`、`noexcept`等,以及成员函数特有的修饰符如`virtual`、`override`、`final`等。函数定义则是提供函数体具体实现的特殊声明,必须与所有声明保持类型一致。C++会自动忽略参数类型的顶层`const`,以兼容C语言。参数名称不属于函数类型的一部分,不同的声明中参数名称可以不同。


11. 选择适当的操作

`{}` 列表在C++中用于初始化命名变量,并可作为表达式使用。它们有两种形式:限定类型 `T{......}` 和未限定类型 `{......}`。未限定类型的 `{}` 列表根据上下文确定其类型。 实现模型方面,`{}` 列表的语义与函数调用类似,通过构造函数或聚合初始化来创建对象。例如,`vector<int>{1, 2, 3}` 会调用 `vector<int>` 的构造函数。 `{}` 列表的优势在于提供了更一致和安全的初始化方式,避免了窄化和隐式转换的问题。例如,`int x{7.8};` 会导致编译错误,因为 `7.8` 不能窄化为 `int`。 此外,`{}` 列表可以用于初始化数组、结构体和类对象,支持嵌套和多维初始化。例如,`vector<vector<int>> v{{1, 2}, {3, 4}};` 初始化了一个二维向量。 总之,`{}` 列表提供了一种灵活且安全的初始化机制,广泛应用于C++编程中。


10. 表达式

本文介绍了一个简单的桌面计算器程序,该程序支持四种标准算术操作(加、减、乘、除)以及变量定义。程序由四个主要部分组成:分析器、输入函数、符号表和驱动。分析器负责语法分析,输入函数处理输入和词法分析,符号表存储永久信息,驱动处理初始化、输出和错误。 计算器的语法规则包括表达式、项和初等项的定义。表达式可以包含加法和减法,项可以包含乘法和除法,初等项可以是数字、名称、赋值表达式或括号内的表达式。程序通过一个枚举类 `Kind` 来定义不同类型的标记(如运算符、数字、名称等),并使用 `TokenStream` 类来管理输入流和获取标记。 程序的核心逻辑是通过递归下降解析器实现的,分别处理表达式、项和初等项。输入函数 `TokenStream` 负责从输入流中读取字符并生成相应的标记。符号表使用 `unordered_map` 存储变量及其对应的值。 整个程序的设计遵循了编译器的基本结构,通过分层处理输入、解析语法、执行计算和输出结果,实现了基本的计算功能。


09. 语句

本文详细介绍了C++中的语句类型及其使用规则。主要内容包括: 1. **语句概述**:C++语句的形式化定义,涵盖声明、表达式、选择语句、循环语句等。特别强调了块(block)和复合语句(compound statement)的概念,以及名字作用域的规则。 2. **声明作为语句**:解释了为什么声明可以作为语句使用,目的是减少未初始化变量造成的错误,并提高代码的局部性。 3. **选择语句**: - **if语句**:条件可以是表达式或声明,支持隐式转换为`bool`类型。逻辑运算符`&&`、`||`、`!`常用于条件中,具有短路求值特性。 - **switch语句**:在一组候选项(`case`标签)中进行选择,`case`标签必须是整型或枚举类型的常量表达式。强调每个分支应包含结束语句(如`break`),并讨论了`default`分支的使用场景。 4. **循环语句**:介绍了`while`、`do-while`、`for`循环的语法和使用规则,特别是`for`循环中的初始化语句和初始化声明的区别。 5. **异常处理**:简要提及了`try`语句块用于处理异常。 通过这些内容,文章全面解析了C++中各种语句的结构和使用方法,帮助读者更好地理解和编写C++程序。


08. 结构、联合与枚举

本文介绍了C++中的结构(`struct`)、联合(`union`)、枚举(`enum`)和限定作用域的枚举类型(`enum class`)。 1. **结构(`struct`)**: - 由任意类型的元素(成员)构成的序列。 - 成员在内存中按声明顺序分配空间,但可能存在“空洞”以满足对齐要求。 - 类型名字一旦出现即可使用,但对象声明需在类型定义完成后进行。 - 可以包含成员函数,尤其是构造函数。 2. **布局**: - 成员按声明顺序存储,但可能因对齐要求而产生未使用的空间。 - 可以通过调整成员顺序减少空间浪费。 3. **名字**: - 类型名字可提前使用,但需避免递归定义。 - 允许在同一作用域中分别声明同名的`struct`和非`struct`,但不推荐。 4. **结构与类**: - `struct`是一种`class`,成员默认是`public`的。 - 可以包含成员函数,尤其是构造函数。 5. **联合(`union`)**: - 同一时刻只保存一个元素的值。 6. **枚举(`enum`)**: - 包含一组命名常量(枚举值)的类型。 7. **限定作用域的枚举类型(`enum class`)**: - 枚举值位于枚举类型的作用域内,不存在向其他类型的隐式类型转换。 通过这些内容,读者可以了解如何在C++中定义和使用结构、联合和枚举类型,以及它们与类的关系。


07. 指针、数组与引用

本文主要讨论了C++中的指针和数组的相关概念和操作。首先,强调了`void*`指针不允许进行加减运算,因为无法确定其指向对象的大小。接着,解释了指针的数值运算依赖于所指向的对象类型,并说明了指针之间的减法只有在指向同一数组的元素时才有效。 在数组部分,文章指出C++不允许直接拷贝数组,并提醒不要尝试按值传递数组给函数,因为这实际上是传递指针,会影响原数组。此外,还介绍了多维数组的定义和使用,包括如何省略第一维的大小。 最后,文章简要介绍了原始字符串字面量的使用,展示了如何在字符串中包含双引号、反斜杠以及多行文本。