string的查找与子串操作(find、rfind、substr)
困难6字符串查找与截取:find、rfind、substr 使用指南
从生活中的例子引入
想象你有一本厚厚的《哈利·波特》,老师让你找出书中所有出现“魔法棒”的位置,并且每次找到后,把“魔法棒”后面的5个字抄下来。
在编程中,字符串就是那本书,find 和 rfind 就像两根手指——一根从前往后翻,一根从后往前翻,帮你快速定位到关键词。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的第二个索引是结束位置(不包含)。初学容易混淆。
常见错误提醒
-
忘记判断是否找到
- C++中如果直接使用
find返回的位置,但没找到时返回string::npos(通常是一个很大的数如4294967295),直接用这个值访问字符串会导致越界或错误。 - 正确做法:先判断
if (pos != string::npos)(C++)或if (pos != -1)(Python)。
- C++中如果直接使用
-
循环查找时忘记更新起始位置
- 例如:
pos = text.find(target);然后循环中不改变pos,会陷入死循环。 - 解决方法:每次找到后,
pos++或pos += target.length()跳过已找过的部分,避免重复查找同一个位置。
- 例如:
-
混淆 C++ 的
substr和 Python 的切片语法- C++ 写
substr(5,3)表示从下标5取3个字符。 - Python 写
text[5:8]表示取下标5到7(左闭右开)。注意第二个数字是结束索引而不是长度。
- C++ 写
-
下标越界
substr时如果pos超过字符串长度,C++ 会抛出out_of_range异常。Python 切片不会报错,但会返回空字符串或自动调整,容易隐藏错误。建议在使用前检查pos是否合理。
-
中文编码问题
- 字符串中的中文字符在UTF-8编码下每个字占3字节,
find返回的是字节索引,不是“第几个字”。对于初学者,建议先用英文或纯ASCII字符练习,理解原理后再考虑多字节编码。
- 字符串中的中文字符在UTF-8编码下每个字占3字节,
完整可运行代码示例
下面是一个综合例子,模拟统计成绩单中“优秀”出现的次数,并提取每段评语。
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),Pythontext[pos:pos+len]。
相关知识点指引
掌握了字符串查找与截取后,你可以继续学习:
- 字符串替换:
string::replace(C++)或str.replace()(Python)—— 把查到的某个子串换成别的内容。 - 插入与删除:
insert和erase(C++),str[:i] + "new" + str[i:](Python)。 - 查找多个关键词:结合循环与条件判断实现复杂搜索。
- 正则表达式:更强大的模式匹配,适合匹配复杂规则(如电话号码、邮箱)。
- 字符串匹配算法:KMP、BM 等,当字符串很长且需要高性能时有用。
就像学会了如何在图书馆里快速找到书和撕下需要的页,下一步你可以学会如何修改或替换这些内容,成为字符串处理的小能手!
例题精讲
在C++中,字符串s = "Hello, world!",执行 int pos = s.find("o", 5); 后,pos的值是?
在C++中,对字符串str = "abracadabra"调用str.rfind('a')返回的值是7。
以下C++代码使用substr提取字符串中从索引3开始长度为4的子串,请补全代码。\nstring s = "abcdefgh";\nstring sub = s.______;在C++中,已知字符串s = "C++ is fun",调用s.find("Java")的返回结果与下列哪个表达式相等?
以下代码提取字符串email中@符号之后的域名部分,请补全代码。\nstring email = "user@example.com";\nint atPos = email.find('@');\nif (atPos != string::npos) {\n string domain = email.______;\n}