C++ 怎么在字符串里查找子串?附 2 种方法(find 与 rfind)

编程狮(w3cschool.cn) 2026-08-13 14:44:33 浏览数 (55)
反馈

C++ 字符串查找子串,最常用的是 string::find,它从左边开始找,找不到就返回 string::npos;想从右边找用 string::rfind。你在处理文件名、解析 URL 参数、提取日志里的关键字时,几乎一定会碰到「这段字符串里有没有某个词、它在第几个位置」这种需求。本文基于 C++17 与 gcc 实测,讲清 find 和 rfind 的基本用法、怎么判断是否找到、以及和 Java 思路的对比。记住一句话:string::find 从左往右找、string::rfind 从右往左找,两者都属于 std::string 的成员函数。看完你就能用标准库把子串查找稳稳搞定。今天这篇文章,编程狮就把这块讲透。

一、先搞清楚:find 怎么用、返回什么

C++ 教程 的字符串章节里,std::string 自带了查找成员函数。最基础的就是 find,从字符串开头向右搜索。

#include <iostream>
#include <string>


int main() {
    std::string s = "hello world";
    size_t pos = s.find("world");   // 从左边找 "world"
    if (pos != std::string::npos) {
        std::cout << "找到,位置:" << pos << "\n";  // 输出 6
    }
}

上面这段里,s.find("world") 返回子串首次出现的下标(从 0 开始),所以 "world" 在位置 6。find 还有第二个参数,可以指定「从第几个字符开始搜」,比如 s.find("o", 5) 表示从下标 5 之后才开始找。利用这个起点参数,你能轻松实现「找第二个匹配」:先用 find 拿到第一个位置 pos,再 s.find(sub, pos + 1) 从下一个字符起继续搜。另外,find 找不到时返回的是 npos 而不是负数,把返回值赋给 int 再和 -1 比较,是另一个高频 bug 源头,务必用无符号的 size_t 接收。string::find 的起点参数是它最实用也最容易被忽略的能力。

二、做法二:rfind 从右边找

rfindfind 几乎一样,只是方向相反——从字符串末尾往左搜,返回最后一次出现的位置。判断「文件名是不是以 .cpp 结尾」就靠它。

std::string file = "main.cpp";
size_t dot = file.rfind(".");
if (dot != std::string::npos && dot == file.size() - 4) {
    std::cout << "是 .cpp 文件\n";
}

上面这段里,rfind(".") 找到最后一个点号的位置,再判断它后面是不是正好 ".cpp"。除了查后缀,rfind 的第二个参数能限定「向左搜索的上界」,比如 s.rfind(":", 10) 只在前 10 个字符里从右往左找冒号,适合解析「前面固定、后面可变」的格式串。和 Java 处理子串的思路其实一脉相承,Java 子串查找 里用 indexOf / lastIndexOf 做同样的事,只是名字不同。

三、怎么判断是否「找到」

find / rfind 找不到时返回 std::string::npos,它是一个特殊的极大值(size_t 的最大值),绝不是 -1。新手最容易写成 if (pos == -1),在 size_t 是无符号类型时逻辑会出错。

if (s.find("key") == std::string::npos) {
    std::cout << "不包含 key\n";
}

判断「是否包含某子串」最稳的写法就是拿 npos 比。想顺手抽取子串,配合 substr(pos, len) 就能把命中的那段切出来。除了 find 和 rfind,std::string 还有 find_first_of、find_last_of、find_first_not_of 等成员:前者在一堆字符里找任意一个,后者找「不属于某集合」的第一个字符,做分隔符裁剪、敏感词扫描很顺手。比如要去掉字符串首尾的空白,用 find_first_not_of(' ') 和 find_last_not_of(' ') 配合 substr 就能优雅实现,不用手写循环。和 C 语言里手写 strstr 比,C 语言选择指南 能帮你理解为什么 C++ 的 string 用起来省心这么多——它把长度和内存都管好了。

四、和 Java 思路对比,以及进阶

