好的,我们来对 C++ 中的字符串操作函数进行一次全面而详细的讲解。

我们将分为三个主要部分:

  1. C 风格字符串的操作函数 (<cstring>头文件):这是从 C 语言继承来的,理解它们有助于了解 C++ 的历史和与 C 库的交互,但在现代 C++ 中应谨慎使用。
  2. std::string 类的成员函数 (<string>头文件):这是现代 C++ 中处理字符串的首选方式,安全、方便、功能强大。
  3. 作用于 std::string 的 STL 算法 (<algorithm>头文件):展示了 std::string作为标准容器的强大之处。

Part 1: C 风格字符串的操作函数 (头文件 <cstring>)

这些函数操作以 \0 (空字符) 结尾的字符数组。它们大多不进行边界检查,是导致缓冲区溢出等安全问题的重灾区,应极力避免在 C++ 新代码中使用。

函数原型 (简化)描述风险与注意
size_t strlen(s)计算字符串 s 的长度,不包括 \0效率低 (O(n)),每次都需从头遍历。
strcpy(dest, src)将字符串 src 复制dest极度危险!不检查 dest 空间,极易导致缓冲区溢出。
strncpy(dest, src, n)最多复制 srcn 个字符到 dest如果 src 长度>=n不会自动添加\0,需要手动处理。
strcat(dest, src)将字符串 src 追加dest 的末尾。极度危险!不检查 dest 剩余空间,极易导致缓冲区溢出。
strncat(dest, src, n)最多追加 srcn 个字符到 dest,并总是在最后添加一个\0相对安全,但仍需程序员自己计算好空间。
int strcmp(s1, s2)比较 s1s2。返回 0 (相等), <0 (s1<s2), >0 (s1>s2)。注意不能用 == 比较 C 风格字符串内容,那是在比较指针地址。
char* strchr(s, c)s 中查找字符 c 首次出现的位置,返回指向该位置的指针,否则返回 nullptr
char* strstr(s1, s2)s1 中查找子串 s2 首次出现的位置,返回指向该位置的指针,否则返回 nullptr

示例(仅为演示,不推荐使用):

#include <cstring>
#include <iostream>
 
int main() {
    char dest[20] = "Hello";
    const char* src = ", World!";
    
    std::cout << "Length: " << strlen(dest) << std::endl; // 输出 5
    
    strcat(dest, src); // 危险操作,但这里空间足够
    std::cout << dest << std::endl; // 输出 "Hello, World!"
    
    if (strcmp(dest, "Hello, World!") == 0) {
        std::cout << "They are equal." << std::endl;
    }
}

Part 2: std::string 类的成员函数 (头文件 <string>)

这是现代 C++ 的标准做法。std::string 对象会自动管理内存,并提供了丰富的成员函数。

1. 修改与操作 (Modification)

函数 (简化)描述示例
s.append(str)在字符串 s 的末尾追加 str。(+= 运算符更常用)s.append("tail");
s.push_back(c)在末尾追加一个字符 cs.push_back('!');
s.assign(str)s 的内容替换为 str。 (= 运算符更常用)s.assign("new content");
s.insert(pos, str)在索引 pos 处插入字符串 strs.insert(5, " C++");
s.erase(pos, len)从索引 pos 处开始,删除 len 个字符。s.erase(0, 6);
s.replace(pos, len, str)从索引 pos 处开始,将 len 个字符替换为 strs.replace(0, 4, "Hi");
s.substr(pos, len)返回一个从 pos 开始,长度为 len新子字符串std::string sub = s.substr(6, 5);
s.clear()清空字符串,使其变为空。s.clear();
s.resize(n, c)改变字符串大小为 n。若变长,用字符 c 填充新空间。s.resize(10, ' ');

示例:

#include <string>
#include <iostream>
 
int main() {
    std::string s = "Hello C++";
    std::cout << "Original: " << s << std::endl;
 
    s.insert(6, "Awesome ");
    std::cout << "After insert: " << s << std::endl; // "Hello Awesome C++"
 
    s.replace(s.find("C++"), 3, "World");
    std::cout << "After replace: " << s << std::endl; // "Hello Awesome World"
    
    std::string sub = s.substr(6, 7);
    std::cout << "Substring: " << sub << std::endl; // "Awesome"
    
    s.erase(5, 8);
    std::cout << "After erase: " << s << std::endl; // "Hello World"
}

2. 查找 (Searching)

