前置知识: C++

字符串处理

4 minIntermediate2026/6/14

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::u8stringUTF-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::formatstd::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 库(第三方,性能最优)