目录
1.string构造方式
变量 构造方式 运行结果 说明 s1默认构造 空字符串 创建一个长度为 0 的空字符串 s2字符串字面量构造 "hello world"用 C 风格字符串初始化 string s3拷贝构造 "hello world"复制 s2的全部内容s4子串构造 "ello w"从 s2的下标 1 开始,取 6 个字符s5子串构造 "ello world"从 s2的下标 1 开始,取 60 个字符(超出实际长度,自动取到末尾)s6子串构造 "ello world"从 s2的下标 1 开始,取到字符串末尾s7取前 n 个字符 "hello"从 str中取前 5 个字符s8n 个字符构造 100 个 #生成由 100 个 #组成的字符串
2.迭代器
1.类型介绍
迭代器类型 作用 获取方式 能否修改元素 iterator正向读写迭代器 s.begin()/s.end()✅ 可以 const_iterator正向只读迭代器 s.cbegin()/s.cend()❌ 不可以 reverse_iterator反向读写迭代器 s.rbegin()/s.rend()✅ 可以 const_reverse_iterator反向只读迭代器 s.crbegin()/s.crend()❌ 不可以 1. 反向迭代器(倒序遍历)
string s = "hello"; // 倒序遍历 for (auto it = s.rbegin(); it != s.rend(); ++it) { cout << *it; } // 输出:olleh2. 只读迭代器(const_iterator)
// 场景1:普通字符串只读遍历(推荐使用 cbegin()/cend()) string s = "hello"; for (auto it = s.cbegin(); it != s.cend(); ++it) { cout << *it; // *it = 'x'; // 报错!const_iterator 不允许修改元素 } // 场景2:const 字符串必须使用 const_iterator const string str = "world"; // string::iterator it = str.begin(); // 报错!const 字符串无法获取可写迭代器 string::const_iterator cit = str.begin(); cout << *cit << endl; // 输出 'w'2.关键边界说明
begin():指向字符串的第一个字符
end():指向字符串最后一个字符的下一个位置(不指向有效元素,仅作为结束标志)
rbegin():指向字符串的最后一个字符(反向遍历的起点)
rend():指向字符串第一个字符的前一个位置(反向遍历的结束标志#include <iostream>
#include <string>
using namespace std;int main() {
string s = "hello world";
// 写法1:显式迭代器遍历 string::iterator it; for (it = s.begin(); it != s.end(); ++it) { cout << *it; // *it 解引用,获取当前迭代器指向的字符 } cout << endl; // 写法2:auto 简化(C++11 及以上推荐) for (auto it = s.begin(); it != s.end(); ++it) { cout << *it << " "; } cout << endl; return 0;}
iterator支持读写,你可以通过*it修改指向的字符
string s = "hello"; string::iterator it = s.begin(); *it = 'H'; // 修改第一个字符为 'H' *(it + 1) = 'E'; // 修改第二个字符为 'E' cout << s << endl; // 输出:HEllo
3.迭代器的实际应用
#include <iostream> #include <string> #include <algorithm> // 包含 STL 算法,如 reverse using namespace std; int main() { string s = "hello, iterator!"; // 1. 正向遍历并修改所有小写字母为大写 cout << "原字符串:" << s << endl; for (auto it = s.begin(); it != s.end(); ++it) { if (islower(*it)) { *it = toupper(*it); } } cout << "转大写后:" << s << endl; // 2. 用反向迭代器倒序输出 cout << "倒序输出:"; for (auto it = s.rbegin(); it != s.rend(); ++it) { cout << *it; } cout << endl; // 3. 用 const_iterator 只读遍历 const string const_s = "read-only string"; cout << "只读遍历:"; for (auto it = const_s.cbegin(); it != const_s.cend(); ++it) { cout << *it; } cout << endl; // 4. 配合 STL 算法:用迭代器反转字符串 reverse(s.begin(), s.end()); cout << "算法反转后:" << s << endl; return 0; }
方式 优点 缺点 适用场景 迭代器 通用(所有容器都能用)、支持算法库、可反向遍历 写法略复杂 跨容器通用代码、需要反向 / 随机访问、配合 STL 算法 下标 [] 写法简单、直观 仅适用于 string/vector等支持随机访问的容器简单遍历、已知索引的场景 范围 for 循环(C++11+) 写法极简、自动处理边界 无法获取当前位置的迭代器,不能修改容器结构 纯遍历 / 只读遍历、不需要修改容器的场景
3. reserve
1. 作用
- 当
new_cap > capacity()→ 重新开辟更大内存,把旧字符拷贝过去,容量变大。- 当
new_cap <= capacity()→ 什么都不做(不会缩容)。- 全程不改变 size,不增加 / 删除字符,不初始化空位置。
2. 意义
1. 避免频繁扩容,提升效率
string 默认追加字符(+= /push_back)时:
空间不够 → 自动扩容 → 重新申请内存 + 拷贝数据 → 效率低。
如果你提前知道要存大量字符:
string s; s.reserve(1000); // 一次性开好空间 // 后续大量 += 不会反复扩容2. 减少内存碎片
提前开好固定容量,避免多次内存申请释放。
3. 配合迭代器(重点)
reserve 扩容会导致 所有迭代器全部失效因为底层内存地址整体更换。
4. reserve VS resize(必考区别)
函数 作用 改 size 改 capacity 影响内容 reserve(n) 预留容量 ❌ ✅ 完全不变 resize(n) 修改有效长度 ✅ ✅ 增删字符、补默认值 5. 特殊用法:手动缩容(shrink)
注意:不太靠谱,不同编译器,效果不同。
// 把容量压缩到刚好等于 size,释放多余空间 s1.reserve(s1.size());
4. shrink_to_fit()
作用:强制把 string 的容量(capacity)缩小到 和 长度(size)完全一样。
注意:reserve和shrink_to_fit都是扩大缩小到(>=)size的值,因为编译器处理不同,可能会存在内存对齐。
#include <iostream> #include <string> using namespace std; int main() { string s("1231241"); cout << "初始状态:\n"; cout << "size = " << s.size() << endl; // 7 cout << "capacity = " << s.capacity() << endl; s.reserve(100); // 强行开大容量 cout << "\nreserve(100) 后:\n"; cout << "size = " << s.size() << endl; // 不变 7 cout << "capacity = " << s.capacity() << endl; // 100+ s.shrink_to_fit(); // 缩容! cout << "\nshrink_to_fit() 后:\n"; cout << "size = " << s.size() << endl; // 7 cout << "capacity = " << s.capacity() << endl; // 7+ return 0;
5. resize
1. 函数原型
void resize(size_t n); void resize(size_t n, char c);2. 三种执行情况
设原:size=oldLen
场景 条件 size 变化 capacity 变化 字符串内容变化 拉长 n>oldLen ✅ 变大为 n 空间不足则扩容 末尾补充:• resize(n):补\0•resize(n,c):补字符c截短 n<oldLen ✅ 缩小为 n ❌ 不变 截断尾部,只保留前 n 个字符 不变 n=oldLen ❌ 无变化 ❌ 无变化 内容完全不变
操作 执行后内容 size 核心行为 s.resize(3)"123"3 截断尾部 s.resize(10,'#')"1231241###"10 末尾补 #s.resize(10)"1231241\0\0\0"10 末尾补空字符
6. at与[]
1. 区分对照
特性 s.at(pos)s[pos]越界检查 ✅ 有 ❌ 没有 越界行为 抛出异常 未定义行为(崩溃 / 乱码) 效率 略低(检查耗时) 更高(无检查) 安全性 高 低 使用场景 需要安全访问、不确定下标是否合法 确定下标合法、追求效率 2. 作用
代码 结果 说明 s.at(0)'1'访问第 0 个字符 s.at(3)'1'访问第 3 个字符 s.at(6)'1'访问最后一个字符 s.at(10)抛异常 越界,程序不会崩,能捕获 s.at(2) = 'A's → "12A1241"可修改字符 3. 特殊作用
// 越界测试 try { cout << s.at(100) << endl; } catch (const out_of_range& e) { cout << "越界啦!" << endl; }
7. 添加类函数
包含:
push_back/+=/append/insert1. push_back
功能
只能在字符串末尾添加 单个字符
特点
只能传
char,不能传字符串末尾追加
空间不足自动扩容
void push_back(char c);
2. operator+= (最常用)
功能
尾部拼接,万能追加
string& operator+=(const string& str); // 加string string& operator+=(const char* s); // 加C字符串 string& operator+=(char c); // 加单个字符
3. append(专业尾部追加,重载最多)
特点
只在尾部
功能比
+=更细,适合批量、截取追加// 1. 追加 string 对象
string& append(const string& str);// 2. 追加 C风格字符串
string& append(const char* cstr);// 3. 追加 n 个相同字符
string& append(size_t n, char ch);// 4. 截取str从pos开始len个字符追加
string& append(const string& str, size_t pos, size_t len);// 5. 追加 cstr 前 n 个字符
string& append(const char* cstr, size_t n);
4. insert(任意位置插入)
功能
在指定下标 / 迭代器位置插入内容可以:头部、中间、尾部插入
// 1. 在pos位置插入字符串 string& insert(size_t pos, const char* s); // 2. 在pos插入n个字符ch string& insert(size_t pos, size_t n, char ch); // 3. 在pos插入string string& insert(size_t pos, const string& str); // 4. 迭代器版本:在it位置插入字符 iterator insert(iterator it, char c); string s = "123"; s.insert(0, "ab"); // 下标0插入 → ab123 s.insert(2, 3, '0'); // 下标2插3个0 → ab000123 // 迭代器插入 auto it = s.begin(); s.insert(it + 1, 'X');
5. 四种方法对比
函数 插入位置 支持内容 优点 缺点 push_back 仅末尾 单个 char 轻量、简单 不能加字符串 += 仅末尾 char / 字符串 /string 写法最简,日常首选 无精细截取功能 append 仅末尾 多种重载(串、n 个字符、子串) 追加功能最全 只能尾插 insert 任意位置 串、n 个字符、单个字符 可中间 / 头部插入 效率偏低,易迭代器失效
8.assign
1、核心作用
重新赋值、覆盖原有全部内容直接清空旧字符串,用新内容替换。和
=赋值类似,但 重载更多、功能更强。
// 1. 赋值 string 对象 string& assign(const string& str); // 2. 赋值 C风格字符串 string& assign(const char* s); // 3. 赋值 n 个相同字符 string& assign(size_t n, char c); // 4. 截取某串 [pos, len] 赋值 string& assign(const string& str, size_t pos, size_t len); // 5. 取 C串 前 n 个字符 string& assign(const char* s, size_t n);2. 实例运用
string s; // 1. 整体赋值 s.assign("12345"); // s = "12345" // 2. n 个相同字符覆盖 s.assign(3, 'a'); // s = "aaa" // 3. 截取子串赋值 string t = "abcdef"; s.assign(t, 2, 3); // 从下标2取3个:cde // 4. 只取前n个字符 s.assign("hello", 2);// he
9. replace
1. 作用
replace = 删除指定区间 + 插入新内容
直接替换字符串中某一段内容,不是只替换单个字符。
2. 最常用函数原型(重点)
s.replace(位置pos, 长度len, 新内容); // 1. 从 pos 位置开始,长度为 len,替换成 str string& replace(size_t pos, size_t len, const string& str); // 2. 替换成 n 个 ch 字符 string& replace(size_t pos, size_t len, size_t n, char ch); // 3. 迭代器版本:替换 [first, last) 区间 string& replace(iterator first, iterator last, const string& str);3. 实例运用
string s = "Hello World"; // 从下标 6 开始,长度 5 替换成 "C++" s.replace(6, 5, "C++"); Hello C++ // 从下标 0 开始,长度 5 替换成 3 个 '*' s.replace(0, 5, 3, '*'); *** World // 替换 [begin, begin+5) 区间 s.replace(s.begin(), s.begin()+5, "Hi"); Hi World4. 例题实用
// 所有空格替换为%% string s5(" hello world hello bit"); //size_t pos = s5.find(' '); //while (pos != string::npos) //{ // s5.replace(pos, 1, "%%"); // pos = s5.find(' ', pos+2); //} //cout << s5 << endl; //相同写法 string s6; s6.reserve(s5.size()); for (auto ch : s5) { if (ch == ' ') s6 += "%%"; else s6 += ch; } cout << s6 << endl;
10. reverse
用法场景 代码 原字符串 反转后结果 说明 1. 反转整个字符串 reverse(s.begin(), s.end());"123456""654321"最常用 2. 反转前 N 个字符 reverse(s.begin(), s.begin()+3);"abcdef""cbadef"反转前 3 个 3. 反转后 N 个字符 reverse(s.end()-3, s.end());"123456""123654"反转最后 3 个 4. 反转中间一段 reverse(s.begin()+2, s.begin()+5);"abcdefg""abedcfg"反转下标 [2,5) 5. 反向迭代器反转 reverse(s.rbegin(), s.rend());"hello""olleh"等价全反转
容器类型 代码 说明 string 字符串 reverse(s.begin(), s.end());反转字符串 vector 向量 reverse(v.begin(), v.end());反转数组元素 普通数组 reverse(arr, arr + 5);反转数组 list 链表 reverse(l.begin(), l.end());反转链表
11.题目运用
415. 字符串相加
https://leetcode.cn/problems/add-strings/
给定两个字符串形式的非负整数
num1和num2,计算它们的和并同样以字符串形式返回。你不能使用任何內建的用于处理大整数的库(比如
BigInteger), 也不能直接将输入的字符串转换为整数形式。题解:
思考题解的运行效率,利用上面介绍的所有方法,可不可以进行优化呢?
class Solution { public: string addStrings(string num1, string num2) { int end1=num1.size()-1; int end2=num2.size()-1; string s; int sum=0; int carry=0; while(end2>=0||end1>=0) { sum=0; int x1=end1>=0?num1[end1]-'0':0; int x2=end2>=0?num2[end2]-'0':0; sum=x1+x2+carry; carry=sum/10; end1--; end2--; sum=sum%10; s.insert(0,1,sum+'0'); } if(carry==1) { s.insert(0,1,1+'0'); } return s; } };
优化:
class Solution { public: string addStrings(string num1, string num2) { int end1=num1.size()-1; int end2=num2.size()-1; string s; s.reserve(max(num1.size(),num2.size())+1); int sum=0; int carry=0; while(end2>=0||end1>=0) { sum=0; int x1=end1>=0?num1[end1]-'0':0; int x2=end2>=0?num2[end2]-'0':0; sum=x1+x2+carry; carry=sum/10; end1--; end2--; sum=sum%10; s+=(sum +'0'); } if(carry==1) { s+=(1 +'0'); } reverse(s.begin(),s.end()); return s; } };1.利用reverse,成功避免了insert头插时,遍历字符串后移问题,提高了效率。
2.利用reserve,根据字符串大小开足空间,避免了后期的过量扩容问题。


