std::string 是 C++ 标准库中最常用的类之一,本质是 std::basic_string<char> 的别名。下面从基础用法、内部机制、常见陷阱到现代特性,逐步拆解。
一、头文件与基本定义
#include <string>
using std::string;
using std::wstring; // std::basic_string<wchar_t>
using std::u16string; // std::basic_string<char16_t>
using std::u32string; // std::basic_string<char32_t>
std::string 负责管理动态字符数组,自动处理内存分配与释放,并带有一个长度字段,不以 '\\0' 的数量计长。
二、构造与赋值
1. 常见构造方式
string s1; // 空字符串
string s2("hello"); // 从 C 风格字符串
string s3(s2); // 拷贝构造
string s4(s2, 2); // "llo",从位置2到末尾
string s5(s2, 1, 3); // "ell",从位置1,取3字符
string s6(5, 'x'); // "xxxxx"
string s7(s2.begin(), s2.end()-1); // 从迭代器范围,"hell"
string s8 = "world"; // 隐式转换
2. 赋值操作
s1 = s2; // 拷贝赋值
s1 = "abc"; // 从 C 字符串赋值
s1 = 'A'; // 单字符
s1.assign(s2, 1, 2); // 从 s2[1] 开始取 2 字符
s1.assign(3, 'B'); // "BBB"
注意:std::string 不像某些语言那样不可变,它是可变的字符序列。
三、容量与大小
s.size() // 字符数(O(1))
s.length() // 同 size()
s.capacity() // 已分配内存可容纳的字符数
s.max_size() // 理论上限
s.empty() // 是否为空
s.reserve(n) // 预留至少 n 个字符的内存,避免反复重分配
s.shrink_to_fit() // 请求释放多余容量(非强制)
-
size() 和 length() 语义相同,都是 char 的数量。
-
capacity() 通常大于等于 size(),多出的空间用于减少 push_back 等操作的重新分配。
四、元素访问
s[i] // 不检查越界
s.at(i) // 越界抛出 std::out_of_range
s.front() // s[0]
s.back() // s[size()-1]
s.data() // 返回内部字符数组指针(C++11 起包含 '\\0',可修改)
s.c_str() // 返回 const char*,保证以 '\\0' 结尾
-
data() 和 c_str() 返回相同指针。
-
修改 data() 返回的缓冲区时,不能超出 size() 并需手动维护结尾 '\\0'(如果后续要作为 C 字符串使用)。
五、修改操作
1. 追加 / 插入 / 删除
s += " world"; // 拼接
s.append("!!!"); // 追加
s.push_back('!'); // 追加单字符
s.insert(0, "prefix"); // 在位置0插入
s.insert(0, 3, 'A'); // 插入3个'A'
s.erase(2, 4); // 从索引2删除4个字符
s.pop_back(); // 删除最后一个字符(C++11)
s.clear(); // 清空
2. 替换
s.replace(pos, len, "new"); // 替换区间 [pos, pos+len)
s.replace(it1, it2, "new"); // 替换迭代器区间
3. 比较
s1 == s2 // 内容相等
s1 != s2
s1 < s2 // 字典序
s1.compare(s2) // 返回 int(<0, 0, >0)
s1.compare(pos, len, s2)
六、查找
全部返回 std::string::npos (即 static const size_type npos = -1) 如果未找到。
s.find("sub") // 子串首次出现位置
s.rfind("sub") // 最后一次出现
s.find_first_of("aeiou") // 在 s 中找第一个属于 "aeiou" 的字符
s.find_last_of("aeiou")
s.find_first_not_of(" ") // 第一个不在参数中的字符
s.find_last_not_of(" ")
七、子串与转换
string sub = s.substr(2, 5); // 从索引2开始取5个字符
int n = stoi("123"); // 字符串转整数
long l = stol("123L");
float f = stof("3.14");
double d = stod("3.14");
string num = to_string(456); // 数值转字符串
八、迭代器
s.begin() // 指向第一个字符
s.end() // 尾后
s.rbegin() // 反向
s.rend()
for (char c : s) { … }
sort(s.begin(), s.end());
reverse(s.begin(), s.end());
九、常见陷阱
1.s[s.size()] 不是 '\\0':C++11 起 s[s.size()] 定义为 CharT()(即 '\\0'),但修改它会引发未定义行为,不要依赖它。
2.data() 和 c_str() 失效:任何非 const 成员函数调用后,返回的指针可能失效,需重新获取。
3.reserve 不改变 size:它只分配内存,若直接用下标访问需确保索引在 size 内,或使用 resize。
4.stoi 等转换异常:转换失败抛出 std::invalid_argument 或 std::out_of_range。
5.npos 是无符号:若 find 返回 npos,与有符号数比较前注意类型转换,勿写 int pos = s.find(…); if (pos < 0)。
十、string常用的函数
1. 构造与赋值
#include <string>
using namespace std;
// ① 默认构造:空字符串
string s1; // s1 == ""
// ② 从 C 风格字符串构造
string s2("hello"); // s2 == "hello"
// ③ 拷贝构造
string s3(s2); // s3 == "hello"
// ④ 重复字符构造:5 个字符 'a'
string s4(5, 'a'); // s4 == "aaaaa"
// ⑤ 赋值运算符
string s5;
s5 = "world"; // s5 == "world"
s5 = s2; // s5 == "hello"
// ⑥ assign —— 功能等价,但有细粒度控制
s5.assign(s2, 1, 2); // 从 s2[1] 开始取 2 个字符,s5 == "el"
s5.assign(3, 'X'); // s5 == "XXX"
2. 容量与信息
string s = "Hello";
// ① size() / length() —— 字符个数
s.size(); // 5
s.length(); // 5
// ② empty() —— 是否为空
s.empty(); // false
string e;
e.empty(); // true
// ③ capacity() —— 已分配空间大小
s.capacity(); // 至少 >= 5,具体值依赖实现
// ④ reserve(n) —— 预留内存,防止反复扩容
s.reserve(100); // capacity 变为 >= 100,size 仍为 5
// ⑤ clear() —— 清空内容,size 归零
s.clear(); // s == "",capacity 通常不变
3. 访问字符
string s = "abcdef";
// ① operator[] —— 不检查越界
char c1 = s[2]; // 'c'
s[0] = 'A'; // s 变为 "Abcdef"
// ② at() —— 越界抛 std::out_of_range
char c2 = s.at(2); // 'c'
// s.at(100); // 抛出异常
// ③ front() —— 第一个字符
char first = s.front(); // 'A'
s.front() = 'Z'; // s 变为 "Zbcdef"
// ④ back() —— 最后一个字符
char last = s.back(); // 'f'
s.back() = 'F'; // s 变为 "ZbcdeF"
// ⑤ c_str() —— 返回以 '\\0' 结尾的 const char*
const char* p = s.c_str(); // 可用于 printf("%s", p);
// 注意:一旦修改 s 或 s 析构,p 就失效
// ⑥ data() —— C++17 起可修改的内部缓冲区
char* pdata = s.data(); // C++17: 可修改
pdata[1] = 'Y'; // s 变为 "ZYcdeF"
// 修改需保证不越界,且保持 size 一致,若需要结尾 '\\0' 要自行维护
4. 修改字符串
string s = "Hello";
// ① operator+= —— 追加字符串/字符
s += " World"; // s == "Hello World"
s += '!'; // s == "Hello World!"
// ② append —— 与 += 类似,支持多种形式
s.append("!!!"); // s == "Hello World!!!!"
s.append(2, '?'); // s == "Hello World!!!!??"
// ③ push_back —— 尾部加单字符
s.push_back('~'); // s == "Hello World!!!!??~"
// ④ pop_back —— 删除尾部单字符 (C++11)
s.pop_back(); // 删除 '~',s == "Hello World!!!!??"
// ⑤ insert —— 在指定位置插入
s.insert(5, "Beautiful "); // 在索引5插入,s == "HelloBeautiful World!!!!??"
s.insert(0, 3, '>'); // 开头插入3个'>',s == ">>>HelloBeautiful World!!!!??"
// ⑥ erase —— 删除子串
s.erase(0, 3); // 删除前3个字符,s == "HelloBeautiful World!!!!??"
s.erase(s.begin(), s.begin()+5); // 删除前5个字符(迭代器)
// ⑦ replace —— 替换区间
s.replace(0, 5, "Hi"); // 将[0,5)替换为"Hi",s == "HiBeautiful World!!!!??"
// ⑧ swap —— 交换两个 string 的内容
string other = "swap";
s.swap(other); // s == "swap",other == "HiBeautiful World!!!!??"
swap(s, other); // 非成员函数版,效果相同
// ⑨ clear —— 清空
s.clear(); // s == ""
5. 查找与搜索
string s = "Hello World, Hello Universe";
string key = "Hello";
// ① find —— 子串首次出现的位置
size_t pos = s.find(key); // 0
pos = s.find("World"); // 6
pos = s.find("xyz"); // string::npos
// ② rfind —— 子串最后一次出现的位置
pos = s.rfind("Hello"); // 13
// ③ find_first_of —— 从 s 中找第一个属于字符集的字符
pos = s.find_first_of("aeiou"); // 1 (第一个元音 'e')
// ④ find_last_of —— 最后一个属于字符集的字符
pos = s.find_last_of("aeiou"); // 29 (最后一个 'e' 在 Universe 中)
// ⑤ find_first_not_of —— 第一个不属于字符集的字符
pos = s.find_first_not_of(" \\t"); // 0 (第一个不是空白)
// ⑥ find_last_not_of —— 最后一个不属于字符集的字符
pos = s.find_last_not_of(" "); // 28 (最后一个非空格是 'e')
// 判断是否包含子串的常用写法:
if (s.find("World") != string::npos) {
// 找到了
}
6. 子串与比较
string s = "Hello World";
// ① substr(pos, len) —— 提取子串
string sub = s.substr(0, 5); // "Hello"
sub = s.substr(6); // "World" (len 默认到结尾)
sub = s.substr(6, 2); // "Wo"
// ② compare —— 字典序比较 (返回 0, <0, >0)
int cmp = s.compare("Hello"); // >0 (World 部分导致更大)
cmp = s.compare(0, 5, "Hello"); // 0
cmp = s.compare(6, 5, "World"); // 0
// 日常推荐直接使用比较运算符
// ③ 比较运算符
bool eq = (s == "Hello World"); // true
bool ne = (s != "hi"); // true
bool lt = (s < "Hello World!"); // true (较短更小)
bool le = (s <= "Hello World"); // true
bool gt = (s > "Apple"); // true
bool ge = (s >= "Hello World"); // true
7. 数值与字符串转换(非成员函数)
// ① to_string —— 数值转字符串
string numStr = to_string(123); // "123"
string piStr = to_string(3.14159); // "3.141590"
// ② stoi —— 字符串转 int
int n = stoi("42"); // 42
n = stoi("0xFF", nullptr, 16); // 按16进制解析,n=255
// ③ stol —— 字符串转 long
long l = stol("1000000"); // 1000000
// ④ stod —— 字符串转 double
double d = stod("3.14"); // 3.14
// 其他变体:stoul, stoull, stof, stold 等用法一致
// 错误处理:若无法转换或超出范围会抛异常
try {
int x = stoi("not_a_number");
} catch (const invalid_argument& e) {
// 非数字
} catch (const out_of_range& e) {
// 数值越界
}
8. 流输入与字面量
#include <iostream>
#include <sstream> // istringstream
using namespace std;
// ① getline —— 读取一行(可指定分隔符)
string line;
getline(cin, line); // 从标准输入读一行,丢弃换行符
// 若从文件流也是相同用法
istringstream iss("apple,banana");
getline(iss, line, ','); // line == "apple"
// ② operator<< 和 operator>> —— 流输出/输入
string s = "Hello";
cout << s; // 输出 "Hello"
cin >> s; // 以空白分隔读取,不适合含空格的字符串
// ③ ""s 字面量 (C++14)
using namespace string_literals;
auto str = "I am a string"s; // 类型为 std::string,等价于 string("I am a string")
9. 迭代器
string s = "example";
// ① begin() / end() —— 正向迭代器
for (auto it = s.begin(); it != s.end(); ++it) {
*it = toupper(*it); // 转为大写
}
// s == "EXAMPLE"
// ② 基于范围的 for 循环
for (char& c : s) {
c = tolower(c); // 转为小写
}
// s == "example"
// 配合标准算法
#include <algorithm>
reverse(s.begin(), s.end()); // s == "elpmaxe"
sort(s.begin(), s.end()); // s == "aeelmpx" (按字符排序)

