HOWTO · C

C 语言中检查字符串是否包含子字符串

本文演示了如何在 C 语言中检查一个字符串是否包含子字符串。

本页内容

要检查以空字符结尾的 C 字符串是否包含一个区分大小写的精确子字符串,请调用 <string.h> 中的 strstr。它会返回首次匹配位置的指针;如果没有匹配项,则返回 NULL。下面的示例均已执行,输出均为 Substring found.。

使用 strstr 检查子字符串

将完整字符串作为第一个参数,将要查找的文本作为第二个参数。直接检查返回的指针;不要将它作为地址打印,因为该值取决于进程。strstr 由标准 C 头文件 <string.h> 声明,并逐字符精确比较,包括字母大小写。

此程序会在一句话中查找 welcome,并打印稳定的找到或未找到提示。示例中的输出标签 Substring found. 按要求保持英文。

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *text = "Hello, welcome to C.";
    const char *needle = "welcome";

    if (strstr(text, needle) != NULL) {
        puts("Substring found.");
    } else {
        puts("Substring not found.");
    }
    return 0;
}
Substring found.

返回的指针指向 text 中首次匹配的字符,因此你可以用它检查该位置的内容,或从该位置继续搜索。非空指针表示找到了匹配项。如果搜索文本是 "",strstr 会返回 text 的起始位置;如果你的应用不应将空搜索词视为匹配项,请针对这一标准行为作出处理。

使用循环搜索子字符串

手动搜索可以清楚地展示比较过程,也便于加入特定于应用程序的行为。下面的函数接受两个非空、以空字符结尾的字符串;如果找到搜索文本,则返回 1,否则返回 0。它会先检查字符串长度,再进行减法,从而避免搜索文本比输入更长时发生无符号下溢。与 strstr 一样,它将空搜索文本视为匹配项。

#include <stdio.h>
#include <string.h>

int contains_substring(const char *text, const char *needle) {
    size_t text_len = strlen(text);
    size_t needle_len = strlen(needle);

    if (needle_len == 0) {
        return 1;
    }
    if (needle_len > text_len) {
        return 0;
    }

    for (size_t i = 0; i <= text_len - needle_len; ++i) {
        size_t j = 0;
        while (j < needle_len && text[i + j] == needle[j]) {
            ++j;
        }
        if (j == needle_len) {
            return 1;
        }
    }
    return 0;
}

int main(void) {
    const char *text = "Hello, welcome to C.";
    const char *needle = "welcome";

    puts(contains_substring(text, needle)
             ? "Substring found."
             : "Substring not found.");
    return 0;
}
Substring found.

外层循环会尝试每个能够容纳完整搜索文本的起始位置。只要对应字符相同,内层循环就会继续前进。如果内层循环到达搜索文本的长度,函数便找到了完整匹配。这种直接明了的方法适合学习或自定义需求;一般进行精确匹配时,除非有理由维护自己的搜索代码,否则应优先使用标准库函数。

检查不区分大小写的子字符串

C 标准库没有定义不区分大小写的子字符串搜索函数。在 GNU 系统上,strcasestr 会忽略大小写进行搜索,它是 GNU 扩展。在使用此特性测试宏的系统上,应在包含头文件之前定义 _GNU_SOURCE,以请求其声明。该函数不是可移植的标准 C 函数,因此使用前应确认目标平台提供了它。

这个完整示例使用 ASCII 文本,并展示 GNU 专用的函数调用。它会打印 Substring found.;由于代码块必须保持与原文逐字节一致,输出标签保留英文。

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *text = "Hello, Welcome to C.";
    const char *needle = "welcome";

    if (strcasestr(text, needle) != NULL) {
        puts("Substring found.");
    } else {
        puts("Substring not found.");
    }
    return 0;
}
Substring found.

为编写可移植代码,应明确指定字符处理策略来实现不区分大小写的匹配,而不要悄悄改写输入字符串。对于按字节处理的文本,tolower 可能会有帮助,但它的参数必须是 EOF,或能够表示为 unsigned char 的值;其行为还取决于当前的 C 区域设置。仅使用这种方法并不能实现 Unicode 大小写折叠。应根据程序需要处理的数据,选择合适的库和文本策略。

处理边界情况并选择方法

strstr 和上面的循环都搜索以空字符结尾的字符串,并比较精确的字符序列。如果搜索词比文本更长,就不可能匹配。如果搜索词为空,strstr 会报告在起始位置找到匹配项,上面的循环实现也遵循相同规则;如果应用需要不同规则,请显式检查空搜索词。循环要求指针非空,这与标准字符串函数要求传入有效字符串参数的条件相同。

strstr 返回首次出现的位置。若要查找后续匹配项,可以从上一个匹配位置加上搜索词长度处继续搜索,同时确保搜索词非空,以便下一次搜索能够向前推进。如果需要查找重叠匹配,则应从上一个匹配位置向前推进一个字符。选择哪种方式,取决于匹配项是否允许共享字符。

通常的精确、区分大小写检查应使用 strstr:它简洁且属于标准 C。需要教学或自定义比较行为时,可以使用手动循环,并说明它对输入的假设。只有在 GNU 扩展可用,且其按字节处理及区域设置行为适合你的文本时,才使用 strcasestr。strpbrk 不能替代精确的子字符串匹配:它检查输入中是否出现某个字符集合里的任意字符,回答的是另一个问题。