C++ 备忘清单
===

提供基本语法和方法的 [C++](https://zh.cppreference.com/) 快速参考备忘单

入门
--------

### hello.cpp

```cpp
#include <iostream>
int main() {
    std::cout << "Hello Quick Reference\n";
    return 0;
}
```

编译运行

```shell
$ g++ hello.cpp -o hello
$ ./hello
Hello Quick Reference
```

### 变量

```cpp
int number = 5;       // 整数
float f = 0.95;       // 浮点数
double PI = 3.14159;  // 浮点数
char yes = 'Y';       // 特点
std::string s = "ME"; // 字符串(文本)
bool isRight = true;  // 布尔值
// 常量
const float RATE = 0.8;
```

----

```cpp
int age {25};      // 自 C++11
std::cout << age;  // 打印 25
```

### 原始数据类型

数据类型 | 大小 | 范围
:- | -- | --
`int`     | 4 bytes        | -2<sup>31</sup> 到 2<sup>31</sup>-1
`float`   | 4 bytes        | _N/A_
`double`  | 8 bytes        | _N/A_
`char`    | 1 byte         | -128 到 127
`bool`    | 1 byte         | `true` / `false`
`void`    | _N/A_          | _N/A_
`wchar_t` | 2 到 4 bytes   | 1 个宽字符
<!--rehype:className=show-header-->

### 用户输入

```cpp
int num;
std::cout << "Type a number: ";
std::cin >> num;
std::cout << "You entered " << num;
```

### 交换

```cpp
int a = 5, b = 10;
std::swap(a, b);
// 输出: a=10, b=5
std::cout << "a=" << a << ", b=" << b;

// 整数交换的奇技淫巧
(x ^= y), (y ^= x), (x ^= y);
// 注意! 以下操作会造成  undefined behavior
x ^= y ^= x ^= y;
```

### 注释

```cpp
// C++中的单行注释
/* 这是一个多行注释
    在 C++ 中 */
```

### If 语句

```cpp
if (a == 10) {
    // do something
}
```

查看: [条件](#c-条件)

### 循环

```cpp
for (int i = 0; i < 10; i++) {
    std::cout << i << "\n";
}
```

查看: [循环 Loops](#c-循环)

### 函数
<!--rehype:wrap-class=row-span-2-->

```cpp
#include <iostream>

void hello();   // 声明

int main() {    // 主函数
    hello();    // 执行函数
}

void hello() { // 定义
  std::cout << "Hello Quick Reference!\n";
}
```

查看: [函数 Functions](#c-函数)

### 引用

```cpp
int i = 1;
int& ri = i; // ri 是对 i 的引用
ri = 2; // i 现在改为 2
std::cout << "i=" << i;
i = 3;   // i 现在改为 3
std::cout << "ri=" << ri;
```

`ri` 和 `i` 指的是相同的内存位置

### 命名空间

```cpp
#include <iostream>
namespace ns1 {int val(){return 5;}}
int main()
{
    std::cout << ns1::val();
}
```

----

```cpp
#include <iostream>
namespace ns1 {int val(){return 5;}}
using namespace ns1;
using namespace std;
int main()
{
    cout << val();
}
```

名称空间允许名称下的全局标识符

C++ 数组
------

### 定义

```cpp
std::array<int, 3> marks; // 定义
marks[0] = 92;
marks[1] = 97;
marks[2] = 98;
// 定义和初始化
std::array<int, 3> = {92, 97, 98};
// 有空成员
std::array<int, 3> marks = {92, 97};
std::cout << marks[2]; // 输出: 0
```

### 操控

```cpp
┌─────┬─────┬─────┬─────┬─────┬─────┐
| 92  | 97  | 98  | 99  | 98  | 94  |
└─────┴─────┴─────┴─────┴─────┴─────┘
   0     1     2     3     4     5
```

----

```cpp
std::array<int, 6> marks = {
  92, 97, 98, 99, 98, 94
};
// 打印第一个元素
std::cout << marks[0];
// 将第 2 个元素更改为 99
marks[1] = 99;
// 从用户那里获取输入
std::cin >> marks[2];
```

### 展示

```cpp
char ref[5] = {'R', 'e', 'f'};
// 基于范围的for循环
for (const int &n : ref) {
    std::cout << std::string(1, n);
}
// 传统的for循环
for (int i = 0; i < sizeof(ref); ++i) {
    std::cout << ref[i];
}
```

### 多维

```cpp
     j0   j1   j2   j3   j4   j5
   ┌────┬────┬────┬────┬────┬────┐
i0 | 1  | 2  | 3  | 4  | 5  | 6  |
   ├────┼────┼────┼────┼────┼────┤
i1 | 6  | 5  | 4  | 3  | 2  | 1  |
   └────┴────┴────┴────┴────┴────┘
```

----

```cpp
int x[2][6] = {
    {1,2,3,4,5,6}, {6,5,4,3,2,1}
};
for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 6; ++j) {
        std::cout << x[i][j] << " ";
    }
}
// 输出: 1 2 3 4 5 6 6 5 4 3 2 1
```

C++ 条件
------------

### If Clause

```cpp
if (a == 10) {
    // do something
}
```

----

```cpp
int number = 16;
if (number % 2 == 0)
{
    std::cout << "even";
}
else
{
    std::cout << "odd";
}
// 输出: even
```

### Else if 语句

```cpp
int score = 99;
if (score == 100) {
    std::cout << "Superb";
}
else if (score >= 90) {
    std::cout << "Excellent";
}
else if (score >= 80) {
    std::cout << "Very Good";
}
else if (score >= 70) {
    std::cout << "Good";
}
else if (score >= 60)
    std::cout << "OK";
else
    std::cout << "What?";
```

### 运算符
<!--rehype:wrap-class=row-span-2-->

#### 关系运算符

:--|--
:--|--
`a == b` | a 等于 b
`a != b` | a 不等于 b
`a < b`  | a 小于 b
`a > b`  | a 大于 b
`a <= b` | a 小于或等于 b
`a >= b` | a 大于或等于 b

#### 赋值运算符

范例 | 相当于
:--|--
`a += b` | _Aka_ `a = a + b`
`a -= b` | _Aka_ `a = a - b`
`a *= b` | _Aka_ `a = a * b`
`a /= b` | _Aka_ `a = a / b`
`a %= b` | _Aka_ `a = a % b`

#### 逻辑运算符

| Example        | Meaning                |
|----------------|------------------------|
| `exp1 && exp2` | Both are true _(AND)_  |
| `exp1 || exp2` | Either is true _(OR)_  |
| `!exp`         | `exp` is false _(NOT)_ |

#### 位运算符

| Operator | Description             |
|----------|-------------------------|
| `a & b`  | Binary AND              |
| `a | b`  | Binary OR               |
| `a ^ b`  | Binary XOR              |
| `a ~ b`  | Binary One's Complement |
| `a << b` | Binary Shift Left       |
| `a >> b` | Binary Shift Right      |

### 三元运算符

```
           ┌── True ──┐
Result = Condition ? Exp1 : Exp2;
           └───── False ─────┘
```

----

```cpp
int x = 3, y = 5, max;
max = (x > y) ? x : y;
// 输出: 5
std::cout << max << std::endl;
```

----

```cpp
int x = 3, y = 5, max;
if (x > y) {
    max = x;
} else {
    max = y;
}
// 输出: 5
std::cout << max << std::endl;
```

### switch 语句

```cpp
int num = 2;
switch (num) {
    case 0:
        std::cout << "Zero";
        break;
    case 1:
        std::cout << "One";
        break;
    case 2:
        std::cout << "Two";
        break;
    case 3:
        std::cout << "Three";
        break;
    default:
        std::cout << "What?";
        break;
}
```

C++ 循环
------------

### While

```cpp
int i = 0;
while (i < 6) {
    std::cout << i++;
}
// 输出: 012345
```

### Do-while

```cpp
int i = 1;
do {
    std::cout << i++;
} while (i <= 5);
// 输出: 12345
```

### Continue 语句

```cpp
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;
    }
    std::cout << i;
} // 输出: 13579
```

### 无限循环

```cpp
while (true) { // true or 1
    std::cout << "无限循环";
}
```

----

```cpp
for (;;) {
    std::cout << "无限循环";
}
```

----

```cpp
for(int i = 1; i > 0; i++) {
    std::cout << "infinite loop";
}
```

### for_each (C++11 起)

```cpp
#include <iostream>
int main()
{
    auto print = [](int num) {
      std::cout << num << std::endl;
    };
    std::array<int, 4> arr = {1, 2, 3, 4};
    std::for_each(arr.begin(), arr.end(), print);
    return 0;
}
```
<!--rehype:className=wrap-text-->

### 基于范围 (C++11 起)

```cpp
for (int n : {1, 2, 3, 4, 5}) {
    std::cout << n << " ";
}
// 输出: 1 2 3 4 5
```

----

```cpp
std::string hello = "Quick Reference.ME";
for (char c: hello)
{
    std::cout << c << " ";
}
// 输出: Q u i c k R e f . M E
```

### 中断语句

```cpp
int password, times = 0;
while (password != 1234) {
    if (times++ >= 3) {
        std::cout << "Locked!\n";
        break;
    }
    std::cout << "Password: ";
    std::cin >> password; // input
}
```

### Several variations

```cpp
for (int i = 0, j = 2; i < 3; i++, j--){
    std::cout << "i=" << i << ",";
    std::cout << "j=" << j << ";";
}
// 输出: i=0,j=2;i=1,j=1;i=2,j=0;
```

### auto

```cpp
std:: string s = "hello world";
for(auto c: s){
    std:: cout << c << " ";
}
// 输出: h e l l o   w o r l d
```

C++ 函数
------------

### 参数和返回

```cpp
#include <iostream>
int add(int a, int b) {
    return a + b;
}
int main() {
    std::cout << add(10, 20);
}
```

`add` 是一个接受 2 个整数并返回整数的函数

### 重载

```cpp
void fun(string a, string b) {
    std::cout << a + " " + b;
}
void fun(string a) {
    std::cout << a;
}
void fun(int a) {
    std::cout << a;
}
```

### 内置函数

```cpp
#include <iostream>
#include <cmath> // 导入库

int main() {
    // sqrt() 来自 cmath
    std::cout << sqrt(9);
}
```

### Lambda 表达式
<!--rehype:wrap-class=col-span-2-->

Lambda 表达式可以在函数内定义,可以理解为在函数内定义的临时函数。格式:

```cpp
auto func = []() -> return_type { };
```

- `[]`为捕获列表,能够捕获其所在函数的局部变量
  - 一个空的捕获列表代表Lambda表达式不捕获任何的变量
  - 对于值捕获,直接在中括号中填写要捕获的变量即可:

      ```cpp
      int val = 5;
      auto func = [val]() -> return_type { };
      ```

- 对于引用捕获,需要在捕获的变量前添加`&`:

  ```cpp
  string str("hello world!");
  auto func = [&str]() -> return_type { };
  ```

- 如果变量太多,需要编译器根据我们编写的代码自动捕获,可以采用隐式捕获的方式。

  - 全部值捕获:

      ```cpp
      int val1, val2;
      auto func = [=]() -> int
          {
              return val1 + val2;
          };
      ```

  - 全部引用捕获:

      ```cpp
      string str1("hello"), str2("word!");
      auto func = [&]() -> string
          {
              return str1 + str2;
          };
      ```

  - 混合隐式捕获:

      如果希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用:

      ```cpp
      int val1 = 123, val2 = 456;
      string str1("123"), str2(456);

      auto func1 = [=, &str1]() -> int
          {
              return   val1 == std::stoi(str1)
                    ? val1 : val2;
          };

      auto func2 = [&, val1]() -> int
          {
              return   str1 == std::to_string(val1)
                    ? str1 : str2;
          };
      ```

- `()` 是参数列表,我们只需要按照普通函数的使用方法来使用即可
- `return_type` 是函数的返回类型,`-> return_type` 可以不写,编译器会自动推导
- `{}` 中的内容就是函数体,依照普通函数的使用方法使用即可
<!--rehype:className=style-timeline-->

此处给出一个 Lambda 表达式的实际使用例子(当然可以使用 `str::copy`):

```cpp
// vec中包含1, 2, 3, 4, 5
std::vector<int> vec({1, 2, 3, 4, 5});
std::for_each(vec.begin(), vec.end(),
              [](int& ele) -> void
          {
              std::cout << ele
                          << " ";
          });
```

## C++多线程

### 多线程介绍

g++编译选项:`-std=c++11`。包含头文件:

- `#include <thread>`:C++多线程库
- `#include <mutex>`:C++互斥量库
- `#include <future>`:C++异步库

### 线程的创建
<!--rehype:wrap-class=row-span-2-->

以普通函数作为线程入口函数:

```cpp
void entry_1() { }
void entry_2(int val) { }

std::thread my_thread_1(entry_1);
std::thread my_thread_2(entry_2, 5);
```

以类对象作为线程入口函数:

```cpp
class Entry
{
    void operator()() { }
    void entry_function() { }
};

Entry entry;
// 调用operator()()
std::thread my_thread_1(entry);
// 调用Entry::entry_function
std::thread my_thread_2(&Entry::entry_function, &entry);
```

以lambda表达式作为线程入口函数:

```cpp
std::thread my_thread([]() -> void
      {
         // ...
      });
```

### 线程的销毁

```cpp
thread my_thread;
// 阻塞
my_thread.join();
// 非阻塞
my_thread.detach();
```

### `this_thread`

```cpp
// 获取当前线程ID
std::this_thread::get_id();
// 使当前线程休眠一段指定时间
std::this_thread::sleep_for();
// 使当前线程休眠到指定时间
std::this_thread::sleep_until();
// 暂停当前线程的执行,让别的线程执行
std::this_thread::yield();
```

### 锁
<!--rehype:wrap-class=row-span-3-->

> `#include <mutex>`

#### 锁的基本操作

创建锁

```cpp
std::mutex m;
```

上锁

```cpp
m.lock();
```

解锁

```cpp
m.unlock();
```

尝试上锁:成功返回`true`,失败返回`false`

```cpp
m.try_lock();
```

解锁

```cpp
m.unlock();
```

#### 更简单的锁 —— `std::lock_guard<Mutex>`

构造时上锁,析构时解锁

```cpp
std::mutex m;
std::lock_guard<std::mutex> lock(m);
```

额外参数:`std::adopt_lock`:只需解锁,无需上锁

```cpp
// 手动上锁
m.lock();
std::lock_guard<mutex> lock(m,
    std::adopt_lock);
```

#### `unique_lock<Mutex>`

构造上锁,析构解锁

```cpp
std::mutex m;
std::unique_lock<mutex> lock(m);
```

##### `std::adopt_lock`

只需解锁,无需上锁

```cpp
// 手动上锁
m.lock();
std::unique_lock<mutex> lock(m,
    std::adopt_lock);
```

##### `std::try_to_lock`

尝试上锁,可以通过`std::unique_lock<Mutex>::owns_lock()`查看状态

```cpp
std::unique_lock<mutex> lock(m,
    std::try_to_lock);
if (lock.owns_lock())
{
    // 拿到了锁
}
else
{
    // 没有
}
```

##### `std::defer_lock`

绑定锁,但不上锁

```cpp
std::unique_lock<mutex> lock(m,
    std::defer_lock);
lock.lock();
lock.unlock();
```

##### `std::unique_lock<Mutex>::release`

返回所管理的`mutex`对象指针,**释放所有权。**一旦释放了所有权,那么如果原来互斥量处于互斥状态,程序员有责任手动解锁。

#### `std::call_once`

当多个线程通过这个函数调用一个可调用对象时,只会有一个线程成功调用。

```cpp
std::once_flag flag;

void foo() { }

std::call_once(flag, foo);
```

### `std::condition_variable`

#### 创建条件变量

```cpp
std::condition_variable cond;
```

#### 等待条件变量被通知

```cpp
std::unique_lock<std::mutex>
    lock;
extern bool predicate();

// 调用方式 1
cond.wait(lock);
// 调用方式 2
cond.wait(lock, predicate);
```

----

- `wait`不断地尝试重新获取并加锁该互斥量,如果获取不到,它就卡在这里并反复尝试重新获取,如果获取到了,执行流程就继续往下走
- `wait`在获取到互斥量并加锁了互斥量之后:
  - 如果`wait`被提供了可调用对象,那么就执行这个可调用对象:
    - 如果返回值为`false`,那么`wait`继续加锁,直到再次被 notified
    - 如果返回值为`true`,那么`wait`返回,继续执行流程
  - 如果`wait`没有第二个参数,那么直接返回,继续执行

#### `std::condition_variable::notify_one`

`notify_one` 唤醒一个调用 `wait` 的线程。注意在唤醒之前要解锁,否则调用 `wait` 的线程也会因为无法加锁而阻塞。

#### `std::condition_variable::notify_all`

唤醒所有调用 `wait` 的线程。

### 获取线程的运行结果
<!--rehype:wrap-class=row-span-2-->

> `#include <future>`

#### 创建异步任务

```cpp
double func(int val);

// 使用std::async创建异步任务
// 使用std::future获取结果
// future模板中存放返回值类型
std::future<double> result =
    std::async(func, 5);
```

#### 获取异步任务的返回值

等待异步任务结束,但是不获取返回值:

```cpp
result.wait();
```

获取异步任务的返回值:

```cpp
int val = result.get();
```

注:

- `get()`返回右值,因此只可调用一次
- 只要调用上述任意函数,线程就会一直阻塞到返回值可用(入口函数运行结束)

#### `std::async` 的额外参数

额外参数可以被放在 `std::async` 的第一个参数位置,用于设定 `std::async` 的行为:

- `std::launch::deferred`:入口函数的运行会被推迟到`std::future<T>::get()`或者`std::future<T>::wait()`被调用时。此时调用线程会直接运行线程入口函数,换言之,**不会创建子线程**
- `std::launch::async`:立即创建子线程,并运行线程入口函数
- `std::launch::deferred | std::launch::async`:默认值,由系统自行决定

#### 返回值的状态

让当前线程等待一段时间(等待到指定时间点),以期待返回值准备好:

```cpp
extern double foo(int val) {}

std::future<double> result =
    async(foo, 5);

//返回值类型
std::future_status status;
// 等待一段时间
status = result.wait_for(
  std::chrono::seconds(1)
  );
// 等待到某一时间点
status = result.wait_for(
  std::chrono::now() +
    std::chrono::seconds(1)
  );
```

在指定的时间过去后,可以获取等待的结果:

```cpp
// 返回值已经准备好
if (status ==
     std::future_status::ready)
{

}
// 超时:尚未准备好
else if (status ==
    std::future_status::timeout)
{ }
// 尚未启动: std::launch::deferred
else if (status ==
    std::future_status::deferred)
{ }
```

#### 多个返回值

```cpp
std::shared_future<T> result;
```

如果要多次获取结果,可以使用`std::shared_future`,其会返回结果的一个**拷贝**。

对于不可拷贝对象,可以在`std::shared_future`中存储对象的指针,而非指针本身。

C++ 预处理器
------------

### 预处理器
<!--rehype:wrap-class=row-span-3-->

- [if](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [elif](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [else](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [endif](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [ifdef](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [ifndef](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [define](https://zh.cppreference.com/w/cpp/preprocessor/replace)
- [undef](https://zh.cppreference.com/w/cpp/preprocessor/replace)
- [include](https://zh.cppreference.com/w/cpp/preprocessor/include)
- [line](https://zh.cppreference.com/w/cpp/preprocessor/line)
- [error](https://zh.cppreference.com/w/cpp/preprocessor/error)
- [pragma](https://zh.cppreference.com/w/cpp/preprocessor/impl)
- [defined](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [__has_include](https://zh.cppreference.com/w/cpp/feature_test)
- [__has_cpp_attribute](https://zh.cppreference.com/w/cpp/feature_test)
- [export](https://zh.cppreference.com/w/cpp/keyword/export)
- [import](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/import&amp;action=edit&amp;redlink=1)
- [module](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/module&amp;action=edit&amp;redlink=1)
<!--rehype:className=style-none cols-2-->

### Includes

```cpp
#include "iostream"
#include <iostream>
```

### Defines

```cpp
#define FOO
#define FOO "hello"
#undef FOO
```

### If
<!--rehype:wrap-class=row-span-2-->

```cpp
#ifdef DEBUG
  console.log('hi');
#elif defined VERBOSE
  ...
#else
  ...
#endif
```

### Error

```cpp
#if VERSION == 2.0
  #error Unsupported
  #warning Not really supported
#endif
```

### 宏

```cpp
#define DEG(x) ((x) * 57.29)
```

### 令牌连接

```cpp
#define DST(name) name##_s name##_t
DST(object);   #=> object_s object_t;
```

### 字符串化

```cpp
#define STR(name) #name
char * a = STR(object);   #=> char * a = "object";
```
<!--rehype:className=wrap-text-->

### 文件和行

```cpp
#define LOG(msg) console.log(__FILE__, __LINE__, msg)
#=> console.log("file.txt", 3, "hey")
```
<!--rehype:className=wrap-text-->

各种各样的
-------------

### 转义序列

转义序列 | 说明
:--|--
`\b`             | 退格键
`\f`             | 换页
`\n`             | 换行
`\r`             | 返回
`\t`             | 水平制表符
`\v`             | 垂直制表符
`\\`             | 反斜杠
`\'`             | 单引号
`\"`             | 双引号
`\?`             | 问号
`\0`             | 空字符

### 关键字
<!--rehype:wrap-class=row-span-2 col-span-2-->

- [alignas](https://zh.cppreference.com/w/cpp/keyword/alignas)
- [alignof](https://zh.cppreference.com/w/cpp/keyword/alignof)
- [and](https://zh.cppreference.com/w/cpp/keyword/and)
- [and_eq](https://zh.cppreference.com/w/cpp/keyword/and_eq)
- [asm](https://zh.cppreference.com/w/cpp/keyword/asm)
- [atomic_cancel](https://zh.cppreference.com/w/cpp/keyword/atomic_cancel)
- [atomic_commit](https://zh.cppreference.com/w/cpp/keyword/atomic_commit)
- [atomic_noexcept](https://zh.cppreference.com/w/cpp/keyword/atomic_noexcept)
- [auto](https://zh.cppreference.com/w/cpp/keyword/auto)
- [bitand](https://zh.cppreference.com/w/cpp/keyword/bitand)
- [bitor](https://zh.cppreference.com/w/cpp/keyword/bitor)
- [bool](https://zh.cppreference.com/w/cpp/keyword/bool)
- [break](https://zh.cppreference.com/w/cpp/keyword/break)
- [case](https://zh.cppreference.com/w/cpp/keyword/case)
- [catch](https://zh.cppreference.com/w/cpp/keyword/catch)
- [char](https://zh.cppreference.com/w/cpp/keyword/char)
- [char8_t](https://zh.cppreference.com/w/cpp/keyword/char8_t)
- [char16_t](https://zh.cppreference.com/w/cpp/keyword/char16_t)
- [char32_t](https://zh.cppreference.com/w/cpp/keyword/char32_t)
- [class](https://zh.cppreference.com/w/cpp/keyword/class)
- [compl](https://zh.cppreference.com/w/cpp/keyword/compl)
- [concept](https://zh.cppreference.com/w/cpp/keyword/concept)
- [const](https://zh.cppreference.com/w/cpp/keyword/const)
- [consteval](https://zh.cppreference.com/w/cpp/keyword/consteval)
- [constexpr](https://zh.cppreference.com/w/cpp/keyword/constexpr)
- [constinit](https://zh.cppreference.com/w/cpp/keyword/constinit)
- [const_cast](https://zh.cppreference.com/w/cpp/keyword/const_cast)
- [continue](https://zh.cppreference.com/w/cpp/keyword/continue)
- [co_await](https://zh.cppreference.com/w/cpp/keyword/co_await)
- [co_return](https://zh.cppreference.com/w/cpp/keyword/co_return)
- [co_yield](https://zh.cppreference.com/w/cpp/keyword/co_yield)
- [decltype](https://zh.cppreference.com/w/cpp/keyword/decltype)
- [default](https://zh.cppreference.com/w/cpp/keyword/default)
- [delete](https://zh.cppreference.com/w/cpp/keyword/delete)
- [do](https://zh.cppreference.com/w/cpp/keyword/do)
- [double](https://zh.cppreference.com/w/cpp/keyword/double)
- [dynamic_cast](https://zh.cppreference.com/w/cpp/keyword/dynamic_cast)
- [else](https://zh.cppreference.com/w/cpp/keyword/else)
- [enum](https://zh.cppreference.com/w/cpp/keyword/enum)
- [explicit](https://zh.cppreference.com/w/cpp/keyword/explicit)
- [export](https://zh.cppreference.com/w/cpp/keyword/export)
- [extern](https://zh.cppreference.com/w/cpp/keyword/extern)
- [false](https://zh.cppreference.com/w/cpp/keyword/false)
- [float](https://zh.cppreference.com/w/cpp/keyword/float)
- [for](https://zh.cppreference.com/w/cpp/keyword/for)
- [friend](https://zh.cppreference.com/w/cpp/keyword/friend)
- [goto](https://zh.cppreference.com/w/cpp/keyword/goto)
- [if](https://zh.cppreference.com/w/cpp/keyword/if)
- [inline](https://zh.cppreference.com/w/cpp/keyword/inline)
- [int](https://zh.cppreference.com/w/cpp/keyword/int)
- [long](https://zh.cppreference.com/w/cpp/keyword/long)
- [mutable](https://zh.cppreference.com/w/cpp/keyword/mutable)
- [namespace](https://zh.cppreference.com/w/cpp/keyword/namespace)
- [new](https://zh.cppreference.com/w/cpp/keyword/new)
- [noexcept](https://zh.cppreference.com/w/cpp/keyword/noexcept)
- [not](https://zh.cppreference.com/w/cpp/keyword/not)
- [not_eq](https://zh.cppreference.com/w/cpp/keyword/not_eq)
- [nullptr](https://zh.cppreference.com/w/cpp/keyword/nullptr)
- [operator](https://zh.cppreference.com/w/cpp/keyword/operator)
- [or](https://zh.cppreference.com/w/cpp/keyword/or)
- [or_eq](https://zh.cppreference.com/w/cpp/keyword/or_eq)
- [private](https://zh.cppreference.com/w/cpp/keyword/private)
- [protected](https://zh.cppreference.com/w/cpp/keyword/protected)
- [public](https://zh.cppreference.com/w/cpp/keyword/public)
- [reflexpr](https://zh.cppreference.com/w/cpp/keyword/reflexpr)
- [register](https://zh.cppreference.com/w/cpp/keyword/register)
- [reinterpret_cast](https://zh.cppreference.com/w/cpp/keyword/reinterpret_cast)
- [requires](https://zh.cppreference.com/w/cpp/keyword/requires)
- [return](https://zh.cppreference.com/w/cpp/language/return)
- [short](https://zh.cppreference.com/w/cpp/keyword/short)
- [signed](https://zh.cppreference.com/w/cpp/keyword/signed)
- [sizeof](https://zh.cppreference.com/w/cpp/keyword/sizeof)
- [static](https://zh.cppreference.com/w/cpp/keyword/static)
- [static_assert](https://zh.cppreference.com/w/cpp/keyword/static_assert)
- [static_cast](https://zh.cppreference.com/w/cpp/keyword/static_cast)
- [struct](https://zh.cppreference.com/w/cpp/keyword/struct)
- [switch](https://zh.cppreference.com/w/cpp/keyword/switch)
- [synchronized](https://zh.cppreference.com/w/cpp/keyword/synchronized)
- [template](https://zh.cppreference.com/w/cpp/keyword/template)
- [this](https://zh.cppreference.com/w/cpp/keyword/this)
- [thread_local](https://zh.cppreference.com/w/cpp/keyword/thread_local)
- [throw](https://zh.cppreference.com/w/cpp/keyword/throw)
- [true](https://zh.cppreference.com/w/cpp/keyword/true)
- [try](https://zh.cppreference.com/w/cpp/keyword/try)
- [typedef](https://zh.cppreference.com/w/cpp/keyword/typedef)
- [typeid](https://zh.cppreference.com/w/cpp/keyword/typeid)
- [typename](https://zh.cppreference.com/w/cpp/keyword/typename)
- [union](https://zh.cppreference.com/w/cpp/keyword/union)
- [unsigned](https://zh.cppreference.com/w/cpp/keyword/unsigned)
- [using](https://zh.cppreference.com/w/cpp/keyword/using)
- [virtual](https://zh.cppreference.com/w/cpp/keyword/virtual)
- [void](https://zh.cppreference.com/w/cpp/keyword/void)
- [volatile](https://zh.cppreference.com/w/cpp/keyword/volatile)
- [wchar_t](https://zh.cppreference.com/w/cpp/keyword/wchar_t)
- [while](https://zh.cppreference.com/w/cpp/keyword/while)
- [xor](https://zh.cppreference.com/w/cpp/keyword/xor)
- [xor_eq](https://zh.cppreference.com/w/cpp/keyword/xor_eq)
- [final](https://zh.cppreference.com/w/cpp/language/final)
- [override](https://zh.cppreference.com/w/cpp/language/override)
- [transaction_safe](https://zh.cppreference.com/w/cpp/language/transactional_memory)
- [transaction_safe_dynamic](https://zh.cppreference.com/w/cpp/language/transactional_memory)
<!--rehype:className=style-none cols-5-->

### 预处理器

- [if](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [elif](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [else](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [endif](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [ifdef](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [ifndef](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [define](https://zh.cppreference.com/w/cpp/preprocessor/replace)
- [undef](https://zh.cppreference.com/w/cpp/preprocessor/replace)
- [include](https://zh.cppreference.com/w/cpp/preprocessor/include)
- [line](https://zh.cppreference.com/w/cpp/preprocessor/line)
- [error](https://zh.cppreference.com/w/cpp/preprocessor/error)
- [pragma](https://zh.cppreference.com/w/cpp/preprocessor/impl)
- [defined](https://zh.cppreference.com/w/cpp/preprocessor/conditional)
- [__has_include](https://zh.cppreference.com/w/cpp/feature_test)
- [__has_cpp_attribute](https://zh.cppreference.com/w/cpp/feature_test)
- [export](https://zh.cppreference.com/w/cpp/keyword/export)
- [import](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/import&amp;action=edit&amp;redlink=1)
- [module](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/module&amp;action=edit&amp;redlink=1)
<!--rehype:className=style-none cols-2-->

另见
----

- [C++ Infographics & Cheat Sheets](https://hackingcpp.com/cpp/cheat_sheets.html) _(hackingcpp.com)_
- [C++ reference](https://zh.cppreference.com/w/) _(cppreference.com)_
- [C++ Language Tutorials](http://www.cplusplus.com/doc/tutorial/) _(cplusplus.com)_

# 数据结构与开发技巧
## map和set
```cpp
#include<iostream>
#include<map> // 注意map的key会自动排序, 所以在遇到排序问题时参考
#include<algorithm>
#include<vector>
#include <unordered_map>
using namespace std;
//     map中 所有元素都是pair
//     pair中 第一个元素为key(键值) 用于索引   第二个元素value(实值)
//     所有元素都会根据键值自动排序
// 本质:
//      map /mulmap底层都是二叉树
// 优点:
//     可根据key值快速找到value值

//      map不允许容器中出现相同的值 
//      mulmap中允许出现重复的值2
// map大小和交换:
//      .size()   //返回容器中元素的数目
//      .empty()   //判断容器是否为空
//      .swap(st)     //交换两个容器
// 插入和删除:
//     insert(elem)  //容器中插入元素  inseert(pair<int,int>               ( , ));
//     clear()    //清除所有元素
//     erase(pos)    //删除pos迭代器所指的元素 返回下一个迭   代器位置
//     erase(key)   删除键值为key的元素

void map_test(){
    // https://blog.csdn.net/tcx1992/article/details/80928790
    // https://blog.csdn.net/sevenjoin/article/details/81937695
    typedef map<int, string> myMap; // 这其实就是将map里面的数据格式给固定下来而已, map<int, string> = myMap
    myMap test;
    //插入
    test.insert(pair<int, string>(3, "a"));
    test.insert(pair<int, string>(4, "b"));
    test.insert(pair<int, string>(5, "c"));
    test.insert(pair<int, string>(8, "d"));
    test.insert(pair<int, string>(50, "e"));

    //遍历(二叉搜索树的中序遍历,按照key值递增顺序)
    cout << "遍历" << endl;

    // for(auto i : test){  // 将temp里面的每个值, 放到i中, 这个i是新建的
	// for(auto &i : test){  // 将temp里面的每个值, 软连接到i, 修改i就是在修改temp中的值
	for(const auto &i : test){ // 将temp里面的每个值, 软连接到i, 禁用修改, 防止在遍历过程中出现改值
        cout << i.second << endl;
    cout << endl;
    auto iter = test.rbegin();//最大的N个数
    for (int i = 0; i < 3; i++)
        cout << iter++->second << endl;
    //查找
    cout << "查找" << endl;
    // 使用find,返回的是被查找元素的位置,没有则返回map.end()。
    auto it = test.find(50); //查找key=50的数据是, find(key)返回的是pair格式, 也就是(50, e), 所以it->second=
    if (it != test.end())
        cout << it->second << endl;
    // 使用count,返回的是被查找元素的个数。如果有,返回1;否则,返回0
    cout << test.count(3) << endl;
    //删除
    cout << "删除" << endl;
    if (test.erase(3))
        cout << "delete success" << endl;
    for (auto &i : test)
        cout << i.second << endl;    
}

void map_test2(){
    map<int, string> myMap;  // 创建
    myMap.insert(pair<int, string>(3, "a")); // 插入
    myMap.insert(pair<int, string>(5, "b"));
    myMap.insert(pair<int, string>(50, "d"));
    for (auto &i : myMap) cout <<i.first <<"value="<< i.second<<"; "; cout<<endl;  // 遍历
        
    //返回map最后一个值
    map<int, string>::reverse_iterator iter = myMap.rbegin(); 
    if (iter != myMap.rend()) cout<<"最后一个值是"<<iter->first << "-" << iter->second <<endl;
    // cout<<"最后一个值是"<<myMap.end()->first << "-" << myMap.end()->second <<endl; //这样是错误的, 因为rend()和end()这两个函数只是标记找没找到 不是返回最后一个元素

    // 最大的2个数
    auto iter1 = myMap.rbegin();
    for (int i = 0; i < 2; i++)
        cout << iter1++->second << endl;

    // 查找find
    auto it = myMap.find(50); //查找key=50的数据是, find(key)返回的是pair格式, 也就是(50, e), 所以it->second=
    if (it != myMap.end()) 
        cout <<it->first << "-"<<it->second << endl;

    // 判断存在, 
    cout << "3有" << myMap.count(3) << endl;
}

int main()
{
    // map_test2();
    unordered_map<int, string> map1{{1, "hel"}, {2, "ahskg"}, {3, "world"}};
    cout<<map1.at(1)<<endl; // 最简单的查找
    // cout<<map1.at(5)<<endl; // 最简单的查找
    return 0;
}
```

## string
```cpp
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <vector>
#include <sstream> // 为了使用stringsteam
using namespace std;
// 题目描述: 给定字符串S,T, 求S中包含T所有字符的最短连续子字符串的长度, 时间复杂度不能超过O(n)
// 输入样例: 
// Input: S = "ADOBECODEBANC", T = "ABC"
// Output: "BANC"

string String_test(){
	string str="hello world";

	//直接打印字符串
	cout<<"str="<<str<<endl; 
	// printf("%s\n", str); //这里会报错, 需要将string转化为const char*类型
	const char *p = str.c_str();
	printf("str=%s\n", p);

	// 求字符串长度:
	cout<<"字符串长度等于: "<<str.length()<<endl;

    // 打印字符串最后一位
    cout<<"字符串最后一位"<<str[str.length()-1]<<endl;
    cout<<"字符串最后一位"<<str.back()<<endl;

    // string切片 substr(起始位置, 长度)
    cout<<"切片"<<str.substr(0, 5)<<endl;  

	//比较字符串
	if (0 == str.compare("hello world")){
		printf("字符串等于hello world\n");
	}

	// 字符串判断空
	if(!str.empty()){
		printf("字符串不为空\n");
	}

    // 字符串翻转
    // reverse(str.begin(), str.end()); // algorithm定义


	// char*、char[]转换为string 
    const char* pszName = "liitdar";
    char pszCamp[] = "alliance";
    string strName = pszName;
    string strCamp = pszCamp;
    cout << "strName is: " << strName << endl;	//strName is: liitdar
    cout << "strCamp is: " << strCamp << endl;	//strCamp is: alliance

    // find检测是否存在
	// size_t find (const string& str, size_t pos = 0) const;
	// size_t find (const char* s, size_t pos = 0) const;
	// size_t find (const char* s, size_t pos, size_t n) const;
	// size_t find (char c, size_t pos = 0) const;
    string str2 = "world";
    size_t son_location = str.find(str2);
    if (son_location != string::npos){ 
    	cout<<"找到子串str2, 在str位置是: "<<son_location<<endl; //找到子串str2, 在str位置是: 6
    }

    // 插入方法 insert
    str.insert(6, "zjq's "); //hello zjq's world
    str.insert(5, 4, 'a'); //在5的位置, 插入4个a
    cout<<str<<endl;

    // int2string stringstream
    int n1 = 1234;
    // n1.str(); // 这肯定不对
    stringstream str3; //注意这里导入头文件<sstream>
    str3 << n1;
    string str4 = str3.str();
    cout<<"将int类型转化为string类型: "<<str4<<endl;

    string str5;
    str3 >> str5;
    cout<<str5<<endl; //总之都要将int转化为string类型


    // 方法2 to_string
    int numb2 = 456;
    string str6;
    str6 = to_string(numb2);    // C++11 标准
    cout << "str6 is: " << str6 << endl; //str6 is: 456
    return str6;
}


int main(int argc, char const *argv[])
{
	string str = String_test();
    cout<<str<<endl;
	return 0;
}
```

## 指针
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <stdio.h>
using namespace std;

// 指针和引用
void test1(int*a, int b, int &c){
	cout<<"函数参数输入是: *a="<<*a<<" b="<<b<<" c="<<c<<endl;
	*a = 10;
	b = 23;
	c = 25;
	cout<<"函数参数修改后的结果是: *a="<<*a<<" b="<<b<<" c="<<c<<endl;
}

void pointor_test1(){
	int x=0, y=1, z=3;
	int *z1 = &z; //指向z地址的指针z1
	cout<<"原始数据值是: *z1="<<*z1<<" x="<<x<<" y="<<y<<" z="<<z<<endl;
	test1(z1,x,y);
	cout<<"经过函数洗礼后的结果是: *z1="<<*z1<<" x="<<x<<" y="<<y<<" z="<<z<<endl;
	/*
	总结: 引用&跟指针的原理都是一样, 就是传递的是指针,所以当函数参数使用&时,该参数的改变就会影响到调用的值 
	
	*z1和y的引用: 根据结果可以看出来, 其实指针*跟引用&有着异曲同工之妙呀, \
	因为指针*z1指向的地址, 传递给函数后, 函数对这个地址的内容进行了修改\
	但是由于*z1指向的地址的参数z的作用于是main函数, 也就是只有等到main函数结束后, \
	回收机制才会将z回收, 所以当*z1去函数test的作用域走了一圈以后, 他指向的地址依然有效;"

	x的调用: 虽然x的作用域是main, 但是传递到test的x, 其实只是值传递给了b, 而b的作用域只是函数test1, 当test1执行完毕, \
	b自然就废了, 所以即使b的值改变了, 但是并没有影响到x的值变化
	 */
}

int * addition(int a, int b){
    // new函数和malloc函数申请的内存在堆, 因此函数即使执行完毕, 堆不会回收
    int *sum = new int(a+b); //创建指针, 指针指向一块新开辟的内存(这块内存是new开辟出来的), 内存的里面的值是a+b
    return sum;

    // 这里之所以返回的地址虽然正确, 但是内容错误, 
    // 这是因为在函数中创建的c是在栈中创建, 作用域只有在函数内有效, 等函数执行完毕,c被回收, 
    // 因此即使地址还是那个地址, 但是c已经不再是那个c了
    // int c = a+b;
    // int *sum = &c; //sum 指向c这块地址
    // cout<<"sum="<<*sum<<" 地址是:"<<sum<<endl;
    // return sum; 

    // int *c = a+b;   //这个错误出在了a+b是一个值, 不是一个地址
    // return c;
}

int subtraction(int a, int b){
    return a-b;
}

int operation(int x, int y, int(*func)(int,int)){
    return (*func)(x,y);
}

int main(int argc, char const *argv[])
{
	pointor_test1(); // 指针和引用的基础使用

	int *p = addition(2,3); //函数指针, 返回指针, 同时证明new int(a+b)开辟的空间在堆上, 需要手动回收或者程序执行完毕才会回收
    cout<<"返回来的值和地址是:"<< *p<<"  地址:"<< p <<endl;

    int x = 3;
    int *p1 = &x; //此时p1指向一块内存, 内存里面存的是3, 
    const int *p2=&x; // 指针能修改, 但是值不能被修改
    int * const p3 = &x; // 指针不能被修改, 但是值可以
    const int * const p4 = &x; // 指针不能被修改, 值也不能

    // 指针函数minus, 指向函数的指针
    int (*minus)(int, int) = subtraction; // minus指向函数subtraction
    int *m = addition(1,2); // 返回的是addition结果保存的地址
    int n = operation(3, *m, minus); // x=3, y=1+2=3, 函数执行minus,即subtraction
    cout<<"结果是:"<<n<<endl;
	return 0;
}
```



## sstream类处理字符串
```cpp
#include <vector>
#include <iostream>
#include <unordered_map>
#include <unordered_map>
#include <sstream> // C++引入了ostringstream、istringstream、stringstream这三个类
/*
istringstream类用于执行C++风格的串流的输入操作。 string str="i am a boy";  该类可以搞出来一个队列分解这四个单词
ostringstream类用于执行C风格的串流的输出操作。
strstream类同时可以支持C风格的串流的输入输出操作。
*/

using namespace std;

int main(int argc, char const *argv[])
{
    // 这里打印出来的东西主要是看s的类型, 
    // 如果s是int, 就打印出12,3,4,5,67
    // 如果s是char, 就打印出所有字符
    // 如果s是string, 就直接将整行打印出来
    string a = "12+3-4+5*67";
    istringstream s1(a);
    int i; 
    // 12 3 -4 5 
    while(s1 >> i){ 
        cout<<i<<" ";
    }cout<<endl;

    istringstream s2(a);
    char c;
    // 1 2 + 3 - 4 + 5 * 6 7 
    while(s2 >> c){ 
        cout<<c<<" ";
    }cout<<endl;

    string b = "hello world, my name is zjq, hello worldB, my name is xixi";
    istringstream s3(b);
    string s;
    // hello//world,//my//name//is//zjq//
    while(s3 >> s){ 
        cout<<s<<"//";
    }cout<<endl;

    // 统计b里面的单词和数量
    istringstream s4(b);
    string str1;
    unordered_map<string , int> counts;
    // hello//world,//my//name//is//zjq//
    while(s4 >> str1){ 
        counts[str1]++;
    }cout<<endl;

    for(const auto& count: counts){
        cout<<count.first<<":"<<count.second<<endl;
    }
}
```



## 技巧

```cpp
// 子串表示连续的, 子序列表示不连续
// 没有特别需要排序的要求, 最好使用unordered_map和unordered_set, 底层是hash, 索引速度快, 没有排序的过程
// 能使用&进行参数传递, 尽可能使用&, 提高内存使用效率

#include <string> // getline

string str;
getline(cin, str); //可以将带空格的字符放入到str中
cout<<str<<endl;
```



# 面试知识点

## [C++编译流程](https://www.cnblogs.com/ericling/articles/11736681.html)

[C/C++程序编译过程详解](C/C++程序编译过程详解)

1. 预处理: **预处理用于将所有的#include头文件以及宏定义替换成其真正的内容**
2. 编译: **将经过预处理之后的程序转换成特定汇编代码**
3. 汇编: **汇编过程将上一步的汇编代码转换成机器码**
4. 链接: **链接过程将多个目标文件以及所需的库文件(.so等)链接成最终的可执行文件(executable file)**。

![image-20210906112152587](D:/0_2024/work/README.assets/image-20210906112152587.png)

## [说一下 static 关键字的作用](https://www.runoob.com/w3cnote/cpp-static-usage.html)

1. 静态局部变量只初始化一次, 延长局部变量生命周期

2. 全局, 只能本文件中使用, 不能在其他文件中访问, 既是extern也不行 等于是在每个源文件中都定义了该变量一次

3. 头文件中定义: 每个CPP文件中会拷贝一份对应的变量

4. 修饰函数: 该函数只能本文件访问

5. 不想被释放时, 比如修饰函数中存放栈空间的数组, 可以加static

6. 类

   ```c
   class A{
       int a=0;
       static int b=0;
       static int c;
   public:
       void fun1(){}
       static void fun2(){}
       // 这句话本来就是错误的, 以为fun3比对象先实现, 但是a还没init
       static void fun3(){  cout<< a <<endl; } // err 
       static void fun4(){ cout << b << endl; } // 通过, 因为fun4和b一起初始化
       void fun5(){ cout << b << endl; } // 通过, 因为b先初始化, 创建对象实例的时候才初始化fun5
   }
   
   A::fun1(); // 1. err 因为类实例化对象后才能使用成员函数
   A::fun2(); // 2. 通过 因为类实例化对象之前已经给静态成员函数分配了空间
   A::fun3(); // 3. err 因为完成成员函数, 但是此时类成员变量还没有初始化, 因此错误
   
   A a = new A;
   a.fun3(); // 4. err 跟3一个道理, fun3先初始化, 但是里面包含的成员变量a未初始化, 编译错误
   
   总结:
   1. 静态成员函数不能使用非静态成员(函数和变量)
   2. 非静态成员函数可以调用静态成员
   3. 静态成员变量必须在初始化先 比如 int A::c = 20;
   ```

   

***

## 说一下 C++和 C 的区别

1. 思想: 面向对象, 面向过程的结构化编程语言
2. 语法: 封装(隐藏实现细节,代码模块化) 继承(派生类可以继承父类数据和方法,扩展已存在的模块, 代码重用) 多态(一个接口, 多种实现, 派生类重写父类虚函数, 实现接口重用)三种特性, 更安全:强制类型转化
3. 动态管理内存方法不一样: C是malloc和free, C++除此之外还有new/delete
4. 支持范式, 模板类, 函数模板等


***

## 指针和引用

| 指针                                                 | 引用                                       |
| ---------------------------------------------------- | ------------------------------------------ |
| 有自己的一块空间                                     | 引用只是一个别名                           |
| sizeof 看一个指针的大小是 4                          | 被引用对象的大小                           |
| 可以被初始化为 NULL                                  | 引用必须被初始化且必须是一个已有对象的引用 |
| 作为参数传递时, 指针需要被解引用才可以对对象进行操作 | 直接对引用的修改都会改变引用所指向的对象   |
| 可以有 const 指针                                    | 无                                         |
| 指针在使用中可以指向其它对象                         | 只能是一个对象的引用, 不能被改变           |
| 多级指针(**p)                                      | 引用只有一级                               |
| 指针和引用使用++运算符的意义不一样                   |                                            |
| 返回动态内存分配的对象或者内存, 必须使用指针         |                                            |



## 智能指针? 为何使用智能指针

减少内存泄漏等问题

### 解决的问题

1. 空指针和野指针
2. 对象重复释放
3. 内存泄漏
4. 不匹配new和delete

### unique_ptr, shared_ptr, weak_ptr

unique_ptr 注意: 初始化相当于一个空指针, 再用make_unique初始化; 唯一指针, 不允许共享, 禁止拷贝复制; 相当于当我相对一个对象进行操作, 但是我不想别的指针操作这个对象, 就可以用unique_ptr; 尽量不要对其赋值操作, 让他自生自灭

shared_ptr 允许多个指针指向, 类似原始指针, 继承了`p->name` 和 ` (*p).name`注意: 降低程序运行效率, shared_ptr析构函数不能太复杂, 特别慢, 当他析构的时候, 整个线程会阻塞, 

weak_ptr 打破循环引用, 只做观察指针, 看一下对象对象存不存在

auto_ptr已经不用了

[智能指针是线程安全的么?](http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html) 显然智能指针控制写不是,因为智能指针操作不是原子性, 当赋值语句执行时, 其实智能指针拷贝对象同时还得对对象的计数进行+1操作, 这两步就会被其他线程钻空子了





C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11 支持,并且第一个已经被 11 弃用。

1. 作用是管理一个指针; 申请空间在函数结束时忘记释放, 造成内存泄漏, 而使用智能指针, 一旦智能指针超出类的作用域, 类会自动调用析构函数, 释放资源, 所以智能指针的作用原理在函数结束后, 自动释放内存空间;
2. auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.”));
   auto_ptr<string> p2; 
   p2 = p1; //auto_ptr 不会报错. 此时p2掠夺了p1所有权, 使用p1的时候, 内存崩溃
3. unique_ptr<string> p3 (new string ("auto"));
   unique_ptr<string> p4; 
   p4 = p3;// 报错, 非法, 避免内存崩溃
4. shared_ptr共享拥有, 多个智能指针可以指向同一个对象, 该对象和其相关资源会在最后一个引用被销毁后释放
5. weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象, 作为管理指针; 为了解决循环引用导致的内存泄漏, 构造函数不会改变引用计数, 不会对对象内存进行管理, 像是一个普通指针, 但会检测所管理的对象是否被释放, 从而避免内存泄漏;
   **

***

## #define和const区别

| const                        |
| ---------------------------- |
| 有类型有名字, 放到静态存储   |
| 编译时确定, 只有一个拷贝     |
| 可以用指针去指向该变量的地址 |
| 不能定义函数                 |

## 重载overload,覆盖(重写)override,隐藏(重定义)overwrite,这三者之间的区别

1. overload,将语义相近的几个函数用同一个名字表示,但是参数列表(参数的类型,个数,顺序不同)不同,这就是==函数重载==,返回值类型可以不同
   特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无
2. override,派生类覆盖基类的虚函数,实现接口的重用,==返回值类型必须相同==
   特征:==不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字==(必须是虚函数)
3. overwrite,派生类屏蔽了其同名的基类函数,返回值类型可以不同
   特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字



## 多态

### [动态多态和静态多态](https://www.cnblogs.com/lizhenghn/p/3667681.html)

多态的实现分为==静态多态和动态多态==

1. 静态多态: 主要是 ==重载== ,在编译的时候就已经确定;
2. 静态多态设计思想: 对于相关的对象类型,直接实现它们各自的定义,不需要共有基类,甚至可以没有任何关系。只需要各个具体类的实现中要求相同的接口声明,这里的接口称之为隐式接口。客户端把操作这些对象的函数定义为模板,当需要操作什么类型的对象时,直接对模板指定该类型实参即可(或通过实参演绎获得)。
3. 动态多态: 用虚函数机制实现的,在运行期间动态绑定
4. 动态多态设计思想: 对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。

如动物音乐大赛, 乌鸦和狗和猫报名, 但是这三个对象都指向动物类(这是一个基类), 使用动物指针对乌鸦, 狗, 猫进行方法调用, 就是多态

### 动态多台和静态多态的比较

|        | 静态多态                                                     | 动态多态                                       |
| ------ | ------------------------------------------------------------ | ---------------------------------------------- |
| 优点   | 编译期完成, 效率高, 编译器可优化                             | 运行期动态绑定,                                |
|        | 强适配性和松耦合性, 通过偏特化,全特化处理特殊类型            | 实现与接口分离, 可以复用                       |
|        | 静态多态通过模板编程为C++带来了泛型设计概念, 如STL库         | 处理同一继承体系下异质对象集合的强大威力       |
| 缺点   | 用模板实现静态多态, 模板不足, 调试困难,编译耗时, 代码膨胀, 编译器支持的兼容性, | 运行期绑定, 运行开销大                         |
|        | 不能处理异质对象集合                                         | 编译器无法对虚函数进行优化                     |
|        |                                                              | 笨重的类继承体系, 对接口的修改影响整个类的层次 |
| 不同点 | 本质不同, 静态多态,编译阶段, 模板实现, 动态多态,运行阶段, 继承虚函数实现 |                                                |
|        | 动态多态接口是显式, 静态是隐式,                              |                                                |


https://www.cnblogs.com/zkfopen/p/11061414.html

### 虚函数表

```cpp
class B {
    virtual int f1 (void);  // 0
    virtual void f2 (int);  // 1
    virtual int f3 (int);   // 2
};

// 虚函数表
vptr -> [B::f1, B::f2, B::f3]
          0      1      2
```

首先对于包含虚函数的类, 编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针的数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。

除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址,
该变量被称为虚函数表指针,简称虚指针(vptr)。例如:

```cpp
B* pb = new B;  /
pb->f3 (12);
// 被编译为
pb->vptr[2] (pb, 12); // B::f3  参数pb是this指针, 他首先找到虚函数表, 调用对应的f3函数

// 注意:虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表。
vptr-> | vptr1  |   vptr2 |   vptr3 |
```

## 多态的工作原理(底层实现机制)

```cpp
// 继承自B的子类
class D : public B {
    int f1 (void); 
    int f3 (int);  
    virtual void f4 (void);
};

// 虚函数表
// 子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表。
// 如下所示, 当基类指向子类时, vptr->vptr(子类)->D::f3, 这是因为他根据动态绑定原则, 先不直接加载基类自身函数, 编译器在运行时, 根据基类指向的子类的vptr函数进行加载指令, 这就实现了多态
vptr(子类)-> D::f1, B::f2, D::f3, D::f4
             0       1     2      3
```

```cpp
// 指向子类虚表的虚指针就存放在子类对象的基类子对象中。例如:
B* pb = new D;  // 父类指向子类, 调用子类的方法
pb->f3 (12);
// 被编译为
pb->vptr(子类)[2] (pb, 12); // D::f3 pb是基类指针, 他首先找基类的虚函数表vptr, 
```


```cpp
// 示例
class A{
public: 
    A():m_ch('A'){}
    virtual void foo() {
        cout << m_ch << "::foo()" << endl ;
    }
    virtual void bar(){
        cout << m_ch << "::bar()" << endl ;
    }
private:
    char m_ch ;
} ;
class B:public A{
public:
    B():m_ch('B'){}
    void foo(){
        cout << "B::foo()" <<endl ;
    }
private:
    char m_ch ; 
} ;

int main(){
    A a ;
    void(**vptr_a)(A*) = *(void(***)(A*))&a ;
    cout << (void *)vptr_a <<endl ;                                 //0x8048bb0
    cout << "foo():"<<(void *)vptr_a[0] <<endl ;            //foo():0x8048992
    cout << "bar():" <<(void *)vptr_a[1] <<endl ;           //bar():0x80489d4
    vptr_a[0](&a) ;                                                              //A::foo()
    vptr_a[1](&a) ;                                                              //A::bar()
    cout << "-----------------------------------------" <<endl ;
    B b ;
    void(**vptr_b)(B*) = *(void(***)(B*))&b ;                
    cout << (void *)vptr_b <<endl ;                                 //0x8048ba0
    cout << "foo():"<<(void *)vptr_b[0] <<endl ;            //foo():0x8048a3a 
    cout << "bar():" <<(void *)vptr_b[1] <<endl ;           //bar():0x80489d4
    vptr_b[0](&b) ;                                                              //B::foo()
    vptr_b[1](&b) ;                                                              //A::bar()
}

```

上述程序说明了虚函数表是真实存在的:
void(**vptr_a)(A*) = *(void(***)(A*))&a ;建立一个vptr_a的虚函数表,如下图:

### 动态绑定

当编译器“看到”通过指针或者引用调用基类中的虚函数时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该调用语句,这段代码在运行时被执行,完成如下操作:
1)根据调用指针或引用的目标对象找到其内部的虚表指针;
2)根据虚表指针找到其所指向的虚函数表;
3)根据虚函数名和函数指针在虚函数表中的索引,找到所调用虚函数的入口地址;
4)在完成函数调用的准备工作以后,直接跳转到虚函数入口地址处顺序执行函数体指令序列,直到从函数中返回。
3.动态绑定对性能的影响
1)虚函数表和虚指针的存在势必要增加内存空间的开销。
2)和普通函数调用相比,虚函数调用要多出一个步骤,增加运行时间的开销。
3)动态绑定会妨碍编译器通过内联优化代码,虚函数不能内联。



## 析构函数为何为虚函数:

父类设置为虚函数,保证new子类时,使用父类指针指向子类对象,释放父类指针时, 会自动释放子类空间, 防止内存泄漏

也就是父类指针释放的应该是子类对象的父类成员, 但是由于虚函数的特点, 同时会调用子类的析构函数

## map和set的实现

|          | map/set                        | unordered_map/unordered_set |
| -------- | ------------------------------ | --------------------------- |
| 底层     | 红黑树                         | 哈希表                      |
| 有序性   | 自动排序                       | 无序, key映射               |
| 查找时间 | O(logn)                        | O(1)                        |
|          | 空间占用率高(保存父子节点关系) | 空间占用率低                |

1. C++关联容器,红黑树,map是KV对,K索引,V数据, set中K为集合; 
2. map修改V不改K, 因为红黑树底层按照K排列,保证有序,如果可以修改K,首先需要删除K,调节树平衡,在插入修改后的K,调节平衡, 将会破坏map和set的结构; 
3. map支持下标查询,不存在默认值, 因此慎用, 建议find

***

## 指针和数组的区别?

```cpp
int arr[4] = {1,2,3,4};
int* p1 = arr;
int *p2 = &arr[0];
cout<<arr<<" "<< p1 << " "<<p2<<endl;
*(p1+4) // 越界, 你不能更改
// 0x7fffeebfabf0 0x7fffeebfabf0 0x7fffeebfabf0
```



| 指针                       | 数组 arr=[1,2,3], arr表示数组的首地址                        |
| -------------------------- | ------------------------------------------------------------ |
| 数据的地址                 | 保存数据                                                     |
| 间接访问数据, 获得指针内容 | 直接访问数据                                                 |
| 动态数据结构               | 固定数目, 数据类型相同                                       |
| malloc分配内存和free释放   | 隐式分配和删除                                               |
| 指向匿名数据, 操作匿名函数 | 自身作为数据名                                               |
|                            | *(arr+1) 表示第2个元素, 也就是数组的这个指针支持加减法, 加减的是元素位置 |
|                            |                                                              |

***

## [深浅拷贝](https://www.zhihu.com/question/36370072/answer/99839254)

==浅拷贝==实际上是对类成员的引用,==深拷贝==是对类成员的复制并且重新分配了内存

## 定义字符串的区别

const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123"; 区别
const 常量区  
\* brr 地址存放

## [类型转换? cast](https://mp.weixin.qq.com/s/6YW7VX787X7kZiRBLbVn-Q)

1. reinterpret_cast:任意类型的指针之间的转换,对转换的结果不做任何保证(不建议使用)
2. dynamic_cast:只能用于存在虚函数的父子关系的强制类型转换
3. const_cast:删除变量的const属性方便再次赋值
4. static_cast:完成基础数据类型;同一个继承体系中类型的转换;任意类型与空指针类型 void* 之间的转换。不能对其他指针类型进行转换

```cpp
int i = 10;
double d2 = static_cast<double>(i); //相当于创建一个static_cast<double>类型的匿名对象赋值给d2
float *p4 = static_cast<float*>(&i); // err
int* p2 = reinterpret_cast<int*>(i); // 任意类型转换
int *p = const_cast<int*>(&i);
```

***

## [const指针](https://blog.csdn.net/xixihaha331/article/details/51280263)

1. 常量指针和指针常量

```cpp
int a = 20;
int b=40;
//------------------------------------------------
const int *p; // 常量指针值不变, 对象可变, 这就是为何 for(const auto &a : arr){}
p=&a;
// (*p)++;  // 这里会报错, 因为不能修改指向的值
p=&b;    // 这里不会报错, 因为可以指向别的对象
printf("a=%d, b=%d, *p=%d\n", a, b, *p); //a=20, b=40, *p=40

//------------------------------------------------
int* const p1 = &a; // 指针常量, 对象不可变, 值可变
// p1=&b; // 会报错, 因为指针是常量, 对象不能变
(*p1)++;  // 这里不会报错, 因为可以改值, 但是不可以改对象
printf("a=%d, b=%d, *p1=%d\n", a, b, *p1); //a=21, b=40, *p1=40

//------------------------------------------------
const int* const p2 = &b; // 常量指针常量值, 
// p2=&a; // 会报错, 因为指针是常量, 对象不能变
// (*p2)++;  // 会报错, 因为值是常量, 值不能变
printf("a=%d, b=%d, *p2=%d\n", a, b, *p2); //a=21, b=40, *p2=40
```

2. 常量参数 `void func(char *dest_str, const char *src_str)`
3. 修饰函数返回值 `const char *get_string(void)` 注意只能是指针传递, 如果是值传递就没用了
4. 修饰成员函数 `int get_count(void) const;` 不可以修改对象的成员变量



## [new/delete 与 malloc/free 的区别是什么](https://blog.csdn.net/linux_ever/article/details/50533149)

|                    | new/delete                                                   | malloc/free                                                  |
| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 类型               | C++的关键字                                                  | C语言库函数                                                  |
| 返回类型安全性     | 只需要对象名即可创建对象, 返回的是对象类型指针, 类型严格与对象匹配, 无需进行类型转换 | 开辟空间大小严格, 返回的是(void*), 需要通过强制类型转换成需要的类型 |
|                    | ==new调用构造函数,  delete调用析构函数, 能保证对象的初始化和回收内存== | 不会调用构造析构函数, 无法满足动态对象要求                   |
|                    | ==由于new对象返回的指针, 在底层空间还存储了这个对象开辟空间的大小, 因此在析构的时候能够根据这个存储进行回收内存== |                                                              |
| 内存分配失败       | ==抛出bac_alloc异常try { int *a = new int(); } catch (bad_alloc) {  }== | 返回NULL                                                     |
| 是都需要指定内存   | ==new无需指定, 编译器会根据类型自行计算==                    | 需要显式指出所需内存                                         |
| 实际创建步骤       | 1, 调用operator new函数, 分配一块足够大的内存, 方便存储特定类型对象,  2, 编译器运行相应的构造函数, 构造对象, 并传入初始值, 3, 对象构造完成, 返回一个指向该对象的指针 |                                                              |
| delete释放对象步骤 | 1, 调用对象析构函数, 2, 编译器调用operator delete函数释放内存空间 |                                                              |

new/delete 是 C++的关键字,而 malloc/free 是 C 语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数

***

## 构造函数和析构函数

| 构造函数                                                     | 析构函数                                                     |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| 无类型, 没有返回值, 名字和类名相同, 可重载                   | 无类型, 无返回值, 名字和类名相同, 不带参数, 不可重载, 析构函数只有一个, 前面加个`~` |
| 作用: 完成对象的初始化                                       | 对象被删除前有系统自动执行清理工作                           |
| 当对象d被创建时, 会自动调用构造函数, 当未定义构造函数时,编译器会自动假设存在两个默认构造函数cdata::cdata(){} |                                                              |
| Cdate::Cdate(const Cdate& a)                                 |                                                              |
| 对象的析构函数在被销毁前调用, 对象何时销毁与其作用域相关     |                                                              |
| 全局对象在程序运行结束时销毁                                 |                                                              |
| 自动对象在离开作用域时销毁                                   |                                                              |
| 动态对象使用delete时销毁                                     |                                                              |




## allocator 内存分配和释放?

1. STL分配器封装与STL容器在内存管理上的底层细节; 
2. new(调用operate new配置内存,调用对象构造函数构造对象内容)delete(调用析构函数, 释放内存); 
3. allocator将两个阶段操作区分开来,内存配置有alloc::allocate()负责, 释放alloc::deallocate(); 对象构造由construct负责,内存析构由destroy负责; 
4. 为了提升内存管理效率, 减少申请小内存内存碎片问题, STL采用两级配置器, 当分配大小空间超过128B, 使用一级空间配置器(malloc, realloc, free进行内存管理和内存空间分配和释放),大于128B, 二级(内存池技术,通过空闲链表来管理内存)

***

## malloc 的原理

malloc函数用于动态分配内存; 为了减少内存碎片和系统调用开销, malloc采用内存池的方式, 首先申请大块内存作为堆, 再将堆分成多个内存块, 以块作为内存管理的基础单位; 当用户申请内存时, 直接从堆区分配一块合适的空闲块; malloc采用隐式链表结构将堆区分成连续,大小不一的块, 包含已分配和未分配块; 同时malloc采用显示链表结构管理所有空闲块, 双向链表, 每个空闲块记录一个连续的, 未分配的地址;
当进行内存分配时,Malloc 会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc 采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。
Malloc 在申请内存时,一般会通过 brk 或者 mmap 系统调用进行申请。其中当申请内存小于128K 时,会使用系统函数 brk 在堆区中分配;而当申请内存大于 128K 时,会使用系统函数 mmap在映射区分配。


## STL迭代器删除元素:

1. 对于序列容器vector,deque来讲,使用erase, 后面元素前移一位,erase返回下一个有效的迭代器; 
2. 对于map,set,使用erase,当前元素迭代器失效,但是因为结构为红黑树,所以删除元素不会影响下一元素迭代器,在调用erase之前,记录下一个元素的迭代器即可, 
3. 对于list,使用不连续分配内存, erase返回下一个有效迭代器

## vector和list 的区别

|                            Vector                            |                           List                           |
| :----------------------------------------------------------: | :------------------------------------------------------: |
| 连续存储的容器,动态数组,在堆上分配空间, 两倍容量增长, 顺序内存 |   动态双向链表, 堆上空间, 每删除一个元素会释放一个空间   |
| 访问:O(1)(随机访问);插入:后插快, 中间需要内存拷贝, 内存申请和释放; 删除: 后删快, 中间需要内存拷贝 | 访问: 随机访问差, 只能开头和结尾; 插入和删除快, 常数开销 |
|    适用场景:经常随机访问,且不经常对非尾节点进行插入删除    |                   适用于经常插入和删除                   |
|                          下面是区别                          |                                                          |
|                             数组                             |                         双向链表                         |
|                         支持随机访问                         |                      不支持随机访问                      |
|                           顺序内存                           |                         离散内存                         |
|                  中间节点插入删除会导致拷贝                  |                           不会                           |
|                  一次性分配好内存, 二倍扩容                  |            list每次在新节点插入会进行内存申请            |
|                  随机访问性能好,插入性能差                   |                           相反                           |

## STL迭代器的作用, 为何不用指针而用迭代器?

1. 迭代器提供一种方法顺序访问一个聚合对象各个元素, 而又不暴露该对象的内部表示; 或者说运用这种方法, 是的我们可以在不知道对象内部结构情况下, 按照一定顺序规则直接反问聚合对象的各个元素
2. 与指针的区别: 迭代器不是指针, 而是类模板, 表现像指针,模拟指针功能,重载指针操作符如->, *, ++等, 相当于一种智能指针, 根据不同类型的数据结构实现不同的操作
3. 迭代器类的访问方式就是把不同集合类的访问逻辑抽象出来, 是的不用暴露集合内部的结构而达到循环遍历的效果;

## C++中类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符
类内部, 不区分, 无限制
子类, 能访问父类的private以外的属性和方法
其他类, 只能访问public

## struct和class的区别

在 C++中,可以用 struct 和 class 定义类,都可以继承。区别在于:structural 的默认继承权限和默认访问权限是 public,而 class 的默认继承权限和默认访问权限是 private。另外,class 还可以定义模板类形参,比如 template <class T, int i>

## C++源文从文本到可执行文件经历过程

1. 预处理: 源代码文件包含的头文件, 预编译语句, 分析替换, 生成预编译文件
2. 编译阶段: 特定编码
3. 汇编阶段: 转化为机器码, 重定位目标文件
4. 链接阶段: 多个目标文件及所需要的库链接成为最终可执行文件

## include "" 和include <>的区别

1. 编译器预处理阶段查找头文件的路径不一样
2. 双引号查找路径: 当前头文件目录, 编译器设置的头文件路径, 系统变量路径path指定的路径
3. <>查找路径: 编译器设置的头文件, 系统变量


## fork,wait,exec 函数

父进程产生子进程使用 fork 拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec 函数可以加载一个 elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork 从父进程返回子进程的 pid,从子进程返回 0.调用了 wait 的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回 0,错误返回-1。exec 执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1

## STL 里 resize 和 reserve 的区别

1. resize(): 改变当前容器内含有元素的数量
   vector<int>v; 
   v.resize(20);
   v.push_back(2); // 此时的2是21位置
2. reserve(len): 改变当前容器最大容量, 不会生成元素; 如果reserve大于capacity, 重新分配个len的对象空间, 原始对象复制过来



## BSS端等六段: C++的内存管理?

![代码存储结构](D:/0_2024/work/README.assets/代码存储结构.jpg)
在C++中, 虚拟内存分为代码段,数据段, BSS段, 堆区, 文件映射区, 栈区六个部分

1. 代码段: 包括只读存储区(字符串常量)和文本区(程序的机器代码), 只读
2. 数据段: 存储程序中已初始化的全局变量和静态变量; 属于静态内存分配
3. BSS段: 存储未初始化或初始化为0的全局变量和静态变量(局部+全局); 属于静态分配, 程序结束后静态变量资源由系统自动释放。
4. 堆区:  调用 new/malloc 函数时在堆区动态分配内存,同时需要调用 delete/free 来手动释放申请的内存。频繁的malloc free造成内存空间不连续, 产生碎片, 因此堆比栈效率低
5. 映射区:存储动态链接库以及调用 mmap 函数进行的文件映射
6. 栈区: 存储函数的返回地址,返回值, 参数, 局部变量; 编译器自动释放,

## 内存泄漏

1. 堆内存泄漏, 如果malloc, new, realloc从堆分配的内存, 由于程序错误造成内存未释放, 产生的
2. 系统资源泄漏: 程序使用系统资源: bitmap, handle, socket忘记释放, 将导致系统效能和稳定差
3. 没有将基类析构函数定义为虚函数, 基类指针指向子类对象后, 释放基类时, 子类资源不会被正确释放


## 判断内存泄漏:

1. 内存泄漏原因: 通常调用malloc/new等内存申请操作, 缺少对应的free/delete
2. 判断内存是否泄漏, 可以使用Linux环境下的内存泄漏检测工具, 也可以在写代码时添加内存申请和释放统计功能, 统计申请和释放是否一致, 以此判断内存泄漏
   varglind,mtrace 检测


## 如何采用单线程的方式处理高并发?

I/O 复用 异步回调

## 大端小端?

大端是指低字节存储在高地址;小端存储是指低字节存储在低地址。我们可以根据联合体来判断该系统是大端还是小端。因为联合体变量总是从低地址存储。

## 设计一个server, 实现多个客户端请求

1. 多线程, 
2. 线程池 ,
3. IO复用

## [C++的线程锁你知道几种?](https://www.cnblogs.com/steamedbun/p/9376458.html)

### 互斥锁mutex

用于控制多线程对他们共享资源互斥访问的一个信号量, 也就是说为了避免多个线程同一个时刻操作一个共同资源;例如线程池中的多个空闲线程核一个任务队列, 任何一个线程都要使用互斥锁互斥访问任务队列, 避免多个线程同时访问任务队列发生错乱, 如果其他线程想要获取互斥锁, 只能阻塞等待

### 条件锁

条件锁就是所谓的条件变量, 某一个线程因为某个条件未满足时, 可以使用条件变量是程序处于阻塞状态, 一旦条件满足以信号量的方式唤醒一个因为该条件而被阻塞的线程

### 自旋锁

假设我们有一个两个处理器core1和core2计算机,现在在这台计算机上运行的程序中有两个线程:T1和T2分别在处理器core1和core2上运行,两个线程之间共享着一个资源。

首先我们说明互斥锁的工作原理,互斥锁是是一种sleep-waiting的锁。假设线程T1获取互斥锁并且正在core1上运行时,此时线程T2也想要获取互斥锁(pthread_mutex_lock),但是由于T1正在使用互斥锁使得T2被阻塞。当T2处于阻塞状态时,T2被放入到等待队列中去,处理器core2会去处理其他任务而不必一直等待(忙等)。也就是说处理器不会因为线程阻塞而空闲着,它去处理其他事务去了。

而自旋锁就不同了,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用自旋锁,而T2也去申请这个自旋锁,此时T2肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2的处理器core2会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。

### 读写锁

说到读写锁我们可以借助于“读者-写者”问题进行理解。首先我们简单说下“读者-写者”问题。计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是一个简单的读者-写者模型。

1 如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样可以有多个线程并行操作。这个时候如果再用写锁加锁就会发生阻塞。写锁请求阻塞后,后面继续有读锁来请求时,这些后来的读锁都将会被阻塞。这样避免读锁长期占有资源,防止写锁饥饿。

2 如果一个线程用写锁锁住了临界区,那么其他线程无论是读锁还是写锁都会发生阻塞。

## 什么类不能被继承

1. 将自身的构造函数与析构函数放在private作用域
2. 友元类 friend
3. class FinalClass final { };

## 结构体和类大小

空的为1, 内存对齐, int double, char 则4,8,1+3, 后面的char需要对齐

## 类的什么方法不能是虚函数

普通函数, 友元函数, 构造函数, 内联成员, 静态态成员函数



## hash扩容

HashMap初始容量大小16,扩容因子为0.75,扩容倍数为2;

底层是数组加链表, 随着数据的增加, hash冲突会增加, 因此设置扩容因子, 当数据数量到达hash容量的扩容因子倍, 就会以二倍扩容, 16*2=32, 然后重新计算每个元素在数组中的位置.

## C++派生类的构造函数和析构函数执行顺序及其构造形式

### 派生类的构造函数和析构函数的执行顺序

   <font color=red>先执行基类的构造函数,随后执行派生类的构造函数,当撤销派生类对象时,先执行派生类的析构函数,再执行基类的析构函数。</font>

### 派生类构造函数和析构函数的构造原则

   1)派生类不能继承基类中的构造函数和析构函数。
   当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
   2)当派生类中还有对象成员时,其构造函数的一般形式为:

### 注意

1. 当基类构造函数不带参数时,派生类不一定需要定义构造函数,然而当基类的析构函数哪怕只有一个参数,也要为派生类定义构造函数,甚至所定义的派生类析构函数的函数体可能为空,仅仅起到传递参数的作用
2. 当基类使用缺省构造函数时或不带参数的构造函数时,则在派生类中定义构造函数时,可以省略:基类构造函数名(参数表),此时若派生类不需要构造 函数,则可以不定义构造函数。
3. 如果派生类的基类也是一个派生类,则每个派生类只需负责其直接基类的 构造,依次上溯。
4. 如果析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的 基类无关,故基类的析构函数不会因为派生类没有析构函数而得不到执行,他们各自是独立的

## 虚函数调用的工作原理 基于虚函数多态的机制

### 虚函数和纯虚函数

虚函数: C++中用于实现多态的机制, 核心理念是通过基类访问派生类定义的函数, 是C++中多态的一个重要体现; 利用基类指针访问派生类中的虚函数, 这种情况采用的是动态绑定技术;

纯虚函数: 基类声明的虚函数, 基类无定义, 要求任何派生类都需要定义自己的实现方法, 在基类中实现纯虚函数的方法是在函数原型后面加 `=0` 纯虚函数不能实例化对象;

### 抽象类

特殊类, 为了抽象和设计的目的建立的, 处于继承层次结构的较上层;

定义: 带有纯虚函数的类为抽象类

作用: 将有关操作作为结果接口组织在一个继承层次结构中, 由他来为派生类提供一个公共根, 派生类将具体实现在其积累中作为接口的操作. 所以派生类实际上刻画了一组子类的操作接口的通用语义, 这些语义传给子类, 子类可以具体实现这些语义, 在将这些语义传给自己的子类

注意: 抽象类只能作为基类, 纯虚函数的实现由派生类给出; 如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。



### 虚函数表

```cpp
class B {
    virtual int f1 (void);  // 0
    virtual void f2 (int);  // 1
    virtual int f3 (int);   // 2
};

// 虚函数表
vptr -> [B::f1, B::f2, B::f3]
          0      1      2
```

首先对于包含虚函数的类, 编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针的数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。

除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址,
该变量被称为虚函数表指针,简称虚指针(vptr)。例如:

```cpp
B* pb = new B;
pb->f3 (12);
// 被编译为
pb->vptr[2] (pb, 12); // B::f3       参数pb是this指针

// 注意:虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表。
vptr-> | vptr1  |   vptr2 |   vptr3 |
```

### 多态的工作原理(底层实现机制)

```cpp
// 继承自B的子类
class D : public B {
    int f1 (void); 
    int f3 (int);  
    virtual void f4 (void);
};

// 虚函数表
// 子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表。
vptr(子类)-> D::f1, B::f2, D::f3, D::f4
             0       1     2      3
```

```cpp
// 指向子类虚表的虚指针就存放在子类对象的基类子对象中。例如:
B* pb = new D;  // 父类指向子类, 调用子类的方法
pb->f3 (12);
// 被编译为
pb->vptr(子类)[2] (pb, 12); // D::f3
```


```cpp
// 示例
class A{
public: 
    A():m_ch('A'){}
    virtual void foo() {
        cout << m_ch << "::foo()" << endl ;
    }
    virtual void bar(){
        cout << m_ch << "::bar()" << endl ;
    }
private:
    char m_ch ;
} ;
class B:public A{
public:
    B():m_ch('B'){}
    void foo(){
        cout << "B::foo()" <<endl ;
    }
private:
    char m_ch ; 
} ;

int main(){
    A a ;
    void(**vptr_a)(A*) = *(void(***)(A*))&a ;
    cout << (void *)vptr_a <<endl ;                                 //0x8048bb0
    cout << "foo():"<<(void *)vptr_a[0] <<endl ;            //foo():0x8048992
    cout << "bar():" <<(void *)vptr_a[1] <<endl ;           //bar():0x80489d4
    vptr_a[0](&a) ;                                                              //A::foo()
    vptr_a[1](&a) ;                                                              //A::bar()
    cout << "-----------------------------------------" <<endl ;
    B b ;
    void(**vptr_b)(B*) = *(void(***)(B*))&b ;                
    cout << (void *)vptr_b <<endl ;                                 //0x8048ba0
    cout << "foo():"<<(void *)vptr_b[0] <<endl ;            //foo():0x8048a3a 
    cout << "bar():" <<(void *)vptr_b[1] <<endl ;           //bar():0x80489d4
    vptr_b[0](&b) ;                                                              //B::foo()
    vptr_b[1](&b) ;                                                              //A::bar()
}

```

上述程序说明了虚函数表是真实存在的:



### 动态绑定

当编译器“看到”通过指针或者引用调用基类中的虚函数时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该调用语句,这段代码在运行时被执行,完成如下操作:
1)根据调用指针或引用的目标对象找到其内部的虚表指针;
2)根据虚表指针找到其所指向的虚函数表;
3)根据虚函数名和函数指针在虚函数表中的索引,找到所调用虚函数的入口地址;
4)在完成函数调用的准备工作以后,直接跳转到虚函数入口地址处顺序执行函数体指令序列,直到从函数中返回。
3.动态绑定对性能的影响
1)虚函数表和虚指针的存在势必要增加内存空间的开销。
2)和普通函数调用相比,虚函数调用要多出一个步骤,增加运行时间的开销。
3)动态绑定会妨碍编译器通过内联优化代码,虚函数不能内联。



# ref

[nwu_zjq](https://gitee.com/nwu_zjq)