C++字符串查找与子串
困难20在字符串里“寻宝”——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;
}
常见错误
很多新手直接用int存find()的返回值,但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()(反向找非指定字符)等,可以帮你在更复杂的场景中快速定位。
掌握了 find 和 substr,你就能像熟练的图书管理员一样,在字符串的汪洋里迅速找到你需要的那一段文字!试试用它们写一个自己的“文本搜索器”吧。
例题精讲
在C++中,对于字符串 s = "hello world",调用 s.find("world") 的返回值是?
在C++中,如果 string 对象的 find 方法返回一个大于字符串长度(s.length())的值,则表明未找到子串。
给定字符串 s = "C++ Programming",要提取子串 "Program",请补全代码:
string sub = s.______(5, 7);对于字符串 s = "abacaba",s.find('a') 和 s.rfind('a') 的返回值分别是?
以下代码用于判断字符串 s 中是否包含子串 "key",请补全:
if (s.find("key") != ______) { cout << "found"; }