CC++ & Algorithm

C++字符串查找与子串

困难20
语言版本:C++Python
概述:学习如何在一个字符串中查找关键词或提取部分内容。

在字符串里“寻宝”——C++ 查找与提取子串方法详解

你有一大段文字,想在里面找到某个词语的位置,或者截取一小段出来,就像从一长串珍珠项链中找出特定颜色的珠子,或者从一整本书里撕下几页。C++ 的 string 类型提供了两个非常实用的工具:find() 可以帮你在字符串里“定位”一个子串;substr() 可以帮你“切出”一段子串。下面我们一步步学明白。


1. 查找子串:用 find() 定位关键词

基本用法
find(子串) 返回子串第一次出现的位置(位置从 0 开始数)。如果找不到,它会返回一个特殊值 string::npos,你可以把它理解成“没找到”。

string sentence = "今天天气真好,我们去公园玩。";
int pos = sentence.find("公园");  // 查找子串"公园"在句子中的位置
if (pos != string::npos) {
    cout << "“公园”在第 " << pos << " 个位置" << endl;
} else {
    cout << "没找到" << endl;
}

从指定位置开始查找
有时候你想跳过前面已经找过的内容,从中间某个位置开始搜。find(子串, 起始位置) 就可以:

string text = "苹果,香蕉,苹果,橘子";
int pos1 = text.find("苹果");         // 从0开始,找到第0个位置(第一个"苹果")
int pos2 = text.find("苹果", pos1+1); // 从第1个字符之后开始,找到第6个位置(第二个"苹果")

反向查找 rfind()
如果想从字符串尾部往前找最后一次出现的子串,用 rfind(),它跟 find() 用法完全一样,只是搜索方向相反。比如在一堆作业文件名中找最后一个“.pdf”:

string files = "report.pdf, homework.pdf, notes.txt";
int last_dot = files.rfind(".pdf");  // 从右往左找,找到最后一个".pdf"的位置
if (last_dot != string::npos) {
    cout << "最后一个 .pdf 文件起始于位置 " << last_dot << endl;
}

常见错误
很多新手直接用 intfind() 的返回值,但 string::npos 是一个非常大的数(通常等于 size_t 的最大值),如果赋值给 int 可能会变成负数,导致条件判断出错。更安全的做法是使用 size_t 类型来存储位置:

size_t pos = sentence.find("公园"); // 用 size_t 代替 int

不过对中小学生来说,用 int 并配合 if (pos != -1) 也是可以接受的,但一定要记住必须检查返回值,否则直接用 pos 去取字符可能导致程序崩溃。


2. 提取子串:用 substr() 切出一段

基本语法
substr(起始位置, 长度) 从原字符串中复制出一段新的字符串,原字符串不变。就像从手链上剪下几颗珠子。

string str = "HelloWorld";
string sub = str.substr(0, 5);  // 从位置0开始取5个字符,得到 "Hello"
string sub2 = str.substr(5);    // 从位置5开始取到末尾,得到 "World"

如果起始位置太靠后了怎么办?
如果 起始位置 超过了字符串的长度,程序会抛出一个异常(out_of_range)。所以使用前最好检查一下位置是否在有效范围内:

string msg = "ABCDE";
int start = 10;
if (start < msg.length()) {
    string part = msg.substr(start, 100); // 即使长度超出,也只取到末尾
} else {
    cout << "起始位置超出字符串长度!" << endl;
}

生活中的例子:从日期字符串“2025-04-18”中提取月份。

string date = "2025-04-18";
int first_dash = date.find('-');      // 找到第一个'-'的位置(4)
int second_dash = date.find('-', first_dash + 1); // 从第一个'-'后面找第二个'-'(7)
string month = date.substr(first_dash + 1, second_dash - first_dash - 1);
cout << "月份是:" << month << endl;  // 输出 "04"

3. 完整实例:从邮箱提取用户名(保留原有)

假设邮箱是 zhang@example.com,想取出“@”前面的部分。

