☀️ 🌙

C++ 字符串基础与常见操作

信息学奥赛入门教程

字符串的定义与初始化

C++ string 与 C 风格字符数组

C++ string 类型 (推荐)

  • 属于 C++ 标准库 ()
  • 动态大小:长度可变,自动管理内存
  • 功能丰富:提供大量便捷的成员函数
  • 更安全:不易发生缓冲区溢出
  • 面向对象,使用更直观

C 风格字符数组 char[]

  • 本质是字符类型的数组
  • 固定大小:声明时需指定大小
  • 以空字符 '\0' 结尾标记结束
  • 需手动管理大小,易越界
  • 操作依赖 C 库函数 ()

内存结构示意

C++ string 通常内部包含指向实际字符数据的指针、当前长度和容量信息。C 风格字符串就是一块连续内存。

C++ string (简化示意)
H
e
l
l
o

(内部管理长度和容量)

C style char array[]
W
o
r
l
d
\0

(必须以 '\0' 结尾)

初始化方式

C++ string 类型提供了多种灵活的初始化方式:

  • 默认构造: 创建一个空字符串。 string s1;
  • 使用字符串字面量: string s2 = "Hello";string s3("World");
  • 拷贝构造: 从另一个 string 对象复制。 string s4 = s2;
  • 使用 N 个字符: 创建包含 N 个相同字符的字符串。 string s5(5, 'A'); // s5 为 "AAAAA"
  • 使用子串: 从另一个 string 的一部分创建。 string s6(s2, 1, 3); // s6 为 "ell" (从s2下标1开始,长度3)

字符串输入输出

输入方式对比

使用 cin >> string

  • 从标准输入流读取。
  • 遇到空白字符(空格、Tab、换行符)时停止读取。
  • 适合读取不含空格的单词或标识符。
  • 注意: 空白字符会留在输入缓冲区中。

使用 getline(cin, string)

  • 从标准输入流读取。
  • 读取整行文本,直到遇到换行符 '\n'
  • 可以读取包含空格的字符串。
  • 注意: 读取后会消耗掉行末的换行符。

重要提示:混合使用的陷阱

如果在 cin >> var; 之后立即使用 getline(cin, str);getline 可能会读取到之前 cin 遗留的换行符,导致读入一个空行。

解决方案:cin 之后、getline 之前,使用 cin.ignore(); 来清除缓冲区中残留的换行符。

int num;
string line;
cin >> num; // 输入数字后按回车,'\n' 留在缓冲区
cin.ignore(); // 消耗掉那个换行符
getline(cin, line); // 现在可以正常读取下一行了

输入效果演示

模拟输入 "Hello World" 后,观察 cingetline 的不同行为。

字符串输出

使用 cout << string 即可方便地输出整个字符串内容。

string greeting = "你好,世界!";
cout << greeting << endl; // 输出:你好,世界!

字符串常用操作

基本属性与访问

  • s.length()s.size(): 返回字符串的字符数 (无'\0')。两者等价。
  • s.empty(): 检查字符串是否为空。返回 truefalse
  • s[i]: 访问下标为 i 的字符 (类似数组,不进行边界检查)。
  • s.at(i): 访问下标为 i 的字符 (进行边界检查,越界会抛出异常)。
  • s.front(): 访问第一个字符 (等价于 s[0])。
  • s.back(): 访问最后一个字符 (等价于 s[s.length()-1])。

拼接与比较

  • 拼接:
    • s1 + s2: 返回一个新的字符串,是 s1 和 s2 的连接。
    • s1 += s2: 将 s2 追加到 s1 的末尾 (修改 s1)。
    • s.append(str): 功能类似 +=,有更多重载形式。
  • 比较:
    • ==, !=, <, >, <=, >=: 直接使用比较运算符,按字典序比较。
    • s1.compare(s2): 返回一个整数。0 表示相等,正数表示 s1 > s2,负数表示 s1 < s2。

修改、查找与子串

修改操作

  • s.insert(pos, str): 在下标 pos 处插入字符串 str
  • s.erase(pos, len): 从下标 pos 开始删除 len 个字符。
  • s.replace(pos, len, str): 将从 pos 开始的 len 个字符替换为 str
  • s.clear(): 清空字符串,使其变为空串。

查找与子串

  • s.find(sub): 查找子串 sub 首次出现的位置(下标)。找不到返回 string::npos
  • s.rfind(sub): 从右向左查找子串 sub 首次出现的位置。
  • s.find_first_of(chars): 查找 chars 中任意字符首次出现的位置。
  • s.find_last_of(chars): 查找 chars 中任意字符最后出现的位置。
  • s.substr(pos, len): 返回从下标 pos 开始,长度为 len 的子串 (不修改原串)。

