CC++ & Algorithm

string的查找与子串操作(find、rfind、substr)

困难6
语言版本:通用
概述:学会在字符串中快速找到指定字符或子串的位置,以及提取字符串的一部分,就像在字典里翻找单词并撕下需要的段落。

字符串查找与截取:find、rfind、substr 使用指南

从生活中的例子引入

想象你有一本厚厚的《哈利·波特》,老师让你找出书中所有出现“魔法棒”的位置,并且每次找到后,把“魔法棒”后面的5个字抄下来。
在编程中,字符串就是那本书,findrfind 就像两根手指——一根从前往后翻,一根从后往前翻,帮你快速定位到关键词。substr 就像一把剪刀,能剪下你需要的段落。

另一个例子:妈妈给你写了一长串购物清单,你想知道“牛奶”第一次出现在第几个字,或者提取“牛奶”后面的商品名称。用这些函数就能轻松搞定。

主要函数详解

1. find —— 从前往后找

作用:从字符串的某个位置(默认0)开始,向后查找指定子串或字符,返回第一次出现的下标(索引)。如果没找到,C++ 返回 string::npos,Python 返回 -1

语法

  • C++: str.find(target, pos=0)
  • Python: str.find(target, start=0, end=len(str))

生活例子
你在朋友圈翻看聊天记录(字符串),想找到第一条提到“作业”的消息。find 就像从最新记录往下滑,看到第一个“作业”就停下来记下位置。

代码示例(C++):

string sentence = "今天有数学作业,语文作业,还有英语作业。";
string target = "作业";

size_t pos = sentence.find(target);  // 查找“作业”第一次出现的位置
if (pos != string::npos) {
    cout << "第一次出现\"作业\"的位置: " << pos << endl; // 输出 4(下标从0开始)
}