string email = "zhang@example.com";
int at = email.find("@");             // 查找'@'的位置
if (at != string::npos) {
    string username = email.substr(0, at); // 从开头截取到'@'之前
    cout << "用户名是:" << username << endl;
}

拓展:如果想同时提取用户名和域名,可以这样做:

string email = "zhang@example.com";
size_t at_pos = email.find('@');        // 用 size_t 更安全
if (at_pos != string::npos) {
    string name = email.substr(0, at_pos);
    string domain = email.substr(at_pos + 1); // 从@之后到末尾
    cout << "用户名:" << name << ", 域名:" << domain << endl;
}

4. 完整示例:统计一篇文章中某个词出现多少次(保留并扩展)

下面的程序可以统计一个关键词在一段文字中出现了几次。注意我们用了 find() 的循环查找,每次找到后把 pos 向后移动一位,避免重复查找同一个位置。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string article = "猫猫和狗狗是好朋友。猫猫喜欢猫薄荷,狗狗喜欢啃骨头。";
    string keyword = "猫猫";
    
    int count = 0;
    size_t pos = 0;          // 当前查找的起始位置,初始为0
    while ( (pos = article.find(keyword, pos)) != string::npos ) {
        count++;
        pos++;  // 移动到下一个位置继续找,这样不会卡在原地
    }
    cout << "“" << keyword << "”出现了 " << count << " 次" << endl;
    return 0;
}

注意:上面的代码中 pos++ 只是让查找位置前进一位。如果关键词本身长度大于 1(比如“猫猫”长度为4),理论上我们可以 pos += keyword.length() 来跳得更远,避免在已经匹配过的内部重复查找。但如果关键词可能重叠(例如“aaa”中找“aa”,第一次找位置0,第二次应该从位置1开始),pos++ 才是正确的。所以要根据实际需求调整步长。


5. 新手常犯的错误

  • 忘记判断 npos:直接使用 find() 返回的位置去调用 substr(),如果没找到,substr(npos, ...) 会崩溃。必须先检查返回值。
  • 混淆 substr()find() 的第二个参数find(子串, 起始位置) 的第二个参数是开始查找的位置;substr(起始位置, 长度) 的第二个参数是要截取的字符数。很多同学会把它们记反。
  • int 存储位置时出现负数string::npos 在有些环境下是 -1,但直接赋值给 int 没问题;但在 64 位系统下 npos 可能是 18446744073709551615,赋值给 int 会变成 -1。所以判断 pos == -1 可能有效,但更规范的做法是用 pos == string::npos
  • substr() 起始位置超出字符串长度:会抛出异常,导致程序终止。建议先检查 start < str.length()

6. 相关知识点指引

  • 字符串长度str.length()str.size(),获取字符串里有多少个字符。
  • 字符串比较==, !=, <, > 可以直接比较两个字符串。
  • 字符串拼接:用 + 运算符把多个字符串连起来。
  • 字符串遍历:用 for (char c : str) 或者下标 str[i] 逐个字符访问。
  • 更多查找方法find_first_of()(找任意一个指定字符)、find_last_not_of()(反向找非指定字符)等,可以帮你在更复杂的场景中快速定位。

掌握了 findsubstr,你就能像熟练的图书管理员一样,在字符串的汪洋里迅速找到你需要的那一段文字!试试用它们写一个自己的“文本搜索器”吧。

例题精讲

1单选题

在C++中,对于字符串 s = "hello world",调用 s.find("world") 的返回值是?

A0
B5
C6
D7
2判断题

在C++中,如果 string 对象的 find 方法返回一个大于字符串长度(s.length())的值,则表明未找到子串。

3填空题
给定字符串 s = "C++ Programming",要提取子串 "Program",请补全代码:
string sub = s.______(5, 7);
4单选题

对于字符串 s = "abacaba",s.find('a') 和 s.rfind('a') 的返回值分别是?

A0 和 6
B0 和 0
C6 和 0
D0 和 4
5填空题
以下代码用于判断字符串 s 中是否包含子串 "key",请补全:
if (s.find("key") != ______) { cout << "found"; }