操作可视化演示

当前字符串:

0A
1B
2C
3D
4E

字符串与队列模拟:按序处理字符

字符串作为字符序列

字符串本质上是一个字符序列,我们可以像遍历数组一样访问它的每个字符。这使得字符串可以与序列处理模型(如队列、栈)很好地结合。

队列 (Queue) 是一种先进先出 (FIFO - First In, First Out) 的数据结构,非常适合模拟按顺序处理任务或数据的场景。

核心操作:

  • push(element): 在队尾加入一个元素。
  • pop(): 移除队头元素。
  • front(): 获取队头元素(不移除)。
  • back(): 获取队尾元素(不移除)。
  • empty(): 判断队列是否为空。
  • size(): 获取队列中元素个数。

在 C++ 中,使用 #include 并声明 queue q; 来创建一个字符队列。

示例:使用队列判断回文字符串

回文串是指正读和反读都一样的字符串(例如 "level", "madam")。我们可以用队列和栈(或仅用双端队列/双指针)来判断。

基于队列的思路(与栈配合):

  1. 将字符串的所有字符按顺序压入一个栈 (Stack - LIFO)。
  2. 将字符串的所有字符按顺序加入一个队列 (Queue - FIFO)。
  3. 依次从栈顶弹出一个字符,从队头取出一个字符,进行比较。
  4. 如果所有对应字符都相同,则是回文串;否则不是。

简化思路(仅用队列,演示出队过程): 虽然判断回文通常不用纯队列,但我们可以用队列模拟字符的顺序处理过程。

字符串实际应用举例

字符串处理是信息学竞赛中的常见题型。以下是一些基础应用示例:

示例一:统计字符出现次数

题意:给定一个字符串和一个字符,统计该字符在字符串中出现的次数。

思路:遍历字符串的每一个字符,如果当前字符与目标字符相同,则计数器加一。

核心代码逻辑:

int count = 0;
for (char c : str) { // C++11 范围for循环
    if (c == target_char) {
        count++;
    }
}
// 或者使用传统 for 循环
for (int i = 0; i < str.length(); ++i) {
    if (str[i] == target_char) {
        count++;
    }
}

示例二:判断回文字符串

题意:判断一个给定的字符串是否为回文串。

思路 1 (双指针):设置两个指针,一个指向字符串开头 (left = 0),一个指向末尾 (right = s.length() - 1)。同步向中间移动,比较 s[left]s[right] 是否相等。如果不等,则不是回文串。如果指针相遇或交错,则是回文串。

思路 2 (反转比较):创建一个原字符串的副本,将其反转,然后比较反转后的字符串是否与原字符串相等。

双指针法演示 (字符串 "level"):

0l
1e
2v
3e
4l

比较 s[0] 和 s[4]

示例三:替换字符串中的子串

题意:给定一个主字符串 s、一个待查找子串 old_sub 和一个替换子串 new_sub,将 s 中所有出现的 old_sub 替换为 new_sub

思路:反复使用 s.find(old_sub, start_pos) 查找 old_sub 的位置。如果找到 (返回值不是 string::npos),则使用 s.replace(pos, old_sub.length(), new_sub) 进行替换。注意更新下一次查找的起始位置 start_pos,防止无限循环(如果 new_sub 包含 old_sub)。

核心代码逻辑:

size_t pos = 0; // size_t 是无符号整数类型,常用于表示大小和索引
while ((pos = s.find(old_sub, pos)) != string::npos) {
    s.replace(pos, old_sub.length(), new_sub);
    pos += new_sub.length(); // 更新下次查找的起始位置
}

总结与回顾

本节重点内容回顾

string vs char[]

string 类更安全、方便,自动管理内存,功能丰富。优先使用 string

输入方式

cin 读到空白停止,getline 读整行。注意混合使用时的换行符问题 (cin.ignore())。

核心操作

掌握长度(size), 访问([], at), 拼接(+, +=), 比较(==, <), 查找(find), 子串(substr), 修改(insert, erase, replace)。

字符串与序列

字符串可视为字符序列,能与队列、栈等数据结构结合,实现复杂逻辑。

常用技巧

遍历 (for 循环, 范围 for), 双指针 (回文判断), 查找替换模式。

string::npos

find 等函数找不到目标时返回的特殊值,用于判断是否查找成功。

学习建议

  • 多练习: 通过编写代码和解决实际问题来巩固知识。
  • 查文档: 遇到不确定的函数用法,查阅 C++ 参考手册 (如 cppreference.com)。
  • 理解原理: 了解常用函数的时间复杂度,有助于选择最优解法。
  • 注意细节: 边界条件、空字符串、查找失败 (string::npos) 等情况要考虑周全。