C++
时间轴
2025-10-03
init
2025-10-12
add next_permutation 生成序列的全排列
2025-10-19
add string
语法
& 与 &&,左值与右值
-
左值 (Lvalue)
-
有名字,可取地址。
-
可以出现在赋值符号左边。
-
例子:
1
2
3int x = 5; // x 是左值
x = 10; // 可以赋值
int* p = &x; // 可以取地址
-
-
右值 (Rvalue)
-
临时对象或字面量,没有名字,不能取地址。
-
通常出现在赋值符号右边。
-
例子:
1
2int y = x + 2; // x+2 是右值
int z = 42; // 42 是右值
注意:右值可以绑定到 右值引用 (
&&)。 -
-
引用类型
| 引用 | 含义 |
|---|---|
T& |
左值引用(只能绑定到左值) |
T&& |
右值引用(只能绑定到右值) |
示例:
1 | int a = 5; |
- 左值引用可以修改左值:
1 | lref = 10; // a = 10 |
- 右值引用常用于移动语义:
1 | vector<int> v1 = {1,2,3}; |
输入输出
输入
首先,在C++语言中,要使用标准的输入,需要包含头文件< iostream >
cin
cin是C++中, 标准的输入流对象,下面列出cin的两个用法,单独读入,和批量读入,cin的原理,简单来讲,是有一个缓冲区,我们键盘输入的数据,会先存到缓冲区中,用cin可以从缓冲区中读取数据。
注意1:cin可以连续从键盘读入数据
注意2:cin以空格、tab、换行符作为分隔符
注意3:cin从第一个非空格字符开始读取,直到遇到分隔符结束读取
getline()
1 | istream& getline (char* s, streamsize n ); |
从cin的注意中,也可以看出,当我们要求读取的字符串中间存在空格的时候,cin会读取不全整个字符串,这个时候,可以采用getline()函数来解决。
注意1:使用getline()函数的时候,需要包含头文件<string>
注意2:getline()函数会读取一行,读取的字符串包括空格,遇到换行符结束
getchar()
该函数会从缓存区中读出一个字符,经常被用于判断是否换行
输出
同样的,在C++语言中,要使用标准的输出,也需要包含头文件<iostream>
输出这边,主要介绍一个函数,就是用的最多的cout,需要注意的是,如果输出endl对象的时候,会输出一个换行符,类似\n。
sstream
引入头文件
1 |
|
三个类
| 类名 | 作用 | 场景 |
|---|---|---|
stringstream |
读写都支持 | 通用 |
istringstream |
只读输入流 | 解析字符串 |
ostringstream |
只写输出流 | 构造字符串 |
常用操作:
| 函数 | 作用 | 示例 |
|---|---|---|
str() |
获取/设置内部字符串 | ss.str("123 456") |
clear() |
清空流状态 | 必须在重复使用时调用 |
operator>> |
提取数据(按空白分割) | ss >> x; |
operator<< |
插入数据 | ss << 42; |
getline() |
按行读取 | getline(ss, s) |
good() fail() |
检查流状态 | 判断解析是否成功 |
注意重复使用时必须调用clear
举例:
分割字符串
1 |
|
| 用途 | 方法 | 示例 | 说明 | 易错点 |
|---|---|---|---|---|
| 空白分词 | ss >> word |
ss >> w1 >> w2 |
按空格、换行、制表符切分 | 遇到逗号等非空白不会分割 |
| 自定义分隔符 | getline(ss, word, ',') |
getline(ss, word, ','); |
按 , 切分 |
必须用 getline,>> 无法自定义分隔符 |
| 解析数字 | ss >> num |
int x; ss >> x; |
字符串数字 → int/double | 非数字会使流失败,需检查 ss.fail() |
| 拼接字符串 | oss << val |
oss << "ID=" << 5; |
高效构造动态字符串 | 输出结束后 oss.str() 才能获取完整内容 |
STL
vector
引用头文件
1 |
|
构造
1 | // 空 vector |
访问元素
-
返回的是元素的引用
-
带边界检查的访问如果越界会抛出 std::out_of_range 异常
1 | // 通过索引访问(不做边界检查) |
修改元素
1 | v[2] = 10; // 修改索引2的值 |
遍历元素
1 | // 索引遍历 |
时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
v[i] |
O(1) | 随机访问 |
v.at(i) |
O(1) | 随机访问,带边界检查 |
push_back(x) |
O(1) 摊销 | 平均常数时间,偶尔扩容 O(n) |
pop_back() |
O(1) | 删除末尾元素 |
insert(v.begin() + i, x) |
O(n) | 插入到中间,平均移动 n/2 个元素 |
erase(v.begin() + i) |
O(n) | 删除中间元素,平均移动 n/2 个元素 |
front() / back() |
O(1) | 访问首尾元素 |
size() / empty() |
O(1) | 获取大小/是否为空 |
clear() |
O(n) | 清空元素(析构调用) |
sort(v.begin(), v.end()) |
O(n log n) | 使用 STL 算法排序 |
queue
引用头文件
1 |
|
构造
1 | // 空队列 |
访问队头队尾
1 | // 查看队首/队尾元素 |
入队出队
1 | // 入队(尾部插入) |
时间复杂度
| 操作 | 时间复杂度 |
|---|---|
push() |
O(1) |
pop() |
O(1) |
front() |
O(1) |
back() |
O(1) |
empty() |
O(1) |
size() |
O(1) |
priority_queue
-
优先队列,内部默认使用大顶堆
-
出队时总是弹出队列中 最大的元素(默认大顶堆)。
-
可以自定义比较器实现 小顶堆。
priority_queue 没有迭代器,只能利用 pop()和 top()实现遍历
引用头文件
1 |
|
构造
priority_queue 的泛型参数中第一个是存储元素的类型,第二个是存储元素的容器,默认为 vector,第三个是元素的比较器
注意 priority_queue 除了能弹出堆顶外,不支持直接修改或删除堆中某一个元素
- 如果想要删除 priority_queue 中某一个元素,建议考虑能不能配合 unordered_map 实现懒删除(只有无效元素到堆顶了才删除,否则只做标记)
1 | // 默认构造 |
访问堆顶
1 | priority_queue<int> pq; |
弹出堆顶元素
1 | pq.pop(); |
自定义比较器
方法 1:使用 std::greater
1 | priority_queue<int, vector<int>, std::greater<int>> min_pq; |
方法 2:自定义结构体/lambda
1 | struct cmp { |
方法 3:如果是结构体,直接重载<运算符
1 | struct Person { |
时间复杂度
| 操作 | 平均复杂度 |
|---|---|
push |
O(log n) |
pop |
O(log n) |
top |
O(1) |
size |
O(1) |
empty |
O(1) |
stack
栈,先进后出
stack 没有迭代器,不能用 for 循环直接遍历。遍历只能通过 top() + pop()。
引用头文件
1 |
|
构造
1 | stack<int> s1; // 空栈 |
入栈/出栈
1 | stack<int> s; |
访问栈顶元素
1 | // 查看栈顶元素 |
时间复杂度
| 操作 | 平均复杂度 |
|---|---|
push |
O(1) |
pop |
O(1) |
top |
O(1) |
size |
O(1) |
empty |
O(1) |
map, unordered_map
-
map
-
map是 有序关联容器,底层通常用 红黑树 实现。 -
每个元素是 key-value 对,key 唯一,自动按 key 排序。
-
适合需要 按键顺序访问 的场景。
-
-
unordered_map
unordered_map是 哈希表,无序存储 key-value。- 查找、插入和删除平均时间复杂度为 O(1)。
- 适合快速查找,不关心顺序。
GCC 的
unordered_map自 C++11 起有一个优化:- 当某个桶内链表长度超过一定阈值(通常是 8)时,链表会转为红黑树。
- 这样可以防止 最坏情况哈希冲突导致的 O(n) 复杂度,把查找复杂度从 O(n) 降到 O(log n)。
- 也就是:链表 → 红黑树,而不是整个哈希表退化为红黑树。
引用头文件
map
1 | // map |
unordered_map
1 | // unordered_map |
构造
map
1 | // 空 map |
unordered_map
1 | // 空表 |
访问元素
map
1 | map<int, string> m; |
unordered_map
1 | unordered_map<int, string> um; |
插入,修改和元素
map
1 | map<int, string> m; |
unorder_map
1 | unordered_map<int, string> um; |
查找元素
map
1 | map<int, string> m; |
unordered_map
1 | unordered_map<int, string> um; |
删除元素
map
1 | map<int, string> m; |
unordered_map
1 | unordered_map<int, string> um; |
遍历
map
map 是按 key 有序遍历的
1 | map<int, string> m; |
unordered_map
注意:
unordered_map是无序的,遍历顺序不固定。
1 | for (auto &[key, value] : um) { |
自定义比较器(仅 map 支持)
1 | map<int, string, std::greater<int>> m; // 按 key 降序 |
时间复杂度
map
| 操作 | 平均复杂度 |
|---|---|
| 插入/删除 | O(log n) |
| 查找 | O(log n) |
| 遍历 | O(n) |
| 访问元素 | O(log n) |
unordered_map
| 操作 | 平均复杂度 | 最坏复杂度 |
|---|---|---|
| 插入 | O(1) | O(n) |
| 查找 | O(1) | O(n) |
| 删除 | O(1) | O(n) |
| 遍历 | O(n) | O(n) |
set, unordered_set
- set
set是 有序集合,底层通常用 红黑树 实现。- 存储 唯一元素,自动按元素大小排序。
- 适合需要 有序访问和快速查找唯一元素 的场景。
- unordered_set
unordered_set是 哈希表集合,无序存储唯一元素。- 平均查找、插入和删除复杂度为 O(1)。
- 适合快速查找,不关心顺序。
GCC 的
unordered_set同样支持 链表长度超过阈值时桶内树化,避免最坏情况 O(n)。
引用头文件
set
1 |
|
unordered_set
1 |
|
构造
set
1 | set<int> s1; // 空集合 |
unordered_set
1 | unordered_set<int> us1; // 空集合 |
插入和修改
set
1 | set<int> s; |
unordered_set
1 | unordered_set<int> us; |
查找元素
set
1 | set<int> s = {1,2,3}; |
unordered_set
1 | unordered_set<int> us = {1,2,3}; |
删除元素
set
1 | s.erase(2); // 根据元素值删除 |
unordered_set
1 | us.erase(2); |
遍历
set
按元素升序遍历
1 | for (auto &val : s) { |
unordered_set
遍历顺序不固定
1 | for (auto &val : us) { |
自定义比较器(仅 set 支持)
1 | set<int, std::greater<int>> s; // 按降序排列 |
时间复杂度
set
| 操作 | 平均复杂度 |
|---|---|
| 插入/删除 | O(log n) |
| 查找 | O(log n) |
| 遍历 | O(n) |
unordered_set
| 操作 | 平均复杂度 | 最坏复杂度 |
|---|---|---|
| 插入 | O(1) | O(n) |
| 查找 | O(1) | O(n) |
| 删除 | O(1) | O(n) |
| 遍历 | O(n) | O(n) |
string
绝大多数现代 C++ 编译器(如 GCC/Clang/MSVC)中的 std::string 实现类似:
- 内部使用动态数组存储字符
- 结尾自动维护
'\0'(兼容 C-string) - 每个
string还维护size和capacity
字符串扩容通常按照指数方式增长(如 2 倍),以减少反复申请内存。
小字符串优化(SSO - Small String Optimization)
SSO 是 std::string 的核心性能优化技术。
目的:减少堆内存分配,加快小字符串操作
当字符串长度较短(通常 ≤ 15 字符),很多实现会直接存放在对象内部的小缓冲区中,而不分配堆内存。
1 | string s = "hello"; // 很可能不分配堆内存(SSO) |
优点:
- 避免频繁
new/delete - 提高性能
- 减少内存碎片
引用头文件
1 |
|
构造
| 构造方式 | 原型 | 说明 | 示例 |
|---|---|---|---|
| 默认构造 | string() |
创建空字符串 | string s1; // "" |
| 拷贝构造 | string(const string& str) |
拷贝已有字符串 | string s2(s1); |
| 移动构造 | string(string&& str) |
移动已有字符串(C++11 后) | string s3(std::move(s1)); |
| C 字符串 | string(const char* s) |
从以 \0 结尾的 C 字符串构造 |
string s4("hello"); |
| C 字符串 + 长度 | string(const char* s, size_t n) |
只取前 n 个字符 | string s5("hello world", 5); // "hello" |
| 重复字符 | string(size_t n, char c) |
创建 n 个相同字符组成的字符串 | string s6(4, 'a'); // "aaaa" |
| 区间构造 | template<class InputIt> string(InputIt first, InputIt last) |
用迭代器区间初始化 | vector<char> v{'x','y','z'}; string s7(v.begin(), v.end()); |
| initializer_list | string(std::initializer_list<char> ilist) |
用列表初始化字符(C++11) | string s8({'a','b','c'}); // "abc" |
默认构造
1 | string s1; |
- 易错点:空字符串的
c_str()仍然有效,返回""。
拷贝构造
1 | string s2("hello"); |
- 性能建议:大字符串尽量使用
const string&传参,避免拷贝
移动构造(C++11+)
1 | string s4(std::move(s2)); |
- 特性:
s2的内容被移动,原对象为空 - 优势:避免拷贝内存,提高性能
C 字符串构造
1 | string s5("hello"); |
- 使用场景:从字面量或 C 风格数组初始化
- 性能建议:如果长度已知,用第二个参数避免多余扫描
- 易错点:传入非
\0结尾字符串时,必须指明长度
重复字符构造
1 | string s7(5, 'x'); // "xxxxx" |
区间构造(迭代器)
1 | vector<char> v{'a','b','c'}; |
initializer_list 构造(C++11+)
1 | string s9({'x','y','z'}); // "xyz" |
访问与赋值操作
下标访问 operator[]
1 | string s = "hello"; |
-
说明:
-
不进行边界检查,访问越界会导致未定义行为。
-
移动构造或扩容后,指向原字符的指针或引用可能失效。
-
安全访问 at()
1 | char c = s.at(1); // 'e' |
- 说明:
- 会进行范围检查,越界时抛
std::out_of_range异常。
- 会进行范围检查,越界时抛
访问首尾字符
1 | char f = s.front(); // 'H' |
- 说明:
- 当字符串为空时调用
front()或back()是未定义行为。
- 当字符串为空时调用
- 易错点:
- 空字符串访问会触发 UB(未定义行为)。
字符串赋值 assign()
assign() 是 std::string 赋值的多用途函数,重载丰富:
| 原型 | 说明 | 示例 |
|---|---|---|
string& assign(const string& str) |
拷贝另一个字符串 | s.assign(s2); |
string& assign(string&& str) |
移动另一个字符串 | s.assign(std::move(s2)); |
string& assign(const string& str, size_t pos, size_t count) |
从 str 的子串赋值 | s.assign(s2, 1, 3); // 取 s2[1..3] |
string& assign(const char* s) |
C 字符串赋值 | s.assign("world"); |
string& assign(const char* s, size_t n) |
前 n 个字符赋值 | s.assign("hello world", 5); // "hello" |
string& assign(size_t n, char c) |
重复字符赋值 | s.assign(4, 'x'); // "xxxx" |
template<class InputIt> string& assign(InputIt first, InputIt last) |
区间赋值 | vector<char> v{'a','b'}; s.assign(v.begin(), v.end()); |
string& assign(initializer_list<char> il) |
列表赋值(C++11+) | s.assign({'x','y','z'}); // "xyz" |
修改操作
追加append
| 原型 | 说明 | 示例 |
|---|---|---|
string& append(const string& str) |
追加整个字符串 | s.append(s2); |
string& append(const string& str, size_t pos, size_t count) |
追加 str 的子串 | s.append(s2, 1, 3); |
string& append(const char* s) |
追加 C 字符串 | s.append("world"); |
string& append(const char* s, size_t n) |
追加 C 字符串前 n 个字符 | s.append("hello world", 5); // "hello" |
string& append(size_t n, char c) |
追加 n 个相同字符 | s.append(3, '!'); // "!!!" |
template<class InputIt> string& append(InputIt first, InputIt last) |
追加区间 | vector<char> v{'a','b'}; s.append(v.begin(), v.end()); |
string& push_back(char c) |
追加单个字符 | s.push_back('x'); |
性能建议
优化拼接操作 —— 默认拼接效率低
1 | string s; |
- 频繁修改时建议用
std::ostringstream或std::string_view
插入insert
| 原型 | 说明 | 示例 |
|---|---|---|
string& insert(size_t pos, const string& str) |
在 pos 插入整个字符串 | s.insert(2, s2); |
string& insert(size_t pos, const string& str, size_t subpos, size_t count) |
插入 str 子串 | s.insert(1, s2, 0, 2); |
string& insert(size_t pos, const char* s) |
插入 C 字符串 | s.insert(0, "Hi"); |
string& insert(size_t pos, const char* s, size_t n) |
插入 C 字符串前 n 个字符 | s.insert(0, "Hello World", 5); |
string& insert(size_t pos, size_t n, char c) |
插入 n 个相同字符 | s.insert(3, 4, '*'); |
iterator insert(const_iterator p, char c) |
插入单字符 | s.insert(s.begin()+1, 'x'); |
template<class InputIt> void insert(const_iterator p, InputIt first, InputIt last) |
插入区间 | s.insert(s.begin(), v.begin(), v.end()); |
删除erase
| 原型 | 说明 | 示例 |
|---|---|---|
string& erase(size_t pos = 0, size_t count = npos) |
从 pos 开始删除 count 个字符 | s.erase(2,3); |
iterator erase(const_iterator p) |
删除迭代器指向字符 | s.erase(s.begin()+1); |
iterator erase(const_iterator first, const_iterator last) |
删除区间 | s.erase(s.begin(), s.begin()+3); |
替换replace
| 原型 | 说明 | 示例 |
|---|---|---|
string& replace(size_t pos, size_t count, const string& str) |
替换 pos 起 count 个字符为 str | s.replace(0,2,"Hi"); |
string& replace(size_t pos, size_t count, const string& str, size_t subpos, size_t subcount) |
替换为 str 子串 | s.replace(0,2,s2,1,2); |
string& replace(size_t pos, size_t count, const char* s) |
替换为 C 字符串 | s.replace(0,2,"OK"); |
string& replace(size_t pos, size_t count, const char* s, size_t n) |
替换为 C 字符串前 n 个字符 | s.replace(0,2,"Hello World",5); |
string& replace(size_t pos, size_t count, size_t n, char c) |
替换为 n 个相同字符 | s.replace(0,2,3,'*'); |
iterator replace(const_iterator first, const_iterator last, InputIt first2, InputIt last2) |
替换迭代器区间 | s.replace(s.begin(), s.begin()+2,v.begin(),v.end()); |
查找操作
| 函数原型 | 说明 | 示例 |
|---|---|---|
size_t find(const string& str, size_t pos=0) const |
从 pos 开始查找子串首次出现位置 | s.find("world"); // 6 |
size_t find(const char* s, size_t pos=0) const |
从 pos 开始查找 C 字符串 | s.find("lo"); // 3 |
size_t find(const char* s, size_t pos, size_t n) const |
查找 C 字符串前 n 个字符 | s.find("hello world", 0, 5); // 查找 "hello" |
size_t find(char c, size_t pos=0) const |
查找字符首次出现位置 | s.find('o'); // 4 |
size_t rfind(const string& str, size_t pos=npos) const |
从右向左查找子串 | s.rfind("lo"); |
size_t rfind(char c, size_t pos=npos) const |
从右向左查找字符 | s.rfind('o'); |
size_t find_first_of(const string& chars, size_t pos=0) const |
查找任意指定字符集合首次出现 | s.find_first_of("aeiou"); |
size_t find_last_of(const string& chars, size_t pos=npos) const |
查找任意字符集合最后一次出现 | s.find_last_of("aeiou"); |
size_t find_first_not_of(const string& chars, size_t pos=0) const |
查找第一个不属于字符集合的位置 | s.find_first_not_of("aeiou"); |
size_t find_last_not_of(const string& chars, size_t pos=npos) const |
查找最后一个不属于字符集合的位置 | s.find_last_not_of("aeiou"); |
字符串比较
| 函数原型 | 说明 | 示例 | 性能 |
|---|---|---|---|
int compare(const string& str) const |
比较整个字符串与 str | s.compare("hello"); |
O(min(n, m)) |
int compare(size_t pos, size_t count, const string& str) const |
比较子串 [pos, pos+count) 与 str |
s.compare(0,2,"he"); |
O(count) |
int compare(size_t pos, size_t count, const string& str, size_t subpos, size_t subcount) const |
比较 s 子串与 str 子串 | s.compare(0,2,s2,1,2); |
O(subcount) |
int compare(const char* s) const |
与 C 字符串比较 | s.compare("hello"); |
O(n) |
int compare(size_t pos, size_t count, const char* s) const |
比较子串与 C 字符串 | s.compare(0,2,"he"); |
O(count) |
int compare(size_t pos, size_t count, const char* s, size_t n) const |
比较子串与 C 字符串前 n 个字符 | s.compare(0,2,"hello",2); // "he" |
O(n) |
返回值:
-
返回值小于0 s<str,
-
返回值等于0 s==str,
-
返回值大于0 s>str
也可以用运算符比较
| 运算符 | 功能 | 示例 | 返回类型 | 比较方式 |
|---|---|---|---|---|
== |
判断是否相等 | s == "hello" |
bool |
大小写敏感 & 按字符逐一比较 |
!= |
判断是否不相等 | s != t |
bool |
同上 |
< |
按字典序小于 | "abc" < "abd" |
bool |
按 ASCII/Unicode 比较 |
<= |
小于等于 | "abc" <= "abc" |
bool |
同上 |
> |
按字典序大于 | "dog" > "cat" |
bool |
同上 |
>= |
大于等于 | "hi" >= "ha" |
bool |
同上 |
子串
| 函数原型 | 说明 | 示例 | 返回值 | 注意 | 性能 |
|---|---|---|---|---|---|
string substr(size_t pos = 0, size_t count = npos) const |
从位置 pos 开始,截取长度为 count 的子串 |
s.substr(2, 4) |
返回新 string | pos > size() 抛异常 |
会产生新字符串拷贝,注意性能 |
字符检查与大小写转换
需要引入c标准库
1 |
常用字符判断函数
| 函数 | 作用 | 示例 |
|---|---|---|
isalnum(c) |
是否为字母或数字 | 'A','z','3' |
isalpha(c) |
是否为字母 | 'A','b' |
isdigit(c) |
是否为数字 | '0'~'9' |
islower(c) |
是否为小写字母 | 'a' |
isupper(c) |
是否为大写字母 | 'Z' |
isspace(c) |
是否为空白字符(空格/换行/制表) | ' ','\n','\t' |
ispunct(c) |
是否为标点符号 | ',' '.' '!' |
isxdigit(c) |
是否为十六进制数字 | '0'~'9','a'~'f' |
isprint(c) |
是否为可打印字符 | 普通字符 |
iscntrl(c) |
是否控制字符 | '\n' '\r' |
大小写转换函数
| 函数 | 说明 | 示例 |
|---|---|---|
tolower(c) |
字符转小写 | tolower('A') → 'a' |
toupper(c) |
字符转大写 | toupper('b') → 'B' |
这两个函数只作用于单个字符,要处理字符串需要配合 transform():
1 |
|
::tolower里的::指的是全局作用域运算符,表示用 全局的 tolower 函数,避免与某些类成员同名造成歧义。
字符串与数字的转换
字符串 → 整型转换函数表
| 函数 | 函数原型 | 参数说明 | 返回值说明 | 示例 |
|---|---|---|---|---|
stoi |
int stoi(const string& str, size_t* pos = 0, int base = 10); |
str: 输入字符串 pos: 返回成功转换的字符数 base: 进制(2~36) |
转换后的 int |
stoi("123") → 123 stoi("1A", nullptr, 16) → 26 |
stol |
long stol(const string& str, size_t* pos = 0, int base = 10); |
同上 | 转换为 long |
stol("99999") → 99999L |
stoll |
long long stoll(const string& str, size_t* pos = 0, int base = 10); |
同上 | 转换为 long long |
stoll("1234567890123") |
stoi (C字符串) |
int stoi(const char* str, size_t* pos = 0, int base = 10); |
str: C风格字符串 |
转换为 int |
stoi("42") → 42 |
字符串 → 浮点型转换函数表
| 函数 | 函数原型 | 参数说明 | 返回值说明 | 示例 |
|---|---|---|---|---|
stof |
float stof(const string& str, size_t* pos = 0); |
str: 字符串 pos: 返回解析字符长度 |
转为 float |
stof("3.14") → 3.14f |
stod |
double stod(const string& str, size_t* pos = 0); |
同上 | 转为 double |
stod("2.71828") |
stold |
long double stold(const string& str, size_t* pos = 0); |
同上 | 转为 long double |
stold("1.6180339887") |
数字 → 字符串转换 (to_string)
| 函数 | 函数原型 | 参数说明 | 返回值说明 | 示例 |
|---|---|---|---|---|
to_string |
string to_string(int value); |
value: 数字 |
返回转换后的字符串 | to_string(42) → "42" |
to_string |
string to_string(double value); |
同上 | 浮点转字符串 | to_string(3.14) → "3.140000" |
to_string |
string to_string(long long value); |
同上 | 支持长整型 | to_string(1234567890123) |
返回值与异常总结
| 情况 | 行为 |
|---|---|
| 输入不是数字 | 抛出 std::invalid_argument |
| 数字超出范围 | 抛出 std::out_of_range |
| 自动去除前导空格 | ✅ 支持 " 123" |
| 支持符号 | ✅ "-42" " +88" |
| 支持部分解析 | ✅ "123abc" → 123 |
字符串与流
引入头文件
1 |
|
三个类
| 类名 | 作用 | 场景 |
|---|---|---|
stringstream |
读写都支持 | 通用 |
istringstream |
只读输入流 | 解析字符串 |
ostringstream |
只写输出流 | 构造字符串 |
常用操作:
| 函数 | 作用 | 示例 |
|---|---|---|
str() |
获取/设置内部字符串 | ss.str("123 456") |
clear() |
清空流状态 | 必须在重复使用时调用 |
operator>> |
提取数据(按空白分割) | ss >> x; |
operator<< |
插入数据 | ss << 42; |
getline() |
按行读取 | getline(ss, s) |
good() fail() |
检查流状态 | 判断解析是否成功 |
注意重复使用时必须调用clear
举例:
分割字符串
1 |
|
| 用途 | 方法 | 示例 | 说明 | 易错点 |
|---|---|---|---|---|
| 空白分词 | ss >> word |
ss >> w1 >> w2 |
按空格、换行、制表符切分 | 遇到逗号等非空白不会分割 |
| 自定义分隔符 | getline(ss, word, ',') |
getline(ss, word, ','); |
按 , 切分 |
必须用 getline,>> 无法自定义分隔符 |
| 解析数字 | ss >> num |
int x; ss >> x; |
字符串数字 → int/double | 非数字会使流失败,需检查 ss.fail() |
| 拼接字符串 | oss << val |
oss << "ID=" << 5; |
高效构造动态字符串 | 输出结束后 oss.str() 才能获取完整内容 |
multiset/multimap
multiset
- 元素 可重复,自动按 key 排序
- 底层通常使用 红黑树
- 不支持通过下标访问,只能用迭代器
- 适合需要 快速查找和按顺序遍历重复元素
multimap<K,V>
- key 可重复,按 key 排序
- 底层通常使用 红黑树
- 可以通过
equal_range查找某个 key 的所有元素
引用头文件
1 | // multiset |
构造
multiset
1 | multiset<int> ms; // 空 multiset |
multimap
1 | multimap<int,string> mm; // 空 multimap |
插入元素
1 | // multiset |
查找元素
1 | // multiset |
删除元素
1 | // multiset |
遍历
1 | // multiset |
时间复杂度
| 操作 | multiset / multimap |
|---|---|
| 插入 | O(log n) |
| 查找 | O(log n) |
| 删除 | O(log n) |
| 遍历 | O(n) |
tuple
引用头文件
1 |
|
构造
1 |
|
访问元素
- 不能像 pair 一样访问,使用 std::get<0>(tuple)
注意,这里 get
要求 index 是常量,编译期可知的常量,因此不能利用它来遍历
1 | int a = std::get<0>(tp); |
- c++17 起可以用结构化绑定,这样更优雅些
1 | auto [a, b, c] = tuple; |
- 也可以使用 std::tie
1 | int a, b, c; |
修改
1 | std::get<1>(t) = 6; |
获取长度
1 | // tuple 长度 |
遍历
不常用,使用结构化绑定 + fold 表达式
1 | auto tp = make_tuple(1, 2.5, "hi"); |
algorithm
引用头文件
1 |
sort
基本用法
1 |
|
自定义比较器
1 | struct Person { |
std::sort 的标准要求是 稳定性不保证,如果需要稳定排序请使用 std::stable_sort
- 稳定排序:如果两个元素相等,排序前它们的相对顺序在排序后保持不变。
- 不稳定排序:相等元素的相对顺序在排序后可能被打乱。
min, max
基本用法
1 | int a = 5, b = 10; |
自定义比较器
1 | struct Person { |
可接受 initializer_list (C++11 及以上)
1 | int x = std::min({3, 1, 4, 2}); // x = 1 |
min_element/max_element
min_element 和 max_element 返回的迭代器
1 | vector<int> v= {1,2,4,8}; |
reverse
反转有序容器
1 | vector<int> v = {1,2,4,8}; |
find
- 顺序查找区间内第一个等于给定值的元素,返回迭代器。找不到返回
end。
1 | vector<int> v = {1, 3, 5, 7}; |
count
- 统计区间内某个值出现的次数
1 | vector<int> v = {1,2,2,3,2}; |
transform
1 | // 单参数变换(Unary Operation) |
例子:
1 | std::transform(s.begin(), s.end(), s.begin(), ::tolower); |
使用的是 单参数版本(Unary Operation),意思是:
- 输入区间:
[s.begin(), s.end()) - 输出区间起始:
s.begin()(原地修改字符串) - 对每个字符应用操作:
::tolower
::tolower里的::指的是全局作用域运算符,表示用 全局的 tolower 函数,避免与某些类成员同名造成歧义。
也可以不使用 tolower,传你自己的处理逻辑:
1 | transform(s.begin(), s.end(), s.begin(), [](char c) { |
双参数变换
两个数组元素相加
1 |
|
binary_search
- 对已排序序列执行二分查找,返回
bool,判断是否存在
1 | vector<int> v = {1,3,5,7}; |
lower_bound/upper_bound
- 返回迭代器,要求序列已排序
lower_bound(begin,end,val):返回第一个 >= val 的位置upper_bound(begin,end,val):返回第一个 > val 的位置
1 | vector<int> v = {1,2,2,3,5}; |
equal_range
- 返回
[lower_bound, upper_bound)的迭代器对,区间包含所有等于给定值的元素
1 | auto range = std::equal_range(v.begin(), v.end(), 2); |
next_permutation/prev_permutation
生成下一个字典序排列,可以用于生成一个序列的全排列
1 |
|
输出:
1 | 1 2 3 |
- 也用于生成一个数组的所有长度为 m 的数字组合
1 |
|
输出
1 | 1 2 |
也可以使用 prev_permutation
1 |
|
fill
fill(begin,end,val):填充整个区间为指定值
1 | vector<int> v(5); |
iota
iota(begin,end,start):生成连续整数序列
1 |
|
copy / swap / replace / remove / remove_if
copy(begin,end,out_it):拷贝区间到另一容器
1 | vector<int> src = {1,2,3}; |
swap(a,b)/iter_swap(it1,it2):交换元素
1 | std::swap(a,b); |
replace(begin,end,old_val,new_val):替换区间内元素
1 | std::replace(v.begin(), v.end(), 2, 5); |
remove(begin,end,val)/remove_if(begin,end,pred):逻辑移除(需要配合erase)
1 | v.erase(std::remove(v.begin(), v.end(), 5), v.end()); |
set operations(集合操作)
- 序列必须 已排序
merge(a,b,out):合并两个已排序序列set_union(a,b,out):并集set_intersection(a,b,out):交集set_difference(a,b,out):差集unique(begin,end):去除相邻重复元素
1 | vector<int> a = {1,2,2,3}; |
使用std::unique必须要求元素是已经有序的
accumulate / all_of / any_of / none_of / for_each
accumulate(begin,end,init):区间求和(或自定义操作)
1 |
|
all_of(begin,end,pred)/any_of/none_of:判断区间元素是否满足条件
1 | bool all_even = std::all_of(v.begin(), v.end(), [](int x){ return x%2==0; }); |
for_each(begin,end,f):区间遍历
1 | std::for_each(v.begin(), v.end(), [](int &x){ x++; }); |
时间复杂度
| 算法 | 功能 | 时间复杂度 | 示例代码 |
|---|---|---|---|
| sort | 排序(不稳定) | O(n log n) |
std::sort(v.begin(), v.end()); |
| stable_sort | 稳定排序 | O(n log² n) 最坏,平均 O(n log n) |
std::stable_sort(v.begin(), v.end()); |
| min / max | 两数取最小/最大 | O(1) |
std::min(a,b); std::max(a,b); |
| min_element / max_element | 区间最小/最大元素迭代器 | O(n) |
*std::min_element(v.begin(), v.end()); |
| reverse | 反转区间 | O(n) |
std::reverse(v.begin(), v.end()); |
| find | 顺序查找 | O(n) |
auto it = std::find(v.begin(),v.end(),5); |
| count | 统计出现次数 | O(n) |
std::count(v.begin(),v.end(),2); |
| binary_search | 是否存在(二分,需有序) | O(log n) |
std::binary_search(v.begin(),v.end(),5); |
| lower_bound | 第一个 >= val(二分) | O(log n) |
auto it=std::lower_bound(v.begin(),v.end(),2); |
| upper_bound | 第一个 > val(二分) | O(log n) |
auto it=std::upper_bound(v.begin(),v.end(),2); |
| equal_range | 返回等于 val 的区间 | O(log n) |
auto r=std::equal_range(v.begin(),v.end(),2); |
| fill | 区间赋值 | O(n) |
std::fill(v.begin(), v.end(), 7); |
| iota | 连续赋值(需 <numeric>) |
O(n) |
std::iota(v.begin(),v.end(),10); |
| copy | 拷贝区间 | O(n) |
std::copy(src.begin(),src.end(),dst.begin()); |
| swap / iter_swap | 交换元素 | O(1) |
std::swap(a,b); |
| replace | 替换指定值 | O(n) |
std::replace(v.begin(),v.end(),2,5); |
| remove / remove_if | 逻辑删除(需 erase) |
O(n) |
v.erase(std::remove(v.begin(),v.end(),5),v.end()); |
| unique | 去除相邻重复(逻辑删除) | O(n) |
v.erase(std::unique(v.begin(),v.end()),v.end()); |
| set_union | 并集(需有序) | O(n+m) |
std::set_union(a.begin(),a.end(),b.begin(),b.end(),back_inserter(out)); |
| set_intersection | 交集(需有序) | O(n+m) |
std::set_intersection(...); |
| set_difference | 差集(需有序) | O(n+m) |
std::set_difference(...); |
| merge | 合并两个有序序列 | O(n+m) |
std::merge(a.begin(),a.end(),b.begin(),b.end(),out.begin()); |
| accumulate | 区间累加(需 <numeric>) |
O(n) |
int sum=std::accumulate(v.begin(),v.end(),0); |
| all_of / any_of / none_of | 区间条件判断 | O(n) |
std::all_of(v.begin(),v.end(),[](int x){return x%2==0;}); |
| for_each | 遍历执行函数 | O(n) |
std::for_each(v.begin(),v.end(),[](int &x){x++;}); |
utility
引用头文件
1 |
pair
引用头文件
1 |
|
构造
1 | pair<int,int> p = {1,2}; |
访问元素
1 | int a = p.first; |
std::move
std::move 其实是将参数 t 转换为一个右值引用类型,不会拷贝内存,不会释放资源,不会调用构造函数和析构函数
1 | template <typename T> |
以 std::vector
1 | std::vector<int> a = {1, 2, 3}; |
这里:std::move(a) 把 a 变成右值引用。编译器会选择 vector 的移动构造函数,而不是拷贝构造。移动构造函数做的事:
- 把 a 的内部指针直接“偷走”,交给 b。
- 把 a 的指针设为空,避免析构时释放同一块内存。
所以最后:b 持有 {1,2,3} 的数据。a 变成空的(size()==0,但仍然是合法对象)
总结:
nums1 = std::move(vec);
- 旧内容自动释放(移动赋值内部完成)
- 新内容接管
- vec 变空不需要自己手动调用析构函数。