
在C++里要查找字符串中的子串,我们最常用、也是最直接的方法,就是利用标准库std::string提供的find成员函数。它功能强大且用起来很顺手,能满足绝大多数日常需求。当然,如果对性能有极致要求或者场景特别复杂,我们也可以考虑更底层的算法,比如KMP,但那通常是“杀鸡用牛刀”了。
在C++中查找子串,std::string::find是你的首选工具。它非常直观,接受一个子串作为参数,然后返回子串在当前字符串中第一次出现的位置(索引)。如果没找到,它会返回一个特殊值std::string::npos。
#include <iostream>
#include <string>
#include <algorithm> // For std::transform
#include <cctype> // For ::tolower
#include <regex> // For std::regex
int main() {
std::string text = "Hello, C++ world! C++ is powerful.";
std::string sub = "C++";
// 基本查找
size_t pos = text.find(sub);
if (pos != std::string::npos) {
std::cout << "子串 '" << sub << "' 第一次出现在位置: " << pos << std::endl;
} else {
std::cout << "子串 '" << sub << "' 未找到." << std::endl;
}
// 从指定位置开始查找
size_t next_pos = text.find(sub, pos + sub.length());
if (next_pos != std::string::npos) {
std::cout << "子串 '" << sub << "' 第二次出现在位置: " << next_pos << std::endl;
} else {
std::cout << "子串 '" << sub << "' 再次未找到." << std::endl;
}
// 查找单个字符
size_t char_pos = text.find('o');
if (char_pos != std::string::npos) {
std::cout << "字符 'o' 第一次出现在位置: " << char_pos << std::endl;
}
// 从末尾开始查找 (std::string::rfind)
size_t last_pos = text.rfind(sub);
if (last_pos != std::string::npos) {
std::cout << "子串 '" << sub << "' 最后一次出现在位置: " << last_pos << std::endl;
}
return 0;
}这段代码展示了find的基本用法,包括从指定位置开始查找,以及rfind(从字符串末尾向前查找)。find还有接受const char*和char的重载版本,用起来都很方便。我个人觉得,对于大多数情况,这已经足够了。
std::string::find默认是大小写敏感的,这意味着"C++"和"c++"会被认为是不同的子串。但在实际应用中,我们经常需要进行大小写不敏感的查找。这块儿说起来其实有点意思,因为标准库并没有直接提供一个find_case_insensitive这样的函数。所以,我们得自己想办法。
立即学习“C++免费学习笔记(深入)”;
最常见的做法是,在查找之前,先把原字符串和目标子串都转换成统一的大小写(比如都转成小写),然后再进行查找。
#include <iostream>
#include <string>
#include <algorithm> // For std::transform
#include <cctype> // For ::tolower
// 辅助函数:将字符串转换为小写
std::string toLower(const std::string& s) {
std::string lower_s = s;
std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(),
[](unsigned char c){ return std::tolower(c); });
return lower_s;
}
int main() {
std::string text = "Hello, c++ world! C++ is powerful.";
std::string sub = "c++";
// 将原始字符串和子串都转换为小写进行查找
std::string lower_text = toLower(text);
std::string lower_sub = toLower(sub);
size_t pos = lower_text.find(lower_sub);
if (pos != std::string::npos) {
std::cout << "大小写不敏感查找:子串 '" << sub << "' 第一次出现在原字符串的位置: " << pos << std::endl;
} else {
std::cout << "大小写不敏感查找:子串 '" << sub << "' 未找到." << std::endl;
}
// 另一种更高级一点的思路是使用std::search配合自定义比较器
// 这种方式避免了创建额外的字符串副本,对性能敏感的场景可能更有优势
auto it = std::search(text.begin(), text.end(),
sub.begin(), sub.end(),
[](char c1, char c2){
return std::tolower(static_cast<unsigned char>(c1)) ==
std::tolower(static_cast<unsigned char>(c2));
});
if (it != text.end()) {
std::cout << "使用std::search和自定义比较器查找:子串 '" << sub << "' 第一次出现在原字符串的位置: " << std::distance(text.begin(), it) << std::endl;
} else {
std::cout << "使用std::search和自定义比较器查找:子串 '" << sub << "' 未找到." << std::endl;
}
return 0;
}通过toLower函数创建小写副本,这是最直接也最容易理解的方法。缺点是会创建新的字符串,如果原字符串非常大且查找频繁,这可能会带来一些内存和性能开销。
所以,我更倾向于推荐第二种方法,就是使用std::search配合一个自定义的比较器(lambda表达式)。这种方式更加灵活,它在比较字符时才进行大小写转换,避免了创建完整的临时字符串,对于追求性能的场景,这无疑是更优雅的选择。不过,说实话,对于大多数非极端性能敏感的场景,第一种方法已经足够好用了。
谈到效率,std::string::find通常采用的是一种优化的暴力匹配算法,或者一些更高级的算法,比如Boyer-Moore算法,这取决于具体的标准库实现。在最坏情况下,它的时间复杂度可能是O(N*M)(N是主串长度,M是子串长度),但在实际应用中,由于各种优化和数据分布,它的平均性能表现相当不错,对于大多数短字符串或中等长度字符串的查找,你几乎感觉不到延迟。
然而,如果你的应用场景是:
在这种情况下,std::string::find的性能瓶颈可能会显现出来。这时,KMP(Knuth-Morris-Pratt)算法就登场了。KMP算法的精髓在于它通过预处理子串(构建一个“部分匹配表”或LPS数组),在匹配失败时,能够知道已经匹配的部分可以跳过多少字符,从而避免不必要的字符回溯,将时间复杂度优化到O(N+M),这是一个显著的提升。
那么,KMP在C++中是否值得自己实现呢?我个人觉得,这得看具体情况:
std::string::find已经足够快,而且代码简洁、不易出错。自己实现KMP不仅复杂,容易引入bug,而且维护成本也高。你可能花了一天时间实现了KMP,结果发现对你的应用来说,性能提升微乎其微,甚至还不如标准库的优化实现。std::string::find成为性能瓶颈的极端场景,并且经过严格的性能分析(profiling)确认,那么自己实现或引入成熟的KMP库是合理的。但请记住,这通常是高级优化手段,不是默认选项。总之,不要过度优化。先用std::string::find,如果真的遇到性能问题,再考虑更复杂的算法。毕竟,可读性和可维护性在大多数时候比那一点点极致性能更重要。
当简单的子串查找无法满足需求时,比如你需要查找符合特定“模式”的字符串(比如邮件地址、电话号码、特定格式的日期等),正则表达式(Regular Expression, Regex)就成了你的得力助手。C++11及更高版本通过<regex>头文件提供了对正则表达式的原生支持,这玩意儿用起来,那叫一个强大。
正则表达式查找子串,不再是简单的“这个字符串里有没有那个字符串”,而是“这个字符串里有没有符合这个规则的片段”。
#include <iostream>
#include <string>
#include <regex> // 引入正则表达式库
int main() {
std::string text = "我的邮箱是 alice@example.com,她的邮箱是 bob.smith@domain.co.uk。";
// 匹配一个简单的邮箱地址模式
std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
// 使用std::regex_search查找匹配的子串
std::smatch match; // 用于存储匹配结果
std::cout << "使用正则表达式查找邮箱地址:" << std::endl;
if (std::regex_search(text, match, email_pattern)) {
// match[0] 包含整个匹配到的字符串
std::cout << "找到第一个邮箱地址: " << match[0] << std::endl;
// 继续查找下一个匹配项
std::string::const_iterator searchStart(text.cbegin());
while (std::regex_search(searchStart, text.cend(), match, email_pattern)) {
std::cout << "找到邮箱地址: " << match[0] << " (位置: " << std::distance(text.cbegin(), match[0].first) << ")" << std::endl;
searchStart = match.suffix().first; // 从上一个匹配的末尾开始继续搜索
}
} else {
std::cout << "未找到邮箱地址。" << std::endl;
}
// 另一个例子:查找数字
std::string numbers_text = "订单号是12345,数量是100,价格是99.50。";
std::regex number_pattern(R"(\d+(\.\d+)?)"); // 匹配整数或浮点数
std::cout << "\n使用正则表达式查找数字:" << std::endl;
std::smatch num_match;
std::string::const_iterator numSearchStart(numbers_text.cbegin());
while (std::regex_search(numSearchStart, numbers_text.cend(), num_match, number_pattern)) {
std::cout << "找到数字: " << num_match[0] << std::endl;
numSearchStart = num_match.suffix().first;
}
return 0;
}在上面的代码中,std::regex用来定义正则表达式模式。R"(...)"是C++11的原始字符串字面量,这对于写正则表达式非常有用,因为它避免了反斜杠的多次转义。
std::regex_search函数用于在字符串中查找第一个匹配正则表达式的子串。如果找到,结果会存储在std::smatch对象中,match[0]就是完整的匹配项。通过循环,并更新搜索起始位置(match.suffix().first),我们可以找到所有匹配的子串。
虽然正则表达式功能强大,但它也有自己的代价。相比于简单的std::string::find,正则表达式的解析和匹配过程通常更耗时。所以,我的建议是:如果find能解决问题,就用find;如果需要模式匹配,再考虑正则表达式。不要为了炫技而滥用正则表达式,它虽然好用,但有时也可能让代码变得不那么直观,而且一不小心写错了模式,调试起来也挺头疼的。
以上就是c++++如何查找字符串中的子串_c++子串查找函数与算法的详细内容,更多请关注php中文网其它相关文章!
c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号