时间轴
时间轴
2025-10-03
init
2025-10-12
add next_permutation 生成序列的全排列
2025-10-19
add string
本文介绍了C++编程语言的基础语法与标准模板库(STL)的核心组件。文章详细讨论了左值与右值及引用类型的概念、标准输入输出流与sstream字符串流的操作方法,并总结了vector、queue及priority_queue等常用容器的使用方式与时间复杂度。
语法
& 与 &&,左值与右值
左值 (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++语言中,要使用标准的输入,需要包含头文件
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 | #include <queue> |
构造
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; |
unordered_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 | #include <set> |
unordered_set
1 | #include <unordered_set> |
构造
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里的::指的是全局作用域运算符,表示使用<cctype>中定义的全局tolower函数,避免与<locale>中的std::tolower(接受 locale 参数的重载版本)混淆。
字符串与数字的转换
字符串 → 整型转换函数表
| 函数 | 函数原型 | 参数说明 | 返回值说明 | 示例 |
|---|---|---|---|---|
stoi | int stoi(const string& str, size_t* pos = 0, int base = 10); | str: 输入字符串pos: 返回成功转换的字符数base: 进制(2~36) | 转换后的int | stoi("123") → 123stoi("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里的::指的是全局作用域运算符,表示使用<cctype>中定义的全局tolower函数,避免与<locale>中的std::tolower(接受 locale 参数的重载版本)混淆。
也可以不使用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
生成下一个字典序排列。若序列初始为字典序最小,循环调用直到返回 false 即可生成全排列
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,但仍然是合法对象)
总结:以b = std::move(a)为例:
- a 的旧内容自动释放(移动构造内部完成)
- b 接管 a 的内部指针
- a 变为空(size()==0,但仍是合法对象)
- 不需要自己手动调用析构函数。
