最直接的方法是用 std::string::find 循环查找,通过控制 pos += 1(重叠)或 pos += sub.length()(非重叠)实现计数,需防死循环和无符号溢出;std::search 更泛化但不支持重叠;regex 过重且慢,空串需特殊处理。

用 std::string::find 循环查找最直接
标准库没提供“统计子串出现次数”的现成函数,std::string::find 是最稳妥的起点。它返回首次匹配位置,找不到时返回 std::string::npos,配合偏移迭代就能覆盖全部重叠/非重叠情况。
关键在于控制搜索起始位置:每次找到后,下一次搜索从 pos + 1(允许重叠)还是 pos + len(跳过已匹配部分)开始——这取决于业务是否需要统计重叠匹配(比如 "aaa" 中 "aa" 出现 2 次)。
常见错误:忘记更新 pos 导致死循环;或写成 pos = s.find(sub, pos + sub.length()) 却没判断 npos 就继续加法,引发无符号整数溢出(npos 是极大值)。
示例(非重叠计数):
立即学习“C++免费学习笔记(深入)”;
size_t count = 0;
size_t pos = 0;
while ((pos = s.find(sub, pos)) != std::string::npos) {
++count;
pos += sub.length(); // 跳过已匹配段
}
用 std::search 配合迭代器更泛化
当字符串是 std::vector<char>、std::array 或自定义容器时,std::search 比 find 更通用。它接受任意双向迭代器,底层用类似 KMP 的优化算法(具体实现依赖标准库),对长文本+短模式有潜在性能优势。
但注意:std::search 默认只找第一个匹配,需手动推进迭代器;且不支持重叠搜索——找到后必须把 first 移动到 result + sub.size() 才能继续,否则会反复匹配同一位置。
示例(等价于非重叠 find):
auto first = s.begin(), last = s.end();
auto sub_first = sub.begin(), sub_last = sub.end();
size_t count = 0;
auto it = std::search(first, last, sub_first, sub_last);
while (it != last) {
++count;
first = it + sub.size(); // 关键:跳过整个匹配段
it = std::search(first, last, sub_first, sub_last);
}
避免用 std::regex 做简单计数
正则表达式适合复杂模式(如忽略大小写、带通配符),但纯字面量子串匹配时,std::regex 开销显著更高:编译正则、构造状态机、回溯匹配……实测在百万字符文本中,find 比 regex_iterator 快 5–10 倍。
更隐蔽的问题是默认行为:std::regex 不自动处理重叠匹配,且 std::regex_constants::match_default 会尝试所有可能分支,进一步拖慢速度。
除非你真需要类似 "a.*b" 这种模糊匹配,否则别为简单子串计数引入 <regex> 头文件和运行时开销。
- 错误示范:
std::regex r("abc");→ 完全没必要,"abc"就是字面量 - 漏掉异常处理:
std::regex构造可能抛std::regex_error,而find完全无异常
边界情况必须检查空字符串
sub 为空时,std::string::find 行为未定义(多数实现返回 0,但 C++ 标准允许任意结果)。实际代码中若输入不可控,必须显式判断:
永远先检查:
if (sub.empty()) {
// 通常约定:空串在非空串中出现 n+1 次(n 个位置可插入),但业务逻辑可能要求返回 0
return s.empty() ? 1 : s.length() + 1;
}
另一个易漏点:s 为空但 sub 非空 → find 直接返回 npos,计数为 0,这个逻辑正确,但测试时容易忽略空输入组合。
还有编码陷阱:如果字符串含多字节 UTF-8 字符,std::string 层面的 find 只做字节匹配,不是语义层面的“子串”。此时需用 ICU 或 Boost.Text,而非标准库函数。


















