字符串处理
std::string与字符串视图
概述
字符串处理是 C++ 开发中最常见的任务之一。C++ 标准库提供了 std::string 作为主要的字符串类型,C++17 引入的 std::string_view 则为只读字符串场景提供了零拷贝的高效方案。C++20 和 C++23 持续增强字符串功能,新增了 starts_with、ends_with、contains 等便捷方法。
理解 std::string 的内部机制(SSO 优化)和 std::string_view 的生命周期约束,是编写高效字符串代码的关键。
基础概念
字符串类型对比
| 类型 | 说明 | 所有权 |
|---|---|---|
const char* | C 风格字符串 | 无,指向静态或外部内存 |
std::string | 标准字符串类 | 拥有数据,自动管理内存 |
std::string_view | 字符串视图(C++17) | 不拥有数据,仅引用 |
std::u8string | UTF-8 字符串(C++20) | 拥有数据 |
SSO(Small String Optimization)
std::string 通常使用 SSO 优化:短字符串(通常 15-22 字节以内)直接存储在对象内部,不分配堆内存。这意味着短字符串的创建和拷贝非常高效。
快速上手
std::string 基本操作
#include <string>
#include <iostream>
int main() {
// 创建字符串
std::string s1 = "Hello";
std::string s2(5, 'a'); // "aaaaa"
std::string s3(s1, 1, 3); // "ell",从位置1取3个字符
// 拼接
std::string greeting = s1 + ", World!";
// 访问
char c = greeting[0]; // 'H'
char last = greeting.back(); // '!'
// 查找
size_t pos = greeting.find("World"); // 7
pos = greeting.find("xyz"); // std::string::npos
// 子串
std::string sub = greeting.substr(0, 5); // "Hello"
// 替换
greeting.replace(7, 5, "C++"); // "Hello, C++!"
// C++20: starts_with / ends_with
greeting.starts_with("Hello"); // true
greeting.ends_with("!"); // true
// C++23: contains
greeting.contains("C++"); // true
return 0;
}
std::string_view 零拷贝传参
#include <string_view>
#include <iostream>
// 使用 string_view 避免不必要的字符串拷贝
void printLength(std::string_view sv) {
std::cout << "长度: " << sv.size() << std::endl;
std::cout << "内容: " << sv << std::endl;
}
int main() {
std::string s = "Hello, World!";
printLength(s); // 从 std::string 隐式转换
printLength("直接传字面量"); // 直接构造 string_view
printLength(s.substr(0, 5)); // 子串视图,零拷贝
return 0;
}
详细用法
字符串搜索与替换
#include <string>
#include <algorithm>
// 替换所有出现的子串
std::string replaceAll(std::string str, const std::string& from,
const std::string& to) {
size_t pos = 0;
while ((pos = str.find(from, pos)) != std::string::npos) {
str.replace(pos, from.length(), to);
pos += to.length(); // 跳过已替换的部分
}
return str;
}
// 使用
auto result = replaceAll("hello world hello", "hello", "你好");
// result = "你好 world 你好"
// 分割字符串
std::vector<std::string> split(const std::string& str, char delimiter) {
std::vector<std::string> tokens;
size_t start = 0, end;
while ((end = str.find(delimiter, start)) != std::string::npos) {
tokens.push_back(str.substr(start, end - start));
start = end + 1;
}
tokens.push_back(str.substr(start));
return tokens;
}
// 使用
auto parts = split("one,two,three,four", ',');
// parts = {"one", "two", "three", "four"}
字符串格式化(C++20 std::format)
#include <format>
#include <iostream>
#include <string>
int main() {
std::string name = "张三";
int age = 25;
double score = 95.5;
// 基本格式化
std::string s1 = std::format("姓名: {}, 年龄: {}", name, age);
// 位置参数
std::string s2 = std::format("{0} 今年 {1} 岁,{0} 的分数是 {2:.1f}",
name, age, score);
// 宽度和对齐
std::string s3 = std::format("{:>10}", name); // 右对齐,宽度10
std::string s4 = std::format("{:<10}", name); // 左对齐
std::string s5 = std::format("{:^10}", name); // 居中对齐
// 数字格式
std::string s6 = std::format("{:d}", 42); // 十进制
std::string s7 = std::format("{:#x}", 255); // 十六进制: 0xff
std::string s8 = std::format("{:#b}", 10); // 二进制: 0b1010
return 0;
}
字符串与数值转换
#include <string>
#include <charconv>
#include <iostream>
int main() {
// 字符串转数值
int i = std::stoi("42");
double d = std::stod("3.14159");
long l = std::stol("1234567890");
// 数值转字符串
std::string s1 = std::to_string(42);
std::string s2 = std::to_string(3.14);
// C++17: std::from_chars(高性能,无异常,无内存分配)
int value;
auto [ptr, ec] = std::from_chars("123abc", "123abc" + 6, value);
if (ec == std::errc{}) {
std::cout << "解析成功: " << value << std::endl; // 123
}
// C++17: std::to_chars(高性能数值转字符串)
char buffer[32];
auto [end, err] = std::to_chars(buffer, buffer + sizeof(buffer), 3.14159);
*end = '\0';
std::cout << buffer << std::endl;
return 0;
}
字符串视图操作
#include <string_view>
// string_view 的安全使用模式
std::string_view getExtension(std::string_view filename) {
size_t dot = filename.rfind('.');
if (dot != std::string_view::npos) {
return filename.substr(dot); // 返回子视图,零拷贝
}
return {};
}
// 使用
std::string file = "document.pdf";
auto ext = getExtension(file); // ext = ".pdf"
// 注意:string_view 不保证以 null 结尾
// 不能直接传给需要 const char* 的 C 函数
// std::string sv_str(sv); // 需要时转为 string
常见场景
配置文件解析
#include <string>
#include <map>
#include <fstream>
std::map<std::string, std::string> parseConfig(const std::string& filepath) {
std::map<std::string, std::string> config;
std::ifstream file(filepath);
std::string line;
while (std::getline(file, line)) {
// 跳过空行和注释
if (line.empty() || line[0] == '#') continue;
// 去除首尾空格
auto start = line.find_first_not_of(" \t");
auto end = line.find_last_not_of(" \t");
if (start == std::string::npos) continue;
line = line.substr(start, end - start + 1);
// 按 = 分割
auto eq = line.find('=');
if (eq != std::string::npos) {
auto key = line.substr(0, eq);
auto value = line.substr(eq + 1);
config[key] = value;
}
}
return config;
}
日志消息构建
#include <format>
#include <string>
#include <chrono>
std::string formatLog(std::string_view level, std::string_view message) {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
// 简化的时间格式化
return std::format("[{}] [{}] {}", "2026-06-14 10:30:00", level, message);
}
// 使用
auto log = formatLog("INFO", "服务启动成功");
// [2026-06-14 10:30:00] [INFO] 服务启动成功
注意事项
std::string_view不拥有数据,底层字符串被销毁后视图变为悬空引用,不要存储 string_view 超出底层字符串的生命周期std::string::c_str()返回的指针在 string 被修改或销毁后失效std::string_view不保证以 null 结尾,不能直接传给需要const char*的 C 函数- 频繁拼接字符串时应使用
reserve()预分配空间,或使用std::ostringstream std::to_string对浮点数的格式化不够精确,推荐使用std::format或std::to_chars- C++20 的
std::format需要#include <format>,GCC 13+ 和 MSVC 19.29+ 支持
进阶用法
C++23 std::print
#include <print>
// C++23: 直接格式化输出到 stdout
std::print("你好, {}! 你的分数是 {:.1f}\n", "张三", 95.5);
// 输出到文件
std::ofstream log("app.log");
std::print(log, "[INFO] {}\n", "服务启动");
Unicode 与编码处理
#include <string>
#include <codecvt> // C++17 已弃用,仅作参考
// C++20: u8string 明确表示 UTF-8 编码
std::u8string utf8_text = u8"你好世界";
// 转换为 std::string(C++20)
std::string text(reinterpret_cast<const char*>(utf8_text.data()),
utf8_text.size());
// 实际项目中推荐使用 ICU 库处理 Unicode
// 或使用 C++23 的 std::text_encoding
高性能字符串拼接
#include <string>
#include <vector>
#include <numeric>
// 方式一:预计算总长度后拼接
std::string join(const std::vector<std::string>& parts, std::string_view delimiter) {
if (parts.empty()) return "";
// 计算总长度
size_t total = (parts.size() - 1) * delimiter.size();
for (const auto& p : parts) total += p.size();
std::string result;
result.reserve(total);
result += parts[0];
for (size_t i = 1; i < parts.size(); ++i) {
result += delimiter;
result += parts[i];
}
return result;
}
// 方式二:使用 std::format 拼接(C++20)
// 方式三:使用 fmt 库(第三方,性能最优)