字符串基础

建议先阅读:10_函数基础 09_数组基础

原理

C 风格字符串的内存布局

C 风格字符串是末尾以 \0(ASCII 0)标记结束的 char 数组。"Hello" 在内存中占 6 字节:

Hello\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”。