所有查找函数在找不到时,都会返回一个特殊静态成员 std::string::npos

函数 (简化)描述
s.find(str, pos=0)从索引 pos 开始,查找 str 首次出现的位置。
s.rfind(str, pos=npos)从索引 pos 开始(或末尾),反向查找 str 首次出现的位置。
s.find_first_of(chars, pos=0)pos 开始,查找 chars任意一个字符首次出现的位置。
s.find_last_of(chars, pos=npos)pos 开始,反向查找 chars任意一个字符首次出现的位置。
s.find_first_not_of(chars, pos=0)pos 开始,查找第一个不包含在 chars 中的字符。
s.find_last_not_of(chars, pos=npos)pos 开始,反向查找第一个不包含在 chars 中的字符。

示例:

std::string text = "file.cpp - a C++ source file.";
const std::string vowels = "aeiou";
 
size_t dot_pos = text.find('.');
if (dot_pos != std::string::npos) {
    std::string extension = text.substr(dot_pos + 1);
    std::cout << "Extension: " << extension << std::endl; // cpp - a C++ source file.
}
 
size_t first_vowel = text.find_first_of(vowels);
std::cout << "First vowel at: " << first_vowel << std::endl; // 1 ('i')

3. 转换 (Conversion)

函数描述
s.c_str()返回一个指向 s 内部数据的 const char* C 风格字符串。用于与 C API 交互。
std::to_string(n)(全局函数) 将数字 n 转换为 std::string。 (C++11)
std::stoi(s)(全局函数) 将 std::string s 转换为 int。 (C++11)
std::stod(s)(全局函数) 将 std::string s 转换为 double。 (C++11)

Part 3: std::string 与 STL 算法 (头文件 <algorithm>)

std::string 是一个标准的序列容器,因此几乎所有的 STL 算法都可以作用于它。这极大地扩展了它的能力。

你需要提供迭代器 s.begin() s.end() 作为算法的操作范围。

算法示例描述示例代码
std::reverse(begin, end)反转范围内的元素。std::reverse(s.begin(), s.end());
std::sort(begin, end)对范围内的元素进行排序。std::sort(s.begin(), s.end());
std::transform(b1, e1, b2, op)对范围1的每个元素应用操作op,结果存入范围2。std::transform(s.begin(), s.end(), s.begin(), ::toupper);
std::remove(begin, end, val)移除范围内所有等于 val 的元素(不改变大小)。s.erase(std::remove(s.begin(), s.end(), ' '), s.end()); 1
std::find(begin, end, val)在范围内查找 val,返回迭代器或 end()auto it = std::find(s.begin(), s.end(), 'x');
std::count(begin, end, val)统计 val 在范围内出现的次数。int count = std::count(s.begin(), s.end(), 'a');

示例:

#include <string>
#include <algorithm>
#include <iostream>
#include <cctype>
 
int main() {
    std::string s = "Hello World";
 
    // 1. 全部转为大写
    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); });
    std::cout << "Uppercase: " << s << std::endl; // "HELLO WORLD"
 
    // 2. 反转字符串
    std::reverse(s.begin(), s.end());
    std::cout << "Reversed: " << s << std::endl; // "DLROW OLLEH"
    
    // 3. 移除所有空格
    s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
    std::cout << "No spaces: " << s << std::endl; // "DLROWOLLEH"
}

总结与最佳实践

  1. 首选 std::string:在所有 C++ 项目中,都应默认使用 std::string 来处理字符串。它安全、高效且功能全面。
  2. 避免 <cstring>:除非你必须与一个只接受 char* 的 C 风格旧 API 交互,否则不要使用 <cstring> 中的函数。
  3. 善用运算符std::string 重载的 +, +=, ==, <, > 等运算符让代码更直观、简洁。
  4. 掌握查找函数和 nposfind 系列函数是字符串处理的核心,理解并正确使用 std::string::npos 进行结果判断是必须的。
  5. 发挥 STL 算法的威力:对于复杂的、非标准的字符串操作(如大小写转换、条件删除、字符排序等),第一时间想到使用 <algorithm> 中的通用算法,而不是自己手写循环。这能让你的代码更标准、更健壮、也更简洁。

Footnotes

  1. 这是著名的 Erase-Remove Idiomstd::remove 只会将不被移除的元素向前移动,并返回一个新的逻辑终点,需要配合 s.erase() 才能真正缩短字符串。