字符串基础
原理
C 风格字符串的内存布局
C 风格字符串是末尾以 \0(ASCII 0)标记结束的 char 数组。"Hello" 在内存中占 6 字节:
| H | e | l | l | o | \0 |
|---|
strlen() 遍历字符直到遇到 \0,时间复杂度 O(n)。频繁调用 strlen 在循环中是常见性能陷阱。sizeof 是编译时运算符,返回数组分配的字节总数,对字符串包含 \0。
strcmp 按字典序逐个字符比较,strcpy/strcat 不检查目标缓冲区大小——是缓冲区溢出漏洞的常见来源。
std::string 的内存模型
std::string 内部布局(典型实现,GCC libstdc++):
| 字段 | 大小 | 说明 |
|---|---|---|
| 指针 → 堆缓冲区 | 8 字节 | 指向动态分配的 char 数组 |
| size (长度) | 8 字节 | 当前字符数 |
| capacity (容量) | 8 字节 | 已分配空间大小 |
短字符串优化(SSO):GCC 的 string 对象内部预留 16 字节的本地缓冲区。长度 <= 15 的字符串不分配堆内存,直接存储在对象内部——这是现代 C++ 标准库的关键优化。
动态内存与扩容
string 的容量按倍数增长(通常 2x)。当 s += "text" 导致 size > capacity 时,触发重新分配:申请新的更大堆内存 -> 拷贝原有数据 -> 释放旧内存。这就是为什么预先 reserve() 可优化大量拼接操作。
语法
声明与初始化
std::string s1; // 空字符串
std::string s2 = "Hello"; // C 字符串初始化
std::string s3("World"); // 构造函数初始化
std::string s4(5, 'A'); // "AAAAA"
std::string s5 = s2; // 拷贝
std::string s6(s2, 1, 3); // 从 s2[1] 开始的 3 个字符 "ell"基本操作
| 操作 | 代码 |
|---|---|
| 长度 | s.length() 或 s.size() (等价) |
| 判空 | s.empty() |
| 拼接 | s1 + s2, s += "abc" |
| 访问 | s[i], s.front(), s.back() |
| 比较 | s1 == s2, s1 < s2, s1.compare(s2) |
find / rfind
std::string text = "Hello World";
size_t pos = text.find("World"); // 6
size_t pos2 = text.find("Java"); // std::string::npos (未找到)
size_t pos3 = text.rfind('o'); // 7 (从右查找)
npos是静态常量,值为 size_t 的最大值,表示查找失败。
substr
std::string s = "Hello World";
std::string sub1 = s.substr(0, 5); // "Hello"
std::string sub2 = s.substr(6); // "World" (从6到末尾)replace
std::string s = "Hello World";
s.replace(6, 5, "C++"); // "Hello C++"字符串与数字互转
// 字符串 -> 数字
int n = std::stoi("12345");
double d = std::stod("3.14159");
// 数字 -> 字符串
std::string s = std::to_string(42);
std::string pi = std::to_string(3.14159); // "3.141590" (默认6位小数)
to_string对浮点数固定 6 位小数。stoi在无有效数字时抛std::invalid_argument,超出范围抛std::out_of_range。
getline 读取整行
std::string line;
std::getline(std::cin, line); // 读到换行符为止
std::getline(std::cin, line, ','); // 自定义分隔符遍历
for (char c : s) { ... } // 值拷贝
for (char& c : s) { c = toupper(c); } // 引用修改
for (size_t i = 0; i < s.size(); i++) // 下标遍历
std::cout << s[i];实践
split 实现(以逗号分隔):
#include <vector>
#include <string>
std::vector<std::string> split(const std::string& s, char delim) {
std::vector<std::string> tokens;
size_t start = 0, end;
while ((end = s.find(delim, start)) != std::string::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + 1;
}
tokens.push_back(s.substr(start));
return tokens;
}力扣:
力扣: 字符串大小写转换题 (string 遍历 + 大小写转换)
力扣: 字符串综合操作题 (string 综合操作)
力扣: 字符串遍历计题 (string 遍历 + 计数)
AI 自检提示:询问 AI “std::string 的 SSO(短字符串优化)如何工作,以及为什么要避免在循环中使用 s = s + c 而不是 s += c”。