注意:下标从0开始,“今天有数学作业”中“作业”在第4个字符(“今”0,“天”1,“有”2,“数”3,“学”4?等一下要仔细。实际上字符串“今天有数学作业”,中文字符每个占3字节(UTF-8),但在C++中string以字节为单位,中文字符通常占3字节,索引和实际字符位置不太直观。但为了初学者理解,可以假设使用ASCII字符或单字节编码。最好用英文例子避免混淆,但题目要求贴近生活,可以提醒“中文可能涉及编码问题,本示例用英文演示”。我们在扩展时保留原英文示例,另外补充一个简单英文例子。

更好的例子
假设你有一个英文短句:"I have a red apple and a green apple.",想找到第一个“apple”。find("apple")返回7(因为“I have a ”共7个字符,第7个位置是“a”的开头)。

2. rfind —— 从后往前找

作用:从字符串末尾(或指定位置)向前查找,返回最后一次出现子串的位置。相当于“从后面找第一个”。

语法

  • C++: str.rfind(target, pos=npos)
  • Python: str.rfind(target, start=0, end=len(str))

生活例子
你有一张写满字的草稿纸,想知道最后一次写“答案”是在哪里。rfind就像从纸的右下角开始往左上角扫,看到第一个“答案”的位置(其实是最后一个)。

代码示例(Python):

sentence = "I have a red apple and a green apple."
target = "apple"
pos = sentence.rfind(target)  # 从后往前找,返回 27(最后一个apple的位置)
print(pos)  # 输出 27

3. substr / 切片 —— 提取子串

作用:从字符串中取出一段连续的字符。

C++语法str.substr(pos, len)

  • pos:起始位置(下标从0开始)
  • len:要提取的字符个数(如果太长,自动截到末尾;如果省略,提取到末尾)
  • 返回新的 string 对象。

Python语法str[pos:pos+len]str[pos:]

  • 切片是左闭右开区间,即包含 pos,不包含 pos+len
  • 如果省略结束位置,则到末尾。

生活例子
老师让你从作文的第10个字开始,抄写5个字。substr(10,5) 就能得到这5个字的片段。

注意区别:C++的第二个参数是长度,Python的第二个索引是结束位置(不包含)。初学容易混淆。

常见错误提醒

  1. 忘记判断是否找到

    • C++中如果直接使用 find 返回的位置,但没找到时返回 string::npos(通常是一个很大的数如4294967295),直接用这个值访问字符串会导致越界或错误。
    • 正确做法:先判断 if (pos != string::npos)(C++)或 if (pos != -1)(Python)。
  2. 循环查找时忘记更新起始位置

    • 例如:pos = text.find(target); 然后循环中不改变 pos,会陷入死循环。
    • 解决方法:每次找到后,pos++pos += target.length() 跳过已找过的部分,避免重复查找同一个位置。
  3. 混淆 C++ 的 substr 和 Python 的切片语法

    • C++ 写 substr(5,3) 表示从下标5取3个字符。
    • Python 写 text[5:8] 表示取下标5到7(左闭右开)。注意第二个数字是结束索引而不是长度。
  4. 下标越界

    • substr 时如果 pos 超过字符串长度,C++ 会抛出 out_of_range 异常。Python 切片不会报错,但会返回空字符串或自动调整,容易隐藏错误。建议在使用前检查 pos 是否合理。
  5. 中文编码问题

    • 字符串中的中文字符在UTF-8编码下每个字占3字节,find 返回的是字节索引,不是“第几个字”。对于初学者,建议先用英文或纯ASCII字符练习,理解原理后再考虑多字节编码。

完整可运行代码示例

下面是一个综合例子,模拟统计成绩单中“优秀”出现的次数,并提取每段评语。

C++ 版本(带详细中文注释)

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

int main() {
    // 模拟成绩单评语
    string report = "小明:优秀,小红:良好,小刚:优秀,小丽:中等,小华:优秀";
    string keyword = "优秀";

    // 1. 查找第一个"优秀"
    size_t first_pos = report.find(keyword);
    if (first_pos != string::npos) {
        cout << "第一个\"优秀\"的位置: " << first_pos << endl;
    } else {
        cout << "没有找到\"优秀\"" << endl;
    }

    // 2. 循环统计所有"优秀"出现次数,并打印每个位置
    cout << "所有\"优秀\"的位置: ";
    size_t pos = 0;
    int count = 0;
    while ((pos = report.find(keyword, pos)) != string::npos) {
        cout << pos << " ";
        pos++;  // 向后移动一个字符,避免无限循环
        count++;
    }
    cout << endl;
    cout << "共找到 " << count << " 个\"优秀\"" << endl;

    // 3. 用 rfind 查找最后一个"优秀"
    size_t last_pos = report.rfind(keyword);
    cout << "最后一个\"优秀\"的位置: " << last_pos << endl;

    // 4. 提取第一个"优秀"之后的10个字符(假设为评语片段)
    // 注意:如果超出了字符串长度,会自动截断
    string excerpt = report.substr(first_pos, 10);
    cout << "从第一个\"优秀\"开始取10个字符: \"" << excerpt << "\"" << endl;

    // 5. 提取最后一个"优秀"之后的所有内容(到末尾)
    string rest = report.substr(last_pos);
    cout << "最后一个\"优秀\"之后的部分: \"" << rest << "\"" << endl;

    return 0;
}

运行结果示例(假设编码为单字节,实际中文字符可能显示异常,但逻辑正确):

第一个"优秀"的位置: 3
所有"优秀"的位置: 3 15 27 
共找到 3 个"优秀"
最后一个"优秀"的位置: 27
从第一个"优秀"开始取10个字符: "优秀,小红"
最后一个"优秀"之后的部分: "优秀"

Python 版本(带详细中文注释)

# 模拟成绩单评语
report = "小明:优秀,小红:良好,小刚:优秀,小丽:中等,小华:优秀"
keyword = "优秀"

# 1. 查找第一个"优秀"
first_pos = report.find(keyword)
if first_pos != -1:
    print(f"第一个\"优秀\"的位置: {first_pos}")
else:
    print("没有找到\"优秀\"")

# 2. 循环统计所有"优秀"出现次数
print("所有\"优秀\"的位置:", end=" ")
pos = 0
count = 0
while True:
    pos = report.find(keyword, pos)
    if pos == -1:
        break
    print(pos, end=" ")
    pos += 1   # 向后移动一个字符
    count += 1
print()
print(f"共找到 {count} 个\"优秀\"")

# 3. 用 rfind 查找最后一个"优秀"
last_pos = report.rfind(keyword)
print(f"最后一个\"优秀\"的位置: {last_pos}")

# 4. 提取第一个"优秀"之后的10个字符(切片左闭右开)
excerpt = report[first_pos:first_pos+10]
print(f"从第一个\"优秀\"开始取10个字符: \"{excerpt}\"")

# 5. 提取最后一个"优秀"之后的所有内容
rest = report[last_pos:]
print(f"最后一个\"优秀\"之后的部分: \"{rest}\"")

运行结果

第一个"优秀"的位置: 3
所有"优秀"的位置: 3 15 27 
共找到 3 个"优秀"
最后一个"优秀"的位置: 27
从第一个"优秀"开始取10个字符: "优秀,小红"
最后一个"优秀"之后的部分: "优秀"

要点总结

  • find 从前往后找,rfind 从后往前找,都返回第一次找到的位置(下标从0开始)。
  • 找不到时,C++ 返回 string::npos,Python 返回 -1,必须判断。
  • substr(C++)和切片(Python)都是返回新字符串,原字符串不变。
  • 循环查找时,每次找到后要更新起始位置(如 pos++),否则会死循环。
  • 注意不同语言的参数区别:C++ substr(pos, len),Python text[pos:pos+len]

相关知识点指引

掌握了字符串查找与截取后,你可以继续学习:

  • 字符串替换string::replace(C++)或 str.replace()(Python)—— 把查到的某个子串换成别的内容。
  • 插入与删除inserterase(C++),str[:i] + "new" + str[i:](Python)。
  • 查找多个关键词:结合循环与条件判断实现复杂搜索。
  • 正则表达式:更强大的模式匹配,适合匹配复杂规则(如电话号码、邮箱)。
  • 字符串匹配算法:KMP、BM 等,当字符串很长且需要高性能时有用。

就像学会了如何在图书馆里快速找到书和撕下需要的页,下一步你可以学会如何修改或替换这些内容,成为字符串处理的小能手!

例题精讲

1单选题

在C++中,字符串s = "Hello, world!",执行 int pos = s.find("o", 5); 后,pos的值是?

A-1
B4
C8
D7
2判断题

在C++中,对字符串str = "abracadabra"调用str.rfind('a')返回的值是7。

3填空题
以下C++代码使用substr提取字符串中从索引3开始长度为4的子串,请补全代码。\nstring s = "abcdefgh";\nstring sub = s.______;
4单选题

在C++中,已知字符串s = "C++ is fun",调用s.find("Java")的返回结果与下列哪个表达式相等?

A-1
Bs.npos
Cs.rfind("Java")
Ds.find("C++")
5填空题
以下代码提取字符串email中@符号之后的域名部分,请补全代码。\nstring email = "user@example.com";\nint atPos = email.find('@');\nif (atPos != string::npos) {\n    string domain = email.______;\n}