[TOC]
# C++标准库是什么?
C++标准库是一系列类和函数的集合,属于C++标准的一部分,只要支持C++标准的环境中就可以直接使用。内容丰富,功能强大。对初学者来说也是很好的提升C++技能的学习材料。
C++标准库主要包括C++库和C语言库,头文件如下图所示。
C++标准库都定义在命名空间std中。

> 本课程的目的是带领大家了解一下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的特性。

## 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)
### 迭代器示意图

**单向链表迭代器只能做自增,不能与数字相加减,也不能两个迭代器相减。**
### 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

`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)

示例
```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. 不存在不能比较的值 | |
# 其他