字符串 (string)
std::string 是标准库的字符串类型,封装了动态字符数组,自动管理内存。相比 C 风格的 char*,它无需手动分配释放,也不会因为忘记 \0 而出错。
C++17 引入的 std::string_view 则提供了不拥有数据的字符串视图,用于只读场景,避免不必要的拷贝。
1. 引入
#include <string> // std::string
#include <string_view> // std::string_view
2. 核心概念
1. 构造与赋值
std::string s1; // 空字符串
std::string s2 = "hello"; // 从字面量
std::string s3("hello", 3); // "hel"(前 3 个字符)
std::string s4(5, 'x'); // "xxxxx"
std::string s5(s2); // 拷贝构造
std::string s6(std::move(s2)); // 移动构造
s1 = "world"; // 赋值
2. 常用操作
| 操作 | 说明 |
|---|---|
size() / length() | 字符数(两者等价) |
empty() | 是否为空 |
clear() | 清空 |
c_str() | 返回 C 风格字符串(保证以 \0 结尾) |
data() | 返回底层字符数组指针 |
substr(pos, n) | 取子串 |
find(str) | 查找子串,返回位置或 npos |
rfind(str) | 从后往前查找 |
find_first_of(chars) | 查找任意一个字符首次出现 |
replace(pos, n, str) | 替换 |
insert(pos, str) | 插入 |
erase(pos, n) | 删除 |
append(str) / += | 追加 |
compare(str) | 比较,返回负数/0/正数 |
starts_with(str) | 是否以某串开头(C++20) |
ends_with(str) | 是否以某串结尾(C++20) |
contains(str) | 是否包含子串(C++23) |
std::string s = "Hello World";
std::cout << s.size() << '\n'; // 11
std::cout << s.substr(6, 5) << '\n'; // World
std::cout << s.find("World") << '\n'; // 6
if (s.find("xyz") == std::string::npos)
std::cout << "未找到\n";
s.replace(0, 5, "Goodbye"); // "Goodbye World"
s += "!"; // "Goodbye World!"
3. 遍历与转换
std::string s = "hello";
// 遍历
for (char c : s)
std::cout << c;
// 转数字
int n = std::stoi("42");
double d = std::stod("3.14");
// 数字转字符串
std::string a = std::to_string(42);
std::string b = std::to_string(3.14);
std::stoi 等函数在转换失败时会抛出 std::invalid_argument 或 std::out_of_range。
4. 小字符串优化(SSO)
大多数实现采用小字符串优化:短字符串(通常 15~22 字节以内)直接存储在 string 对象内部的缓冲区中,不进行堆分配。
std::string short_str = "hi"; // 无堆分配
std::string long_str(1000, 'x'); // 堆分配
这意味着短字符串的拷贝成本很低,但 sizeof(std::string) 通常比想象中大(约 32 字节)。
3. 用法
string_view
std::string_view 是对字符序列的非拥有视图,只保存指针和长度:
void process(std::string_view sv) // 不拷贝
{
std::cout << sv.size() << '\n';
}
std::string s = "hello";
process(s); // 从 string 构造,无拷贝
process("world"); // 从字面量构造,无拷贝
process(s.substr(0, 3)); // 注意:substr 返回临时 string,此处有拷贝
优势:
- 传参时避免拷贝,尤其适合只读的字符串参数
- 可以接受
std::string、字符串字面量、char*等多种来源 - 取子串是 \(O(1)\)(只调整指针和长度)
局限:
- 不拥有数据,底层字符串销毁后视图会悬垂
- 不保证以
\0结尾,不能直接传给需要 C 字符串的接口 - 没有
c_str(),需要转换时用std::string(sv)
// 危险:返回指向临时对象的视图
std::string_view bad()
{
std::string local = "temporary";
return local; // 错误:local 已销毁
}
// 安全:返回 string
std::string good()
{
return "safe";
}
何时用哪个
| 场景 | 推荐 |
|---|---|
| 需要拥有数据、修改内容 | std::string |
| 只读参数、临时查看 | std::string_view |
需要以 \0 结尾传给 C API | std::string(c_str()) |
| 成员变量长期持有 | std::string(视图易悬垂) |
| 返回字符串 | std::string(视图可能悬垂) |
4. 注意事项
npos是size_t的最大值。用find判断时要写== std::string::npos,不要写成-1。c_str()的指针可能失效。任何修改字符串的操作都可能触发重新分配,使之前取得的指针悬垂。string_view不拥有数据。绝不要返回指向局部变量的string_view,也不要把string_view作为长期成员。string_view不保证\0结尾。传给printf等 C 函数会越界。s + "a"与"a" + s不同。前者合法,后者中"a"是const char*,指针加法是未定义行为。应写std::string("a") + s。substr返回新字符串。string_view::substr返回视图(\(O(1)\)),string::substr返回新字符串(\(O(n)\)),注意区分。- 字符串拼接的性能。循环中反复
s += x可能多次重新分配,预先reserve()可避免。