Skip to content

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++23

SSO(小字符串优化)

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 是格式化字符串的最佳选择。

系统学习 C++ 生态,深入底层架构