视频加载失败

C++ 中的 string

1219 字
6 分钟
C++ 中的 string

std::string 是 C++ Standard Library 提供的标准类之一,这也是 C++ 开发中最常用的类之一,它本质上是对 C 风格字符串(char*)的封装,提供了自动内存管理和丰富的操作接口。

核心概念#

std::string 位于 <string> 头文件中,本质上是对 std::basic_string<char> 的特化版本。

  • 头文件:#include <string>
  • 命名空间:std
  • 特点:动态管理内存,自动处理扩容,兼容 C 风格字符串。

初始化与构造#

#include <string>
#include <vector>
std::string s1; // 默认构造,空字符串 ""
std::string s2 = "Hello, World"; // 拷贝初始化
std::string s3("Hello, World"); // 直接初始化
std::string s4(5, 'A'); // 生成 "AAAAA"
std::string s5(s2); // 拷贝构造
std::string s6(s2, 1, 3); // 子串构造:从索引 1 开始取 3 个字符 -> "ell"
// C++11 及以后
std::string s7 = {'H', 'i'}; // 列表初始化

容量与属性#

了解容量对于优化性能至关重要,特别是避免不必要的内存重分配。

函数说明备注
size() / length()返回字符数量两者完全等价,一般用 size()
empty()判断是否为空推荐使用,比 size() == 0 语义更清晰
capacity()当前分配的内存容量通常 >=size()
reserve(n)预分配 n 字节内存避免 append 时的多次内存重分配
shrink_to_fit()释放多余内存(C++11)capacity 接近 size

元素访问与遍历#

访问单个字符#

  • operator[]s[i]不检查越界,效率高,但越界为导致未定义行为。
  • at()s.at[i]检查越界,越界会抛出 std::out_of_range 异常。
  • front() / back():访问首尾字符。

C 风格字符串#

  • c_str():返回 const char*,以 NULL 结尾。主要用于兼容旧式 C 接口(如 printf)。
  • data():C++11 前不保证 NULL 结尾,C++11 后与 c_str() 基本一致。

遍历#

std::string s = "Hello, World";
// 1. 基于下标
for (size_t i = 0; i < s.size(); ++i) { /* ... */ }
// 2. 基于迭代器
for (auto it = s.begin(); it != s.end(); ++i) { /* ... */}
// 3. 基于范围 for 循环(C++11,推荐)
for (char c: s) { /* ... */ } // 值拷贝
for (char& c: s) { c = toupper(c) } // 引用修改

修改与操作#

操作函数示例
追加+=append()push_back()s += " World"
插入insert(pos, str)s.insert(0, "Hi ")
删除erase(pos, len), pop_back()s.erase(0, 3)
替换replace(pos, len, str)s.replace(6, 5, "C++")
清空clear()size 设置为 0,capacity 通常不变
截断/扩充resize(n)改变 size,多出的补默认值

查找与子串#

所有查找函数如果未找到,都会返回常量 std::string::npos(通常是 -1 的无符号形式,即最大的 size_t())。

std::string = "filename.txt";
// 1. 查找 find
size_t pos = s.find(".");
if (pos != std::string::npos) {
// 找到了
}
// 2. 反向查找 rfind(找最后一个出现的位置)
size_t last_dot = s.rfind(".");
// 3. 查找集合 find_first_of / find_first_not_of
// 查找第一个出现的元音字母
size_t v_pos = s.find_first_of("aeiou");
// 4. 获取子串 substr
// 参数:substr(开始位置, 长度)
std::string name = s.substr(0, pos); // "filename"
std::string ext = s.substr(pos + 1); // "txt"(省略长度则取到末尾)

类型转换#

需要包含头文件 <string>

数值转字符串 std::to_string(value)#

std::string s = std::to_string(3.14); // "3.140000"

字符串转数值#

  • std::stoi(s)int
  • std::stol(s)long
  • std::stod(s)double
  • 注意:这些函数会处理前导空格,但遇到非法字符会抛异常

现代化特性#

C++17:std::string_view#

std::string 是 “拥有者”,其拷贝开销大。std::string_view 只是一个 “观察者”(包含指针和长度),零拷贝。

函数参数尽量使用 std::string_view 代替 const std::string&,特别是当你只需要读取字符串内容时。

// 避免了临时 std::string 对象的构造和内存分配
void print_prefix(std::string_view sv) {
i f(sv.substr(0, 3) == "Pre") { // substr 也是 O(1) 操作
// ...
}
}

C++20#

填补了长期以来 C++ std::string 缺少高频接口的遗憾:

  • s.starts_with("prefix"):判断前缀
  • s.ends_with("suffix"):判断后缀
  • s.contains("sub"):判断包含

底层原理#

SSO(Small String Optimization)#

原理:为了避免短字符串频繁的堆内存分配(Heap Allocation),std::string 内部通常有一个小的栈缓冲区(通常 15 或 22 字节,取决于编译器实现)。

效果

  • 如果字符串很短(例如:"Hello"),直接存放在栈上对象内部,无 malloc,速度极快。
  • 如果字符串变长,才会在堆上分配内存,指针指向堆。

COW(Copy-On-Write)#

在 C++11 之后已被废弃。现在的标准规定 std::string 不允许使用 COW。这意味着 std::string a = b; 一定会发生深拷贝(除非使用 std::move)。

最佳实践#

参数传递#

  • 只读不拥有:C++17 前用 const std::string&,C++17 后优先用 std::string_view
  • 需要修改:用 std::string&
  • 需要拥有权(Sink):可以直接传值 std::string 并配合 std::move

性能优化#

  • 在循环中追加字符串前,先用 reserve() 预留空间(写在循环之前)。
  • 尽量避免在循环内部频繁构造 std::string
  • 换行符优先用 \n 而不是 std::endl(除非你需要立即刷新缓冲区)。

安全性#

  • 永远不要对 c_str() 返回的指针进行 delete 操作。
  • string 对象被修改或销毁后,之前获取的 iterators、references 和 pointers(如 c_str() 的结果)可能会失效。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
C++ 中的 string
https://bfmhno3.github.io/posts/string-in-cpp/
作者
bfmhno3
发布于
2026-01-03
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
bfmhno3
Hello, I'm bfmhno3.
公告
本站曾使用 Jekyll + Minimal Mistakes 主题,现已迁移至 Astro + Firefly。博客内容未变,但文章链接有所调整,请自行查找需要的文章。
分类
标签
最新动态
站点统计
文章
36
分类
2
标签
58
总字数
136,569
运行时长
0
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.7
文章许可
CC BY-NC-SA 4.0
文章目录