# C++

## 1. 预处理阶段能做什么

* 预处理不属于 C++ 语言，过多的预处理语句会扰乱正常的代码，除非必要，应当少用慎用；
* “#include”可以包含任意文件，所以可以写一些小的代码片段，再引进程序里；
* 头文件应该加上“Include Guard”，防止重复包含；
* “#define”用于宏定义，非常灵活，但滥用文本替换可能会降低代码的可读性；
* “条件编译”其实就是预处理编程里的分支语句，可以改变源码的形态，针对系统生成最合适的代码。

## 2. 编译阶段能做什么

* “属性”相当于编译阶段的“标签”，用来标记变量、函数或者类，让编译器发出或者不发出警告，还能够手工指定代码的优化方式。
* 官方属性很少，常用的只有“deprecated”。我们也可以使用非官方的属性，需要加上名字空间限定。
* static\_assert 是“静态断言”，在编译阶段计算常数和类型，如果断言失败就会导致编译错误。它也是迈向模板元编程的第一步。
* 和运行阶段的“动态断言”一样，static\_assert 可以在编译阶段定义各种前置条件，充分利用 C++ 静态类型语言的优势，让编译器执行各种检查，避免把隐患带到运行阶段。

## 3. 面向对象编程

* “面向对象编程”是一种设计思想，要点是“抽象”和“封装”，“继承”“多态”是衍生出的特性，不完全符合现实世界。
* 在 C++ 里应当少用继承和虚函数，降低对象的成本，绕过那些难懂易错的陷阱。
* 使用特殊标识符“final”可以禁止类被继承，简化类的层次关系。
* 类有六大基本函数，对于重要的构造 / 析构函数，可以使用“= default”来显式要求编译器使用默认实现。
* “委托构造”和“成员变量初始化”特性可以让创建对象的工作更加轻松。
* 使用 using 或 typedef 可以为类型起别名，既能够简化代码，还能够适应将来的变化。

## 4. 自动类型推导

* “自动类型推导”是给编译器下的指令，让编译器去计算表达式的类型，然后返回给程序员。
* auto 用于初始化时的类型推导，总是“值类型”，也可以加上修饰符产生新类型。它的规则比较好理解，用法也简单，应该积极使用。
* decltype 使用类似函数调用的形式计算表达式的类型，能够用在任意场合，因为它就是 一个编译阶段的类型。
* decltype 能够推导出表达式的精确类型，但写起来比较麻烦，在初始化时可以采用 decltype(auto) 的简化形式。
* 因为 auto 和 decltype 不是“硬编码”的类型，所以用好它们可以让代码更清晰，减少后期维护的成本。

## 5. const & volatile & mutable

1. const

* 它是一个类型修饰符，可以给任何对象附加上“只读”属性，保证安全；
* 它可以修饰引用和指针，“const &”可以引用任何类型，是函数入口参数的最佳类型；
* 它还可以修饰成员函数，表示函数是“只读”的，const 对象只能调用 const 成员函数。

2. volatile

* 它表示变量可能会被“不被察觉”地修改，禁止编译器优化，影响性能，应当少用。

3. mutable

* 它用来修饰成员变量，允许 const 成员函数修改，mutable 变量的变化不影响对象的常量性，但要小心不要误用损坏对象。

尽可能多用 const，让代码更安全

## 6. 智能指针到底智能在哪里

* 智能指针是代理模式的具体应用，它使用 RAII 技术代理了裸指针，能够自动释放内存，无需程序员干预，所以被称为“智能指针”。
* 如果指针是“独占”使用，就应该选择 unique\_ptr，它为裸指针添加了很多限制，更加安全。
* 如果指针是“共享”使用，就应该选择 shared\_ptr，它的功能非常完善，用法几乎与原始指针一样。
* 应当使用工厂函数 make\_unique()、make\_shared() 来创建智能指针，强制初始化，而且还能使用 auto 来简化声明。
* shared\_ptr 有少量的管理成本，也会引发一些难以排查的错误，所以不要过度使用。

## 7. C++ 中的异常处理

* 异常是针对错误码的缺陷而设计的，它不能被忽略，而且可以“穿透”调用栈，逐层传播到其他地方去处理；
* 使用 try-catch 机制处理异常，能够分离正常流程与错误处理流程，让代码更清晰；
* throw 可以抛出任何类型作为异常，但最好使用标准库里定义的 exception 类；
* 完全用或不用异常处理错误都不可取，而是应该合理分析，适度使用，降低异常的成本；
* 关键字 noexcept 标记函数不抛出异常，可以让编译器做更好的优化。

## 8. lambda 表达式

* lambda 表达式是一个闭包，能够像函数一样被调用，像变量一样被传递；
* 可以使用 auto 自动推导类型存储 lambda 表达式，但 C++ 鼓励尽量就地匿名使用，缩小作用域；
* lambda 表达式使用“\[=]”的方式按值捕获，使用“\[&]”的方式按引用捕获，空的“\[]”则是无捕获（也就相当于普通函数）；
* 捕获引用时必须要注意外部变量的生命周期，防止变量失效；
* C++14 里可以使用泛型的 lambda 表达式，相当于简化的模板函数。

## 9. C++ 中的字符串

* C++ 支持多种字符类型，常用的 string 其实是模板类 basic\_string 的特化形式；
* 目前 C++ 对 Unicode 的支持还不太完善，建议尽量避开国际化和编码转化，不要“自讨苦吃”；
* 应当把 string 视为一个完整的字符串来操作，不要把它当成容器来使用；
* 字面量后缀“s”表示字符串类，可以用来自动推导出 string 类型；
* 原始字符串不会转义，是字符串的原始形态，适合在代码里写复杂的文本；

## 10. 详解 C++ 容器

* 标准容器可以分为三大类，即顺序容器、有序容器和无序容器；
* 所有容器中最优先选择的应该是 array 和 vector，它们的速度最快，开销最低；
* list 是链表结构，插入删除的效率高，但查找效率低；
* 有序容器是红黑树结构，对 key 自动排序，查找效率高，但有插入成本；
* 无序容器是散列表结构，由 hash 值计算存储位置，查找和插入的成本都很低；
* 有序容器和无序容器都属于关联容器，元素有 key 的概念，操作元素实际上是在操作 key，所以要定义对 key 的比较函数或者散列函数。

## 11. 多线程编程

* 多线程是并发最常用的实现方式，好处是任务并行、避免阻塞，坏处是开发难度高，有数据竞争、死锁等很多“坑”；
* call\_once() 实现了仅调用一次的功能，避免多线程初始化时的冲突；
* thread\_local 实现了线程局部存储，让每个线程都独立访问数据，互不干扰；
* atomic 实现了原子化变量，可以用作线程安全的计数器，也可以实现无锁数据结构；
* async() 启动一个异步任务，相当于开了一个线程，但内部通常会有优化，比直接使用线程更好。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gists.lanlance.cn/cs/cpp.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
