[TOC] # C++标准库是什么? C++标准库是一系列类和函数的集合,属于C++标准的一部分,只要支持C++标准的环境中就可以直接使用。内容丰富,功能强大。对初学者来说也是很好的提升C++技能的学习材料。 C++标准库主要包括C++库和C语言库,头文件如下图所示。 C++标准库都定义在命名空间std中。 ![headers_for_std_lib](./img/headers_for_std_lib.png) > 本课程的目的是带领大家了解一下C++标准库,因此讲解不会太深入,只是大致过一遍让大家知道有这么个东西。上面图片中的内容也不会全部涉及,如有需要的可以自行深入研究。   # 环境准备 安装的环境要能支持C++20以上的版本。能支持C++23最好。如果某个类或方法编译报错,则需要检查一下是哪个版本的C++才支持的,看看当前使用的版本是否支持 ## Visual Studio 安装器下载地址:https://visualstudio.microsoft.com/zh-hans/downloads/ 下载Community 版本的,不需要激活码。 以Microsoft Visual Studio Community 2022 为例,新建项目之后,在项目 **属性(Properties)-->通用(General)-->C++语言标准(C++ Language Standard)**选择Preview版本即可支持C++23的特性。 ![Visual Studio C++标准设置](./img/vs_cpp_std.png)   ## GCC(MinGW) MinGW推荐以下两个版本,选择其中一个即可。**w64devkit提供的工具更多,操作更接近Linux。** w64devkit:https://github.com/skeeto/w64devkit/releases mingw-builds:https://github.com/niXman/mingw-builds-binaries/releases 在编译时,使用**-std=...**指定需要使用的C++版本,例如: ```c++ g++ -o -std=c++20 ``` 可使用的C++标准有:c++98 c++11 c++14 c++17 c++20 c++23   ## 学习参考 cppreference中文版:https://zh.cppreference.com/ cppreference英文版:https://en.cppreference.com/ cplusplus:https://cplusplus.com/ learncpp:https://www.learncpp.com/ hackingcpp:https://hackingcpp.com/     # C语言库 | C++头文件 | C语言头文件 | 内容 | |:---------------------------------------------------:|:-----------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| | [cstdio](cppref/zh/zh/cpp/header/cstdio.html) | [stdio.h](./cppref/zh/zh/c/io.html) | 提供通用文件操作并提供有字符输入/输出能力的函数。 | | [cmath](cppref/zh/zh/cpp/header/cmath.html) | [math.h](./cppref/zh/zh/c/numeric/math.html) | 常用数学函数 | | [cstring](cppref/zh/zh/cpp/header/cstring.html) | [string.h](./cppref/zh/zh/c/string/byte.html) | 字符串处理函数和一些内存操作函数 | | [cstdlib](cppref/zh/zh/cpp/header/cstdlib.html) | [stdlib.h](./cppref/zh/zh/c/header.html) | 基础工具库:[动态内存管理](cppref/zh/zh/c/memory.html)、[程序支持工具](cppref/zh/zh/c/program.html)、[随机数](cppref/zh/zh/c/numeric/random.html)、[算法](cppref/zh/zh/c/algorithm.html)、[字符与数字转换](cppref/zh/zh/c/string/byte.html) | | [cctype](cppref/zh/zh/cpp/header/cctype.html) | [ctype.h](cppref/zh/zh/c/string/byte.html) | 字符分类与大小写转换函数库 | | [cinttypes](cppref/zh/zh/cpp/header/cinttypes.html) | [inttypes.h](cppref/zh/zh/c/types/integer.html) | 整数类型格式宏常量 | | [clocale](cppref/zh/zh/cpp/header/clocale.html) | [locale.h](cppref/zh/zh/c/locale.html) | 本地化工具库 | | [cstdarg](cppref/zh/zh/cpp/header/cstdarg.html) | [stdarg.h](cppref/zh/zh/c/variadic.html) | 变参数函数工具库 | | [cstdint](cppref/zh/zh/cpp/header/cstdint.html) | [stdint.h](cppref/zh/zh/c/types/integer.html) | 定宽整数类型及宏常量定义 | | [ctime](cppref/zh/zh/cpp/header/ctime.html) | [time.h](cppref/zh/zh/c/chrono.html) | 时间和日期工具 | | [cassert](cppref/zh/zh/cpp/header/cassert.html) | [assert.h](cppref/zh/zh/c/error.html) | 断言工具库 | | [cerrno](cppref/zh/zh/cpp/header/cerrno.html) | [errno.h](cppref/zh/zh/c/error.html) | 错误号定义与错误处理 | | [cfenv](cppref/zh/zh/cpp/header/cfenv.html) | [fenv.h](cppref/zh/zh/c/numeric/fenv.html) | 浮点环境函数与宏 | | [cfloat](cppref/zh/zh/cpp/header/cfloat.html) | [float.h](cppref/zh/zh/c/types/limits.html) | 浮点类型极限宏定义 | | [climits](cppref/zh/zh/cpp/header/climits.html) | [limits.h](cppref/zh/zh/c/types/limits.html) | 整数类型一些宏定义 | | [csetjmp](cppref/zh/zh/cpp/header/csetjmp.html) | [setjmp.h](cppref/zh/zh/c/program.html) | 非局部跳转 | | [csignal](cppref/zh/zh/cpp/header/csignal.html) | [signal.h](cppref/zh/zh/c/program.html) | 几个为信号管理的函数和常量宏 | | [cstddef](cppref/zh/zh/cpp/header/cstddef.html) | [stddef.h](cppref/zh/zh/c/types.html) | 附加基本类型及便利宏 | | [cuchar](cppref/zh/zh/cpp/header/cuchar.html) | [uchar.h](cppref/zh/zh/c/string/multibyte.html) | UTF-16 和 UTF-32 字符工具 | | [cwchar](cppref/zh/zh/cpp/header/cwchar.html) | [wchar.h](cppref/zh/zh/c/string/wide.html) | 扩展多字节和宽字符工具 | | [cwctype](cppref/zh/zh/cpp/header/cwctype.html) | [wctype.h](cppref/zh/zh/c/string/wide.html) | 用来确定包含于宽字符数据中的类型的函数 |       # 字符串 ## basic_string与string C++当中基本数据类型没有字符串,需要处理字符串时用的是字符数组,操作起来相当不方便,因此在C++标准库中提供了一个字符串类`std::string`,将字符串的一些基本操作封装到类中,简化了字符串的操作。 string类的定义如下: ```c++ typedef basic_string string; ``` 可以看到,string类由模板类basic_string类型为char时定义而来,因此要学习string有哪些操作,需要学习basic_string。   **[basic_string文档](cppref/zh/zh/cpp/string/basic_string.html)**   ### 构造 ```cpp #include #include using namespace std; int main() { // 用char*赋值构造 string str1 = "北国风光,千里冰封,万里雪飘"; cout << "str1 = " << str1 << endl; string str2("望长城内外,惟馀莽莽"); cout << "str2 = " << str2 << endl; // 无参构造,构造一个空字符串,构造之后可以通过 = 进行赋值 string str3; cout << "str3 = " << str3 << endl; // 用指定字符重复指定次数填充构造 string str4(10, 'H'); cout << "str4 = " << str4 << endl; // 拷贝构造 string str5(str2); cout << "str5 = " << str5 << "\t\t" << "str2 = " << str2 << endl; // 移动构造 string str6(move(str5)); cout << "str6 = " << str6 << "\t\t" << "str5 = " << str5 << endl; // 指定字符范围进行构造 string str7(str1, 3); string str8(str1, 3, 9); cout << "str7 = " << str7 << endl << "str8 = " << str8 << endl; // assign string str9 = "大河上下"; str9.assign(str2); cout << "str9 = " << str9 << endl; // 字符串拼接 string str10 = str1 + str2; cout << "str10 = " << str10 << endl; str10 += "只识弯弓射大雕"; cout << "str10 += " << str10 << endl; return 0; } ```   ### 元素访问 ```c++ #include #include using namespace std; int main() { string str1 = "Hello World"; string str2 = "人生易老天难老,岁岁重阳"; // at与[] string str10 = "Hello World kkkkddd"; cout << "[] " << str1[10] << endl; cout << "at " << str1.at(3) << endl; str1.at(3) = 'K'; str1[10] = 'a'; cout << "str1 = " << str1 << endl; // front back cout << "front() = " << str1.front() << endl; cout << "back() = " << str1.back() << endl; // c_str data cout << "c_str() = " << str1.c_str() << endl; cout << "data() = " << str1.data() << endl; return 0; } ```   ### 容量 ```c++ #include #include using namespace std; int main() { string str1 = "漫天皆白,雪里行军情更迫"; string str2 = "Hello Worldddddd"; string str3; // empty cout << str1.empty() << endl; cout << str3.empty() << endl; // size length cout << "str1.size() = " << str1.size() << "\tstr1.length() = " << str1.length() << endl; cout << "str2.size() = " << str2.size() << "\tstr2.length() = " << str2.length() << endl; cout << "str3.size() = " << str3.size() << "\tstr3.length() = " << str3.length() << endl; // max_size cout << "str1.max_size() = " << str1.max_size() << endl; cout << "str2.max_size() = " << str2.max_size() << endl; cout << "str3.max_size() = " << str3.max_size() << endl; // capacity cout << "str1.capacity() = " << str1.capacity() << endl; cout << "str2.capacity() = " << str2.capacity() << endl; cout << "str3.capacity() = " << str3.capacity() << endl; // reserve str2.reserve(100); cout << "str2.size() = " << str2.size() << "\t\t" << "str2.capacity() = " << str2.capacity() << endl; str2.reserve(10); cout << "str2.size() = " << str2.size() << "\t\t" << "str2.capacity() = " << str2.capacity() << endl; // shrink_to_fit str2.shrink_to_fit(); cout << "str2.size() = " << str2.size() << "\t\t" << "str2.capacity() = " << str2.capacity() << endl; // resize str2.resize(120); cout << "str2.size() = " << str2.size() << "\t\t" << "str2.capacity() = " << str2.capacity() << endl; str2.resize(10); cout << "str2.size() = " << str2.size() << "\t\t" << "str2.capacity() = " << str2.capacity() << endl; return 0; } ```   ### 迭代器 迭代器是一种遍历接口,一般是为容器类(如链表、队列等。string类也是一种容器)提供统一的遍历接口,使用人员无需关心容器内存分配与管理细节。 C++标准库中容器的迭代器分为四种:**普通迭代器(简称迭代器) iterator**、**只读迭代器 const_iterator**、**反向迭代器 reverse_iterator**、**反向只读迭代器 const_reverse_iterator**。 ```c++ #include #include using namespace std; int main() { string str = "Hello World"; // 普通迭代器, 可用来读写 string::iterator iter = str.begin(); // auto iter = str.begin(); for(; iter != str.end(); iter++) { cout << *iter << "\t"; *iter = 'A' + (iter - str.begin()); } cout << endl << str << endl; // 只读迭代器 string::const_iterator citer = str.cbegin(); for(; citer != str.cend(); citer++) { cout << *citer << "\t"; } cout << endl; // 反向迭代器 string::reverse_iterator riter = str.rbegin(); for(; riter != str.rend(); riter++) { cout << *riter << "\t"; *riter = 'B' + (riter - str.rbegin()); } cout << endl << str << endl; // 反向只读迭代器 string::const_reverse_iterator criter = str.crbegin(); for(; criter != str.crend(); criter ++) { cout << *criter << "\t"; } return 0; } ```   ### 插入 ```c++ #include #include using namespace std; int main() { string str = "Hello World"; string str2 = "STRING2"; // 在指定位置开始重复插入指定次数的单个字符 str.insert(2, 5, 'A'); cout << str << endl; // 在指定位置开始插入字符串 str.insert(5, "This is A New Insert"); cout << str << endl; // 在指定位置插入另一个string str.insert(10, str2); cout << str << endl; // 在指定位置插入另一个string指定范围内的内容(起始位置,字符数) str.insert(0, str2, 2, 2); cout << str << endl; // 在结尾处插入一个字符 str.push_back('R'); cout << str << endl; // 在结尾处追加:可以是单个字符、字符串、string,可以指定追加范围和追加字符数量 str.append("What to do?"); cout << str << endl; return 0; } ```   ### 删除 ```c #include #include using namespace std; int main() { string str = "Hello World"; string str2 = "STRING2"; // 清除全部内容 str2.clear(); cout << str2 << endl; // 删除最后一个字符 str.pop_back(); cout << str << endl; // 删除指定索引与数量的字符 str.erase(2, 2); cout << str << endl; // 删除指定迭代器范围内的字符 str.erase(str.begin() + 1, str.end() - 2); cout << str << endl; return 0; } ```   ### 检测与比较 ```c++ #include #include using namespace std; int main() { string str = "Hello World"; string str2 = "STRING2"; // 是否以指定字符开头 可以是单个字符 cout << str.starts_with("He") << endl; // 检测是否以指定字符串结尾 可以是单个字符 cout << str.ends_with(".jpg") << endl; // 检测是否包含指定字符串 可以是单个字符 cout << str.contains("llo") << endl; // 以字典序与另一个string相比,如果小于另一个返回负数,大于返回正数,内容相同返回0 cout << str.compare(str2) << endl; // 以字典序比较两个string,内容相同返回true,否则返回false bool cmp_result = str == str2; cout << cmp_result << endl; // 三路比较,返回strong_odering strong_ordering order = str <=> str2; // 再将order与0相比,如果order > 0, 则str > str2(字典序) // 如果order < 0, 则 str < str2 // 如果order == 0, 则 str == str2 bool gt = order > 0; bool lt = order < 0; bool eq = order == 0; cout << "gt = " << gt << endl; cout << "lt = " << lt << endl; cout << "eq = " << eq << endl; // 或者用is_gt is_lt is_eq判断 cout << "is_gt " << is_gt(order) << endl; cout << "is_lt " << is_lt(order) << endl; cout << "is_eq " << is_eq(order) << endl; return 0; } ```   ### 替换与子串 ```c++ #include #include using namespace std; int main() { string str = "Hello World"; string str2 = "STRING2"; // 用给定字符串或string替换指定部分 可以用起始位置索引+替换字符个数指定范围,也可以用迭代器起止指定 // 用于替换的字符串或string也可以指东圃 str.replace(2, 10, "NIHAOAHAHAHAH"); cout << str << endl; // 指定位置以后到结束的子串 string str3 = str.substr(5); cout << str3 << endl; // 指定起始位置和字符数量的子串 str3 = str.substr(5, 2); cout << str3 << endl; return 0; } ```   ### 查找 ```c++ #include #include using namespace std; int main() { string str = "Hello World"; string str2 = "STRING2"; // 查找给定的子串(字符串或另一个string),若存在则返回第一个符合的子串开始的位置,不存在则返回-1 (string::npos,转为有符号整型后为-1) cout << str.find("ll") << endl; cout << (int)str.find("WDDS") << endl; // 可以指定开始查找的位置 cout << str.find("Wor", 5) << endl; cout << str.find("Wor", 9) << endl; // 用法同find, 但是从后开始查找,返回倒数第一个符合的子串开始位置,查找不到则返回string::npos cout << str.rfind("o") << endl; // 写不动文档了,find_first_of find_first_not_of find_last_of find_last_not_of大家有兴趣自行试一下 return 0; } ```   ### 其他操作 ```c++ #include #include using namespace std; int main() { string str = "1234ABCD"; // string转为数值 int a1 = stoi(str); cout << a1 << endl; // 数值转为string string str2 = "Hello From number" + to_string(100032); cout << str2 << endl; // 生成字符串hash hash hs1; cout << "hash of str = " << hs1(str) << endl; cout << "hash of str2 = " << hs1(str2) << endl; return 0; } ```   ## string_view 用于解决string在参数传递时内容会复制问题,可以减少开销,提升性能。 ```c++ void fun1(string str) { cout << "fun1 str.data() = " << (uintptr_t)str.data() << endl; } void fun2(string &str) { cout << "fun2 str.data() = " << (uintptr_t)str.data() << endl; } void fun(string_view sv) { cout << "fun string_view data() = " << (uintptr_t)sv.data() << endl; } int main() { char s[] = "西风烈,长空雁叫霜晨月"; cout << "char s[]地址 = " << (uintptr_t)s << endl; string str(s); cout << "str.data()地址 = " << (uintptr_t)str.data() << endl; string str2(str); cout << "str2.data()地址 = " << (uintptr_t)str2.data() << endl; fun1(s); fun1(str); // fun2不能接收char* fun2(str); // fun2(s); string_view sv1(s); string_view sv2(str); cout << "sv1.data()地址 = " << (uintptr_t)sv1.data() << endl; cout << "sv2.data()地址 = " << (uintptr_t)sv2.data() << endl; fun(s); fun(sv1); fun(str); return 0; } ```       # 容器 ## array C++传统C风格的数组实际上只是一个裸数组,携带的信息很少,不像Java中的数组有边界检查以及数组大小等信息,使用的时候需要考虑的方面有很多。 `std::array`是C++ 11中提供的一个封装了因定大小数组容器,可以用来替代传统数组。   [array文档](cppref/zh/zh/cpp/container/array.html)   ### 基本操作 ```c++ #include #include using namespace std; int main() { array arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; array arr2; // 用指定值填充数组 arr.fill(100); // 访问元素 没有边界检查 arr[0] = 111; cout << "arr[0] = " << arr[0] << endl; cout << "arr[-1] = " << arr[-1] << endl; cout << "arr[11] = " << arr[11] << endl; // 访问元素 有边界检查 arr.at(2) = 1234; cout << "arr.at(2) = " << arr.at(2) << endl; // cout << "arr.at(-1) = " << arr.at(-1) << endl; // 会报错,终止程序 // cout << "arr.at(11) = " << arr.at(11) << endl; // 会报错,终止程序 // 访问第一个元素 arr.front() = 147852; cout << "arr.front() = " << arr.front() << endl; // 访问最后一个元素 arr.back() = 999; cout << "arr.back() = " << arr.back() << endl; // 访问底层数组 int *data = arr.data(); for (int i = 0; i < arr.size(); i++) { cout << "data[" << i << "] = " << data[i] << endl; } // 检查是否为空 cout << boolalpha; cout << "arr.empty() = " << arr.empty() << endl; cout << "arr2.empty() = " << arr2.empty() << endl; // 大小 cout << "arr.size() = " << arr.size() << endl; cout << "arr.max_size() = " << arr.max_size() << endl; // for (int i = 0; i < arr.size(); i++) // { // cout << arr[i] << "\t"; // } // C++ 11 之后的range循环 for (auto &a : arr) { cout << a << "\t"; } return 0; } ```   ### 迭代器 遍历 ```c++ #include #include using namespace std; int main() { array arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; array arr2; // 普通迭代器 array::iterator iter = arr.begin(); // C++ 11之后可以用auto // auto iter = arr.begin(); *(iter + 2) = 11111; for(; iter != arr.end(); iter ++) { cout << *iter << "\t"; } // 返回迭代器 array::reverse_iterator riter = arr.rbegin(); for(; riter != arr.rend(); riter++) { cout << *riter << "\t"; } return 0; } ``` 访问指针成员 ```c++ #include #include using namespace std; class Person { private: const char * name; int age; public: int number; Person() : Person("", 0, 0) {} Person(const char *name, int age, int number) : name(name), age(age), number(number) {} void set(const char *name, int age, int number) { this->name = name; this->age = age; this->number = number; } void sayHello() { cout << "Hello EveryBody" << endl; cout << "My name is " << name << endl; cout << "And I'm " << age << " years old." << endl; cout << "My number is " << number << endl; cout << "\n\n" << endl; } }; int main() { // array persons; // persons[0].set("Tom", 18, 112211); // persons[1].set("Jerry", 20, 112212); // persons[2].set("Lee", 22, 112213); array persons = {Person("Tom", 18, 112201), Person("Jerry", 20, 112202), Person("Lee", 22, 112203)}; array::iterator iter = persons.begin(); for(; iter != persons.end(); iter++) { cout << "NUMBER = " << iter->number << endl; iter->sayHello(); } return 0; } ```   ## vector `std::vector`是一个动态数组容器,可以在使用时动态修改数组大小。[std::array文档](cppref/zh/zh/cpp/container/vector.html) ### 构造 ```c++ #include #include using namespace std; int main() { // 无参构造 vector vec1; // 指定初始大小 用对应类型的0值填充 vector vec2(5); // 指定初始大小并用指定的值填充 vector vec3(5, 'A'); // 初始化 vector vec4 = {1, 2, 3, 4, 5, 6, 7}; // 用迭代器初始化 vector vec5(vec4.begin(), vec4.end()); // 复制构造 vector vec6(vec5); // 移动构造 vector vec7(move(vec6)); // 循环 for (auto &v : vec3) { cout << v << "\t"; } return 0; } ``` insert讲解一下内存重新分配的一些知识。 ### 就地构造 当`std::vector`用于存放复合数据类型(结构体、类)时,直接用`insert`和`push_back`时,会多次构造和拷贝对应的对象,因此C++ 11以后容器类加入了`insert`和`push_back`对应的**就地构造(原位构造)**的插入方法`emplace`和`emplace_back`,可以只用构造一次对象,提高性能。 - `emplace_back`参数直接为对应类的构造函数的参数 - `emplace`第一个参数为插入位置,其余参数为对应构造函数的参数 示例: ```c++ #include #include #include using namespace std; class Person { private: string name; int age; int number; public: Person() : name("none"), age(0), number(-1) { cout << "Person无参构造调用" << endl; } Person(const string name, int age, int number) : name(name), age(age), number(number) { cout << "Person三参构造调用" << endl; } Person(const Person &person) : name(person.name), age(person.age), number(person.number) { cout << "Person拷贝构造调用" << endl; } Person(Person &&person) : name(move(person.name)), age(move(person.age)), number(move(person.number)) { cout << "Person移动构造调用" << endl; } Person& operator = (const Person &other) = default; Person& operator = (Person &&other) = default; friend ostream& operator << (ostream& , const Person& ); }; ostream& operator << (ostream& out, const Person& person) { return out << "{" << "name: " << person.name << ", age: " << person.age << ", number: " << person.number << "}"; } int main() { vector vec; vec.reserve(5); vec.push_back(Person("张三", 18, 11212)); Person person("李四", 20, 1122); vec.push_back(person); vec.emplace_back("王五", 22, 20012); vec.emplace(vec.begin(), "赵六", 23, 200145); for(auto &p : vec) { cout << p << endl; } return 0; } ```   ## forward_list `std::forward_list`为单向链表。[std::forward_list文档](cppref/zh/zh/cpp/container/forward_list.html) ### 迭代器示意图 ![](./img/forward_list_iterator.png) **单向链表迭代器只能做自增,不能与数字相加减,也不能两个迭代器相减。**   ### sort && reverse sort是将链表数据进行升序排序,也可以自定义比较函数。reverse将链表元素进行逆序。 ```c++ #include #include using namespace std; class Person { private: string name; int age; int number; public: Person() : name("none"), age(0), number(-1) {} Person(const string name, int age, int number) : name(name), age(age), number(number) {} Person(const Person &person) : name(person.name), age(person.age), number(person.number) {} Person(Person &&person) : name(move(person.name)), age(move(person.age)), number(move(person.number)) {} Person& operator = (const Person &other) = default; Person& operator = (Person &&other) = default; int GetAge() const { return age; } friend ostream& operator << (ostream& , const Person& ); }; ostream& operator << (ostream& out, const Person& person) { return out << "{" << "name: " << person.name << ", age: " << person.age << ", number: " << person.number << "}"; } bool comp(const Person &p1, const Person &p2) { return p1.GetAge() < p2.GetAge(); } int main() { forward_list fls = {5, 6, 2, 3, 1}; // 升序排序 fls.sort(); // 配合reverse可进行降序排序 fls.sort(); fls.reverse(); for (auto &v : fls) { cout << v << "\t\t"; } // 对于复合数据类型可自定义排序函数 比如按年龄排序 forward_list person_list = {{"张三", 22, 2001}, {"李四", 20, 2002}, {"王五", 21, 2003}}; person_list.sort(comp); // 也可以用lambda表达式 // person_list.sort([](const Person &p1, const Person &p2) { return p1.GetAge() > p2.GetAge(); }); for (auto &vv : person_list) { cout << vv << "\t\t"; } return 0; } ```   ### merge && splice_after `merge`将两个单项链表合并为一个。如果是两个已排好序的链表,则合并后按升序排列。如果两个链表无序,也能合并,但目前我还没找着按什么规则合并。 合并后第二个链表会直接变为空。 `splice_after`将另一个链表的指定范围内的元素转移到本列表指定位置之后。第二个链表未指定范围则为全链表内容。 转移后的元素将不会继续在第二个链表中存在。 示例 ```c++ #include #include using namespace std; int main() { forward_list fls = {5, 6, 2, 3, 1}; forward_list fls2 = {0, 4, 17, 12, 15,18}; fls.sort(); fls2.sort(); fls2.merge(fls); for (auto &v : fls2) { cout << v << "\t\t"; } return 0; } ``` ```c++ #include #include using namespace std; int main() { forward_list fls = {5, 6, 2, 3, 1}; forward_list fls2 = {11, 14, 25, 30}; forward_list fls3(fls2); forward_list fls4 = {100, 200, 300, 400}; // 合并全部 fls.splice_after(fls.begin(), fls3); // 合并指定位置之后的 auto iter = fls2.begin(); iter ++; iter ++; fls.splice_after(fls2.begin(), fls2, iter); // 指定范围 auto iter2 = fls4.begin(); iter2 ++; fls.splice_after(fls.begin(), fls4, iter2, fls4.end()); for (auto &vv : fls) { cout << vv << "\t\t"; } return 0; } ```       ### unique && remove && remove_if `remove`移除指定值的元素 `remove_if`移除满足指定条件的元素 示例 ```c++ #include #include using namespace std; // 一个元素返回true时移除对应元素 bool pre(const int &val) { return val > 3; // 移除大于3的元素 } int main() { forward_list fls = {5, 6, 2, 3, 1}; fls.remove(3); fls.remove_if(pre); // 也可以用lambda表达式 fls.remove_if([](const int &val) { return val > 3; }); for (auto &vv : fls) { cout << vv << "\t\t"; } return 0; } ``` `unique`用于移除相邻重复的元素,只保留一个。不相邻的不影响。也可以自定义两个元素是否相等的比较函数来移除。 ```c++ #include #include using namespace std; int main() { forward_list fls = {1, 1, 1, 6, 1, 3, 1}; fls.unique(); fls.unique([](const int &v1, const int &v2) { return v1 == v2; }); // 效果跟上面的一样,可以用这种方式来自定义比较函数 for (auto &vv : fls) { cout << vv << "\t\t"; } return 0; } ``` ## list `std::list`为双向链表。[std::list文档](cppref/zh/zh/cpp/container/list.html) ## ## stack ![](C:\Users\YAN\Desktop\STD\img\stack.png) `std::stack`为栈,是一种后进先出数据结构。[std::stack文档](cppref/zh/zh/cpp/container/stack.html) 示例: ```c++ #include #include #include /* using namespace std; int main() { stack str_stack; // 入栈, 如果是复合数据结构,用emplace就地构造代替push入栈 str_stack.push("粒粒皆辛苦"); str_stack.push("谁知盘中餐"); str_stack.push("汗滴禾下土"); str_stack.push("锄禾日当午"); // 出栈 while(!str_stack.empty()) { string str = str_stack.top(); // 先用top获取到栈顶元素 str_stack.pop(); // 弹出栈顶元素 cout << str << "--已出栈,感觉良好。栈里还有" << str_stack.size() << "个元素" << endl; } return 0; } ```   **课后练习** 提供一个字符串,利用栈,判断其中的括号是否匹配。   ## queue `std::queue`为队列,是一种先进先出数据结构。[std::queue文档](cppref/zh/zh/cpp/container/queue.html) ![](./img/queue.png) 示例 ```c++ #include #include using namespace std; int main() { queue q; // 入队,如果是复合数据类型,用emplace就地构造代替push入队 q.push("张三"); q.push("李四"); q.push("王五"); // 出队 while (!q.empty()) { const char *name = q.front(); // 先获取队首元素 q.pop(); // 将队首元素出队 cout << name << "已出队,感觉良好。队里还有" << q.size() << "个人" << endl; } return 0; } ```   **课后练习** 有余力的同学可以研究一下用队列求解迷宫路径问题。   ## deque `std::deque`是双端队列,即在队列两端都可以进行操作,也可以进行随机下标访问。其操作基本上与`std::vector`一样,比`std::vector`多了在头部进行插入和移除的操作。 一般来说,`std::vector`用在需要频繁进行随机下标访问的场景,如果需要频繁在头部和尾部进行插入和删除操作,则用`std::deque`。 [std::deque文档](cppref/zh/zh/cpp/container/deque.html)   ## priority_queue `std::priority_queue`为优先队列。是一种可以根据优先级的高低确定出队顺序的数据结构。如果是复合数据类型,需要提供比较函数或者重载<运算符。 [std::priority_queue文档](cppref/zh/zh/cpp/container/priority_queue.html) 示例 **自定义数据类型的比较** ```c++ #include #include #include #include using namespace std; class Person { private: int age; string name; public: Person() : Person(0, "") {} Person(const int age, const string name) : age(age), name(name) {} int GetAge() const { return age; } friend ostream& operator << (ostream& out, const Person& person); friend bool operator < (const Person &p1, const Person &p2); }; ostream& operator << (ostream& out, const Person& person) { return out << "{" << "name: " << person.name << ", age: " << person.age << "}"; } bool operator < (const Person &p1, const Person &p2) { return p1.age < p2.age; } int main() { priority_queue q; q.emplace(60, "Tom"); q.emplace(70, "Jerry"); q.emplace(65, "Lee"); // 出队 while (!q.empty()) { auto top = q.top(); // 先获取队首元素 q.pop(); // 将队首元素出队 cout << top << "\t"; } return 0; } ``` **自定义比较器** ```cpp #include #include #include #include using namespace std; class Person { private: int age; string name; public: Person() : Person(0, "") {} Person(const int age, const string name) : age(age), name(name) {} int GetAge() const { return age; } friend ostream& operator << (ostream& out, const Person& person); friend bool operator < (const Person &p1, const Person &p2); }; ostream& operator << (ostream& out, const Person& person) { return out << "{" << "name: " << person.name << ", age: " << person.age << "}"; } bool operator < (const Person &p1, const Person &p2) { return p1.age < p2.age; } // 方式一 模仿less定义比较器 struct Comp { bool operator()(const Person& p1, const Person& p2) const { return p1.GetAge() > p2.GetAge(); } }; // 方式二 定义普通比较函数 bool cmp(const Person& p1, const Person& p2) { return p1.GetAge() < p2.GetAge(); } typedef bool (*Comp2)(const Person& p1, const Person& p2); int main() { // 方式三 通过lambda表达式定义比较函数 auto cmp3 = [](const Person& p1, const Person& p2) { return p1.GetAge() < p2.GetAge(); }; // priority_queue, Comp> q; // priority_queue, bool (*)(const Person& p1, const Person& p2))> q(cmp); // priority_queue, Comp2> q(cmp3); priority_queue, decltype(cmp3)> q(cmp3); q.emplace(60, "Tom"); q.emplace(70, "Jerry"); q.emplace(65, "Lee"); // 出队 while (!q.empty()) { auto top = q.top(); // 先获取队首元素 q.pop(); // 将队首元素出队 cout << top << "\t"; } return 0; } ```   ## set ## multiset ## unordered_set ## unordered_multiset ## map ## multimap ## unordered_map ## unordered_multimap ## span与容器       # 算法       # 迭代器       # 数值操作 ## ## 随机数 ## 位操作 ```cpp ```       # 时间日期 时间点的定义 ```c++ chrono::system_clock::time_point tp = chrono::system_clock::now(); // 等价于 chrono::time_point tp = chrono::system_clock::now(); ``` 时间点类型的转换:将纳秒时间点转为秒时间点 ```c++ chrono::time_point tp2 = chrono::time_point_cast(tp); ```       # 文件系统(filesystem) C++ 17之后正式引入了filesystem用于遍历操作目录。命名空间为`std::filesystem`。使用多个命名空间时,不建议都使用`using namespace ...`的形式,但可以用下面的方式对命名空间进行简化别名: ```c++ namespace fs = std::filesystem; fs::path pth = fs::current_path(); ``` 如此之后则可以用`fs`代替命名空间`std::filesystem`。 [filesystem文档](cppref/zh/zh/cpp/filesystem.html)   示例: 列出当前目录下所有`.cpp`文件 ```c++ #include #include using namespace std; namespace fs = std::filesystem; int main() { fs::directory_iterator diter(fs::current_path()); for (const fs::directory_entry &entry : diter) { if (entry.path().string().ends_with(".cpp")) // ends_with需要C++ 20才支持 { cout << entry.path().string() << endl; } } return 0; } ```       # 正则表达式 ## regex_match ```c++ #include #include #include using namespace std; int main() { // 是否为有效IP地址 regex re("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); string str = "192.168.0.256"; cout << boolalpha << regex_match(str, re) << endl; // 判断是否为有效邮箱地址 regex re2("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"); string str2 = "zhangsa@qq.com"; cout << boolalpha << regex_match(str2, re2) << endl; } ``` ## regex_search `match_results`两种常用特化类型 ```c++ typedef match_results cmatch; typedef match_results smatch; ``` ```c++ #include #include #include using namespace std; int main() { string str = "abc123def456ghi789jkl"; regex re("(\\d)(\\d)(\\d)"); smatch sm; if (regex_search(str, sm, re)) { cout << sm.str() << endl; cout << sm.size() << endl; cout << sm[0] << endl; cout << sm[1] << endl; cout << sm[2] << endl; cout << sm[3] << endl; cout << sm.prefix() << endl; cout << sm.suffix() << endl; cout << sm.position() << endl; } else { cout << "没匹配到" << endl; } while(regex_search(str, sm, re)) { cout << sm.str() << "\t"; str = sm.suffix(); } } ``` ## regex_replace ```c++ #include #include #include using namespace std; int main() { string str = "abc123def456ghi789jkl"; regex re("\\d{3}"); string str2 = regex_replace(str, re, "*"); cout << str2 << endl; str2 = regex_replace(str, re, "($&)"); cout << str2 << endl; } ```       # 多线程 ## mutex ```c++ #include #include #include #include #include using namespace std; int i = 0; mutex mtx; void tf() { while (i < 10) { mtx.lock(); lock_guard lock(mtx); cout << "子线程:" << i << endl; i++; mtx.unlock(); this_thread::sleep_for(chrono::milliseconds(10)); } } int main() { thread th(tf); while (i < 10) { mtx.lock(); cout << "主线程进行中:" << i << endl; i++; mtx.unlock(); this_thread::sleep_for(chrono::milliseconds(10)); } th.join(); return 0; } ``` ## lock_guard ```c++ #include #include #include #include #include using namespace std; int i = 0; mutex mtx; void tf() { while (i < 10) { // mtx.lock(); lock_guard lock(mtx); cout << "子线程:" << i << endl; i++; // mtx.unlock(); this_thread::sleep_for(chrono::milliseconds(10)); } } int main() { thread th(tf); while (i < 10) { // mtx.lock(); lock_guard lock(mtx); cout << "主线程进行中:" << i << endl; i++; // mtx.unlock(); this_thread::sleep_for(chrono::milliseconds(10)); } th.join(); return 0; } ``` ## ## condition_variable 多个线程轮流输出数字 ```c++ #include #include #include #include // 创建互斥量和条件变量来进行线程同步 std::mutex mtx; std::condition_variable cv; int current_number = 1; void print_thread(int thread_id, int thread_count) { for (int i = 0; i < 100; ++i) { // 加锁 std::unique_lock lock(mtx); // 等待条件满足 cv.wait(lock, [&] { return (current_number % thread_count) == thread_id; }); // 输出数字 std::cout << "Thread " << thread_id << ": " << current_number << std::endl; // 增加数字并通知其他线程 ++current_number; cv.notify_all(); } } int main() { const int num_threads = 3; // 定义线程数量 // 创建多个线程 std::vector threads; for (int i = 0; i < num_threads; ++i) { threads.emplace_back(print_thread, i, num_threads); } // 唤醒第一个线程开始输出 cv.notify_all(); // 等待所有线程执行完毕 for (auto& t : threads) { t.join(); } return 0; } ``` 启动三个线程轮流输出ABC ```c++ #include #include #include #include using namespace std; class PrintChar { private: int loop_num; int index; mutex mtx; condition_variable cv; public: PrintChar(int loop_num) : loop_num(loop_num), index(0) {} void A() { for (int i = 0; i < loop_num; i++) { unique_lock ulk(mtx); cv.wait(ulk, [&]() { return index % 3 == 0; }); cout << "A"; this_thread::sleep_for(chrono::milliseconds(100)); index ++; cv.notify_all(); } } void B() { for (int i = 0; i < loop_num; i++) { unique_lock ulk(mtx); cv.wait(ulk, [&]() { return index % 3 == 1; }); cout << "B"; this_thread::sleep_for(chrono::milliseconds(100)); index ++; cv.notify_all(); } } void C() { for (int i = 0; i < loop_num; i++) { unique_lock ulk(mtx); cv.wait(ulk, [&]() { return index % 3 == 2; }); cout << "C"; this_thread::sleep_for(chrono::milliseconds(100)); index ++; cv.notify_all(); } } }; int main() { int num = 0; cout << "请输入循环次数:" ; cin >> num; PrintChar p(num); thread th1(&PrintChar::A, &p); thread th2(&PrintChar::B, &p); thread th3(&PrintChar::C, &p); th1.join(); th2.join(); th3.join(); return 0; } ``` ## async 通过async启动并行计算,对比单线程计算时间 ```c++ #include #include using namespace std; uint64_t parallel_sum(uint64_t begin, uint64_t end) { uint64_t sum = 0; for (uint64_t i = begin; i < end; i++) { sum += i; } return sum; } int main() { auto t1 = chrono::high_resolution_clock::now(); uint64_t sum = 0; for (uint64_t i = 0; i < 10000000; i++) { sum += i; } auto t2 = chrono::high_resolution_clock::now(); auto t = t2 - t1; cout << sum << endl << t.count() << endl; t1 = chrono::high_resolution_clock::now(); auto f1 = async(launch::async, parallel_sum, 0, 2000000); auto f2 = async(launch::async, parallel_sum, 2000000, 4000000); auto f3 = async(launch::async, parallel_sum, 4000000, 6000000); auto f4 = async(launch::async, parallel_sum, 6000000, 8000000); auto f5 = async(launch::async, parallel_sum, 8000000, 10000000); sum = f1.get() + f2.get() + f3.get() + f4.get() + f5.get(); t2 = chrono::high_resolution_clock::now(); t = t2 - t1; cout << sum << endl << t.count() << endl; return 0; } ``` ## promise future ```c++ #include #include #include #include #include using namespace std; void sumfromto(int start, int end, promise ps) { int sum = 0; for (int i = start; i <= end; i++) { sum += i; } ps.set_value(sum); } int main() { promise ps; future sum_future = ps.get_future(); thread th(sumfromto, 1, 100, move(ps)); cout << sum_future.get() << endl; th.join(); } ``` ## 原子操作 ```c++ #include #include #include #include #include #include using namespace std; // int total(0); atomic_int total(0); mutex mtx; void fun() { for (int i = 0; i < 1000000; i++) { // mtx.lock(); total ++; total --; // mtx.unlock(); } } int main() { auto start = chrono::steady_clock::now(); vector vec; for (int i = 0; i < 8; i++) { vec.emplace_back(fun); } for (int i = 0; i < 8; i++) { vec[i].join(); } cout << "total = " << total << endl; auto end = chrono::steady_clock::now(); auto dur = chrono::duration_cast(end - start); cout << dur << endl; return 0; } ```       # 通用工具库       # 语言支持库 ## 三路比较 | 序列类型 | 特点 | 举例 | |:----------------:|:----------------------------------------------------------:|:--------------------------------------------------------------------------------:| | partial_ordering | 1. 等价的值不一定完全相等,a等价于b,可能存在函数F,F(a)不等于F(b)
2. 存在不能比较的值 | 1. 0.0和-0.0虽然 等价,但二进制值不同,如果函数F为取两者的二进制值,则F(0.0)和F(-0.0)不相等
2. 浮点数当中NaN即为不可比较值 | | weak_ordering | 1. 等价的值并不一定完全相等。a等价于b,可能存在函数F,F(a)不等于F(b)
2. 不存在不能比较的值 | 以忽略大小写的方式比较"ABC"和"abc"是等价的,但是这并不是完全相同的两个值 | | strong_ordering | 1. 等价的值完全相等,只要a==b, 就有F(a)==F(b)
2. 不存在不能比较的值 | |                         # 其他