C++ 备忘清单 === 提供基本语法和方法的 [C++](https://zh.cppreference.com/) 快速参考备忘单 入门 -------- ### hello.cpp ```cpp #include 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 | -231 到 231-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 个宽字符 ### 用户输入 ```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-循环) ### 函数 ```cpp #include 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 namespace ns1 {int val(){return 5;}} int main() { std::cout << ns1::val(); } ``` ---- ```cpp #include namespace ns1 {int val(){return 5;}} using namespace ns1; using namespace std; int main() { cout << val(); } ``` 名称空间允许名称下的全局标识符 C++ 数组 ------ ### 定义 ```cpp std::array marks; // 定义 marks[0] = 92; marks[1] = 97; marks[2] = 98; // 定义和初始化 std::array = {92, 97, 98}; // 有空成员 std::array marks = {92, 97}; std::cout << marks[2]; // 输出: 0 ``` ### 操控 ```cpp ┌─────┬─────┬─────┬─────┬─────┬─────┐ | 92 | 97 | 98 | 99 | 98 | 94 | └─────┴─────┴─────┴─────┴─────┴─────┘ 0 1 2 3 4 5 ``` ---- ```cpp std::array 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?"; ``` ### 运算符 #### 关系运算符 :--|-- :--|-- `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 int main() { auto print = [](int num) { std::cout << num << std::endl; }; std::array arr = {1, 2, 3, 4}; std::for_each(arr.begin(), arr.end(), print); return 0; } ``` ### 基于范围 (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 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 #include // 导入库 int main() { // sqrt() 来自 cmath std::cout << sqrt(9); } ``` ### Lambda 表达式 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` 可以不写,编译器会自动推导 - `{}` 中的内容就是函数体,依照普通函数的使用方法使用即可 此处给出一个 Lambda 表达式的实际使用例子(当然可以使用 `str::copy`): ```cpp // vec中包含1, 2, 3, 4, 5 std::vector 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 `:C++多线程库 - `#include `:C++互斥量库 - `#include `:C++异步库 ### 线程的创建 以普通函数作为线程入口函数: ```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(); ``` ### 锁 > `#include ` #### 锁的基本操作 创建锁 ```cpp std::mutex m; ``` 上锁 ```cpp m.lock(); ``` 解锁 ```cpp m.unlock(); ``` 尝试上锁:成功返回`true`,失败返回`false` ```cpp m.try_lock(); ``` 解锁 ```cpp m.unlock(); ``` #### 更简单的锁 —— `std::lock_guard` 构造时上锁,析构时解锁 ```cpp std::mutex m; std::lock_guard lock(m); ``` 额外参数:`std::adopt_lock`:只需解锁,无需上锁 ```cpp // 手动上锁 m.lock(); std::lock_guard lock(m, std::adopt_lock); ``` #### `unique_lock` 构造上锁,析构解锁 ```cpp std::mutex m; std::unique_lock lock(m); ``` ##### `std::adopt_lock` 只需解锁,无需上锁 ```cpp // 手动上锁 m.lock(); std::unique_lock lock(m, std::adopt_lock); ``` ##### `std::try_to_lock` 尝试上锁,可以通过`std::unique_lock::owns_lock()`查看状态 ```cpp std::unique_lock lock(m, std::try_to_lock); if (lock.owns_lock()) { // 拿到了锁 } else { // 没有 } ``` ##### `std::defer_lock` 绑定锁,但不上锁 ```cpp std::unique_lock lock(m, std::defer_lock); lock.lock(); lock.unlock(); ``` ##### `std::unique_lock::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 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` 的线程。 ### 获取线程的运行结果 > `#include ` #### 创建异步任务 ```cpp double func(int val); // 使用std::async创建异步任务 // 使用std::future获取结果 // future模板中存放返回值类型 std::future 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::get()`或者`std::future::wait()`被调用时。此时调用线程会直接运行线程入口函数,换言之,**不会创建子线程** - `std::launch::async`:立即创建子线程,并运行线程入口函数 - `std::launch::deferred | std::launch::async`:默认值,由系统自行决定 #### 返回值的状态 让当前线程等待一段时间(等待到指定时间点),以期待返回值准备好: ```cpp extern double foo(int val) {} std::future 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 result; ``` 如果要多次获取结果,可以使用`std::shared_future`,其会返回结果的一个**拷贝**。 对于不可拷贝对象,可以在`std::shared_future`中存储对象的指针,而非指针本身。 C++ 预处理器 ------------ ### 预处理器 - [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&action=edit&redlink=1) - [module](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/module&action=edit&redlink=1) ### Includes ```cpp #include "iostream" #include ``` ### Defines ```cpp #define FOO #define FOO "hello" #undef FOO ``` ### If ```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"; ``` ### 文件和行 ```cpp #define LOG(msg) console.log(__FILE__, __LINE__, msg) #=> console.log("file.txt", 3, "hey") ``` 各种各样的 ------------- ### 转义序列 转义序列 | 说明 :--|-- `\b` | 退格键 `\f` | 换页 `\n` | 换行 `\r` | 返回 `\t` | 水平制表符 `\v` | 垂直制表符 `\\` | 反斜杠 `\'` | 单引号 `\"` | 双引号 `\?` | 问号 `\0` | 空字符 ### 关键字 - [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) ### 预处理器 - [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&action=edit&redlink=1) - [module](https://zh.cppreference.com/mwiki/index.php?title=cpp/keyword/module&action=edit&redlink=1) 另见 ---- - [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 #include // 注意map的key会自动排序, 所以在遇到排序问题时参考 #include #include #include using namespace std; // map中 所有元素都是pair // pair中 第一个元素为key(键值) 用于索引 第二个元素value(实值) // 所有元素都会根据键值自动排序 // 本质: // map /mulmap底层都是二叉树 // 优点: // 可根据key值快速找到value值 // map不允许容器中出现相同的值 // mulmap中允许出现重复的值2 // map大小和交换: // .size() //返回容器中元素的数目 // .empty() //判断容器是否为空 // .swap(st) //交换两个容器 // 插入和删除: // insert(elem) //容器中插入元素 inseert(pair ( , )); // 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 myMap; // 这其实就是将map里面的数据格式给固定下来而已, map = myMap myMap test; //插入 test.insert(pair(3, "a")); test.insert(pair(4, "b")); test.insert(pair(5, "c")); test.insert(pair(8, "d")); test.insert(pair(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 myMap; // 创建 myMap.insert(pair(3, "a")); // 插入 myMap.insert(pair(5, "b")); myMap.insert(pair(50, "d")); for (auto &i : myMap) cout <::reverse_iterator iter = myMap.rbegin(); if (iter != myMap.rend()) cout<<"最后一个值是"<first << "-" << iter->second <first << "-" << myMap.end()->second <second << endl; // 查找find auto it = myMap.find(50); //查找key=50的数据是, find(key)返回的是pair格式, 也就是(50, e), 所以it->second= if (it != myMap.end()) cout <first << "-"<second << endl; // 判断存在, cout << "3有" << myMap.count(3) << endl; } int main() { // map_test2(); unordered_map map1{{1, "hel"}, {2, "ahskg"}, {3, "world"}}; cout< #include #include #include #include // 为了使用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="< str3 << n1; string str4 = str3.str(); cout<<"将int类型转化为string类型: "<> str5; cout< #include #include #include using namespace std; // 指针和引用 void test1(int*a, int b, int &c){ cout<<"函数参数输入是: *a="<<*a<<" b="< #include #include #include #include // 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<> c){ cout<> s){ cout< counts; // hello//world,//my//name//is//zjq// while(s4 >> str1){ counts[str1]++; }cout< // getline string str; getline(cin, str); //可以将带空格的字符放入到str中 cout<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 p1 (new string ("I reigned lonely as a cloud.”)); auto_ptr p2; p2 = p1; //auto_ptr 不会报错. 此时p2掠夺了p1所有权, 使用p1的时候, 内存崩溃 3. unique_ptr p3 (new string ("auto")); unique_ptr 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()" <(i); //相当于创建一个static_cast类型的匿名对象赋值给d2 float *p4 = static_cast(&i); // err int* p2 = reinterpret_cast(i); // 任意类型转换 int *p = const_cast(&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 ## 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(): 改变当前容器内含有元素的数量 vectorv; 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++派生类的构造函数和析构函数执行顺序及其构造形式 ### 派生类的构造函数和析构函数的执行顺序 先执行基类的构造函数,随后执行派生类的构造函数,当撤销派生类对象时,先执行派生类的析构函数,再执行基类的析构函数。 ### 派生类构造函数和析构函数的构造原则 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()" <