理解了 find 和 rfind 的用法后,把它们放进真实任务里才算是真会。C++ 字符串查找最常见的需求有三类:一是从一段文本里摘出所有满足条件的子串,比如把日志里出现的所有报错编号都找出来;二是判断某个标记是不是存在于字符串中,用来做简单的格式校验;三是按分隔符把长字符串拆成若干段,例如解析用逗号分隔的配置项。这些任务单独用 find 都能做,难的是把找下一个、再找下一个的循环写对,尤其要注意起点要不断后移,否则就会在同一处死循环。实际项目里,查找往往还要和处理逻辑绑定,比如找到后顺手替换或计数,这时把 find 和后续操作放在同一个循环里,代码既短又不容易出错。下面用一张表对照 C++ 和 Java 的写法,方便你从熟悉的语言迁移过来。C++ 字符串查找 的进阶玩法远不止 find 和 rfind,但先把这两个用熟,已能覆盖日常九成需求。

需求 C++ Java 对应
从左找 s.find(sub) s.indexOf(sub)
从右找 s.rfind(sub) s.lastIndexOf(sub)
是否包含 s.find(sub) != npos s.contains(sub)
取子串 s.substr(pos, len) s.substring(pos, pos+len)

如果你在做技术选型,五大语言对比 里把 C++ 和 Java、Python 的字符串能力列得很清楚。进阶一点,C++17 还有 std::string_view,查找时不用拷贝原串,处理大文本更省;但日常 90% 的场景,findrfind 已经够用。最后提醒一个性能细节:find 每次都是从给定起点线性扫描的,在超长字符串里高频查找,可以考虑先把要找的子串预编译或用更专业的算法库,不过对绝大多数业务代码来说这属于过度优化,不必过早担心。

实战:查找并替换所有匹配

find 只能拿到「下一个」位置,要处理全部,用循环把起点不断后移即可:

size_t pos = s.find("bug");
while (pos != std::string::npos) {
    s.replace(pos, 3, "fix");       // 把 "bug" 换成 "fix"
    pos = s.find("bug", pos + 3);   // 从替换后的位置继续
}

这里每次替换后都要用「新位置 + 被替换长度」作为下一次起点,否则会卡在同一处死循环。这个模式在处理日志清洗、模板渲染时非常常见。

五、踩坑清单

  • 现象:判断 pos == -1 结果不对。 原因possize_t 无符号,-1 会变成超大正数。 办法:永远用 == std::string::npos 判断。
  • 现象substrout_of_range原因:起始位置或长度越界。 办法:先确认 find 返回的不是 npos,再调用 substr
  • 现象:中文子串查找位置对不上。 原因find 按「字符(code point)」计数,C++ 里中文占多个字节但算一个 char?实际 std::string 按字节存 UTF-8,一个汉字占 3 字节,find 返回的「位置」是字节偏移。 办法:需要按「字符」定位时,改用支持 Unicode 的库或 std::u32string
  • 现象:find 到位置后 substr 切出来是乱码。 原因:把「字节偏移」当成了「字符下标」去 substr,中文被从中间切断。 办法:处理中文时统一用基于字符的遍历或现成 UTF-8 库,不要直接拿 find 的返回值当地符索引。

图:在终端运行上述 find / rfind 示例的输出结果(需人工补图)

总结

C++ 字符串查找子串,靠 string::find 从左、string::rfind 从右,两者找不到都返回 string::npos。要点带走:判断「找到没」必须比 npos 而不是 -1;取子串用 substr 且要先确认不是 npos;中文按字节计偏移,跨字符场景要换 Unicode 方案。下一步,把 find 和 substr 组合,你就能写出文件名解析、参数提取这类实用小工具。

延伸学习

想把 C++ 基础打牢,可以按这个顺序来:

  1. 先过一遍 C++ 入门课程,把字符串、容器、STL 练熟;
  2. 想系统学 C 与 C++ 的底层能力,C语言/C++零基础软件入门 是边学边写的形式,适合巩固。

常见问题

Q:find 找不到时返回 -1 吗?
A:不是。它返回 string::npos,本质是无符号极大值。写成 == -1 在 size_t 下会出错,务必用 == string::npos

Q:find 和 rfind 的第二个参数有什么用?
A:指定从哪个下标开始搜。find 的第二个参数是「起点」,rfind 的第二个参数是「终点(向左搜的上界)」,都能缩小搜索范围。

Q:C++ 怎么判断字符串是否包含子串?
A:最稳的写法是 s.find(sub) != std::string::npos。C++20 起 std::string 还有 contains 成员可以直接用,老标准用 find 比 npos 即可。

C C++

1 人点赞