string & string_view
std::string是 C++ 的动态字符串,string_view是 C++17 引入的零拷贝字符串视图,两者配合是现代 C++ 字符串处理的标准方式。
std::string 基础
cpp
#include <string>
// 构造
std::string s1 = "hello";
std::string s2("world", 3); // "wor"(前3个字符)
std::string s3(5, 'x'); // "xxxxx"
std::string s4 = s1 + " " + "world"; // 拼接
// 访问
s1[0]; // 'h',不检查边界
s1.at(0); // 'h',越界抛异常
s1.front(); // 'h'
s1.back(); // 'o'
s1.data(); // const char*(C++11 保证以 '\0' 结尾)
s1.c_str(); // const char*(同上)
// 大小
s1.size(); // 5
s1.length(); // 5(同 size)
s1.empty(); // false
s1.capacity(); // 已分配容量
// 修改
s1 += " world";
s1.append(" !");
s1.insert(5, ","); // 在位置 5 插入
s1.erase(5, 1); // 从位置 5 删除 1 个字符
s1.replace(0, 5, "hi"); // 替换 [0,5) 为 "hi"
s1.clear();查找与子串
cpp
std::string s = "hello world hello";
// 查找
size_t pos = s.find("hello"); // 0
size_t pos2 = s.find("hello", 1); // 12(从位置1开始找)
size_t pos3 = s.rfind("hello"); // 12(从右往左找)
size_t pos4 = s.find("xyz"); // std::string::npos
if (pos4 == std::string::npos) {
std::cout << "未找到\n";
}
// 查找字符集合
size_t p = s.find_first_of("aeiou"); // 第一个元音的位置
size_t p2 = s.find_last_of("aeiou"); // 最后一个元音的位置
size_t p3 = s.find_first_not_of("helo wrd"); // 第一个不在集合中的字符
// 子串
std::string sub = s.substr(6, 5); // "world"(从位置6取5个字符)
std::string sub2 = s.substr(6); // "world hello"(到末尾)
// 比较
s.compare("hello world hello") == 0; // 相等
s.starts_with("hello"); // C++20
s.ends_with("hello"); // C++20
s.contains("world"); // C++23SSO(小字符串优化)
cpp
// 大多数实现对短字符串(通常 ≤ 15 字节)不分配堆内存
// 直接存储在 string 对象内部(栈上)
std::string short_str = "hello"; // 存在栈上,无堆分配
std::string long_str = "this is a longer string that exceeds SSO"; // 堆分配
// sizeof(std::string) 通常是 24 或 32 字节
// 包含:指针/内联缓冲区 + 大小 + 容量
// 验证 SSO
#include <cstdio>
std::string s = "hi";
printf("stack addr: %p\n", (void*)&s);
printf("data addr: %p\n", (void*)s.data());
// 如果两个地址接近,说明是 SSO(数据在对象内部)string_view — 零拷贝视图
cpp
#include <string_view>
// string_view 只是一个 {指针, 长度} 对,不拥有数据
std::string_view sv1 = "hello world"; // 指向字符串字面量
std::string s = "hello world";
std::string_view sv2 = s; // 指向 string 的内部缓冲区
// 所有只读操作都支持
sv1.size();
sv1.substr(0, 5); // 返回 string_view,零拷贝!
sv1.find("world");
sv1.starts_with("hello");
// 函数参数:用 string_view 代替 const string&
// 可以接受 string、字面量、char* 而无需拷贝
void process(std::string_view sv) {
std::cout << sv << "\n";
}
process("literal"); // 零拷贝
process(s); // 零拷贝
process(s.substr(0, 5)); // 临时 string,有拷贝(注意!)string_view 的陷阱
cpp
// 陷阱 1:悬空视图(最危险)
std::string_view get_view() {
std::string s = "hello";
return s; // s 析构后,view 悬空!
}
// 陷阱 2:不以 '\0' 结尾
std::string_view sv = "hello world";
sv = sv.substr(0, 5); // "hello",但 sv.data()[5] 是 ' ',不是 '\0'
// printf("%s", sv.data()); // 危险!会读到 "hello world"
// 安全转换为 string
std::string safe(sv); // 显式构造,保证 '\0' 结尾
// 陷阱 3:string_view 不能用于需要 null-terminated 的 C API
// c_str() 不存在!字符串转换
cpp
#include <string>
#include <charconv> // C++17,最快的转换方式
// 数字 → 字符串
std::string s1 = std::to_string(42);
std::string s2 = std::to_string(3.14);
// 字符串 → 数字
int n = std::stoi("42");
long l = std::stol("123456789");
double d = std::stod("3.14");
// 注意:stoi 等在失败时抛异常
// C++17 charconv:最快,不抛异常,不分配内存
char buf[20];
auto [ptr, ec] = std::to_chars(buf, buf + 20, 42);
// ec == std::errc{} 表示成功
int val;
auto [ptr2, ec2] = std::from_chars("42abc", "42abc" + 2, val);
// val == 42,ptr2 指向 'a'
// 大小写转换
std::string s = "Hello World";
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
std::transform(s.begin(), s.end(), s.begin(), ::toupper);格式化(C++20 std::format)
cpp
#include <format>
// 类型安全的格式化,比 printf 更安全,比 stringstream 更快
std::string s = std::format("Hello, {}! You are {} years old.", "Alice", 30);
std::string s2 = std::format("{:.2f}", 3.14159); // "3.14"
std::string s3 = std::format("{:>10}", "right"); // " right"
std::string s4 = std::format("{:0>5}", 42); // "00042"
// 直接输出(C++23 std::print)
std::print("Hello, {}!\n", "world");
std::println("Value: {}", 42); // 自动换行关键认知
函数参数接受字符串时,用 std::string_view 代替 const std::string&,可以接受任何字符串类型且零拷贝。注意 string_view 的生命周期问题,不要返回指向局部变量的 string_view。C++20 的 std::format 是格式化字符串的最佳选择。