首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C 语言字符串查找三剑客:strchr、strrchr 与 strstr 深度解析

C 语言字符串查找三剑客:strchr、strrchr 与 strstr 深度解析

作者头像
byte轻骑兵
发布2026-01-20 17:09:33
发布2026-01-20 17:09:33
2500
举报

在 C 语言开发中,字符串处理是高频场景,而查找字符 / 子串更是核心操作。标准库<string.h>提供的 strchr、strrchr 和 strstr 函数,凭借高效、稳定的实现,成为避免 “重复造轮子” 的关键工具。本文全方位拆解这三个函数,真正做到 “知其然且知其所以然”。


一、函数家族总览

在深入单个函数前,先通过一张对比表快速建立认知,明确三者的核心定位:

函数名

查找目标

查找方向

核心作用

适用场景

strchr

单个字符

左→右(正向)

找字符首次出现的位置

验证字符存在、单字符分割

strrchr

单个字符

右→左(反向)

找字符最后一次出现的位置

提取文件名、获取文件后缀

strstr

子串(多字符)

左→右(正向)

找子串首次出现的起始位置

关键词匹配、子串提取

二、逐个拆解:从原理到实践

2.1 strchr:正向查找单个字符

1. 函数简介

strchr(string character search)是最基础的字符查找函数,功能是在指定字符串中从左到右查找第一个匹配目标字符的位置,找到则返回指向该字符的指针,未找到则返回 NULL。

2. 函数原型

代码语言:javascript
复制
char *strchr(const char *str, int c);

参数说明

  • str:待查找的字符串(必须以'\0'结尾,否则会触发越界访问);
  • c:目标字符(虽声明为int,但实际会被转换为char类型,原因见 “注意事项”);

返回值

  • 成功:指向第一个匹配字符的指针(指针指向原字符串的内存地址,非拷贝);
  • 失败:NULL。

3. 实现逻辑(伪代码)

strchr 的实现逻辑非常直观,核心是 “遍历字符串 + 字符比对”:

代码语言:javascript
复制
function strchr(str: const char*, c: int) -> char*:
    if str == NULL:  // 处理空指针(标准未定义,此处为安全增强)
        return NULL
    convert c to char (c_char = (char)c)  // 关键转换:int→char
    while *str != '\0':  // 遍历到字符串结束符停止
        if *str == c_char:
            return (char*)str  // 找到,返回当前指针
        str = str + 1  // 指针后移
    // 特殊情况:若目标是'\0',返回字符串末尾指针
    if c_char == '\0':
        return (char*)str
    return NULL  // 未找到

4. 典型使用场景

  • 验证字符是否存在:判断字符串中是否包含某个字符(如判断邮箱是否包含'@');
  • 分割单字符分隔的字符串:如按','分割 CSV 文件中的字段;
  • 截取字符串前缀:如从"name:zhangsan"中提取"name"(找':'的位置后截断)。

5. 示例代码

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 场景1:验证邮箱格式(是否含'@')
    const char *email = "user@example.com";
    char *at_pos = strchr(email, '@');
    if (at_pos != NULL) {
        printf("邮箱格式合法,'@'位置:%ld(索引)\n", at_pos - email);
        printf("邮箱前缀:%.*s\n", (int)(at_pos - email), email);  // 截取前缀
    } else {
        printf("邮箱格式非法(无'@')\n");
    }

    // 场景2:查找字符串末尾(目标为'\0')
    const char *test_str = "hello";
    char *end_pos = strchr(test_str, '\0');
    printf("字符串长度:%ld\n", end_pos - test_str);  // 输出5,等价于strlen

    return 0;
}

运行结果

6. 注意事项

  • c的类型陷阱:c声明为int是为了兼容EOF(值为 - 1)。若直接用char接收,当c=EOF时,有符号char会将 - 1 截断为0xFF,导致比对错误;
  • 空指针风险:若str为 NULL,函数行为未定义(多数编译器会触发段错误),需提前判断;
  • 字符串必须以'\0'结尾:若字符串无结束符,会遍历到非法内存区域,导致程序崩溃。

2.2 strrchr:反向查找单个字符

1. 函数简介

strrchr(string reverse character search)是 strchr 的 “反向版本”,功能是在字符串中从右到左查找最后一个匹配目标字符的位置,核心场景是获取 “最右侧” 的字符(如文件路径中的文件名)。

2. 函数原型

代码语言:javascript
复制
char *strrchr(const char *str, int c);
  • 参数与 strchr 完全一致,仅查找方向不同;
  • 返回值:指向最后一个匹配字符的指针,未找到则返回 NULL。

3. 实现逻辑(伪代码)

与 strchr 的区别是 “先定位字符串末尾,再反向遍历”:

代码语言:javascript
复制
function strrchr(str: const char*, c: int) -> char*:
    if str == NULL:
        return NULL
    convert c to char (c_char = (char)c)
    const char *last_match = NULL  // 记录最后一次匹配的位置
    // 第一步:遍历到字符串末尾(找到'\0')
    while *str != '\0':
        if *str == c_char:
            last_match = str  // 更新匹配位置(覆盖之前的匹配)
        str = str + 1
    // 特殊情况:目标是'\0',返回末尾指针
    if c_char == '\0':
        return (char*)str
    // 返回最后一次匹配的位置(未匹配则为NULL)
    return (char*)last_match

4. 典型使用场景

  • 提取文件路径中的文件名:如从"/a/b/c.txt"中提取"c.txt"(找最后一个'/'后截取);
  • 获取文件后缀:如从"image.png"中提取".png"(找最后一个'.');
  • 分割带重复分隔符的字符串:如从"a,b,c,d"中提取最后一个字段"d"(找最后一个',')。

5. 示例代码(提取文件名)

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

// 从文件路径中提取文件名
char *get_filename(const char *path) {
    if (path == NULL) return NULL;
    // 找最后一个'/'
    char *slash_pos = strrchr(path, '/');
    if (slash_pos != NULL) {
        return slash_pos + 1;  // 跳过'/',返回文件名起始位置
    }
    return (char*)path;  // 若无'/',路径本身就是文件名
}

int main() {
    const char *paths[] = {
        "/home/user/test.c",  // 带完整路径
        "readme.md",          // 无路径(仅文件名)
        "/root/docs/",        // 以'/'结尾(无文件名)
        NULL                  // 结束标志
    };

    for (int i = 0; paths[i] != NULL; i++) {
        char *filename = get_filename(paths[i]);
        printf("路径:%15s → 文件名:%s\n", paths[i], filename);
    }

    return 0;
}

运行结果

6. 注意事项

  • 与 strchr 的共性问题:同样需注意c的类型转换、空指针和字符串结束符;
  • 路径以 '/' 结尾的处理:若路径最后一个字符是'/'(如"/a/b/"),strrchr 会返回该'/'的指针,此时slash_pos + 1指向'\0',需额外判断空字符串;
  • 性能对比:strrchr 需遍历整个字符串(无论是否提前找到匹配),效率与字符串长度正相关,与 strchr(找到即返回)在最坏情况下一致。

2.3 strstr:正向查找子串

1. 函数简介

strstr(string substring search)是子串查找函数,功能是在指定字符串(主串)中从左到右查找第一个匹配目标子串的起始位置,是比 strchr 更灵活的查找工具(支持多字符匹配)。

2. 函数原型

代码语言:javascript
复制
char *strstr(const char *haystack, const char *needle);

参数说明

  • haystack:主串(待查找的字符串,需以'\0'结尾);
  • needle:子串(要查找的目标,需以'\0'结尾);

返回值

  • 成功:指向主串中子串起始位置的指针;
  • 失败:NULL;
  • 特殊情况:若needle为空字符串(""),返回haystack本身(C 标准规定)。

3. 实现逻辑(伪代码:朴素算法)

strstr 的实现比前两个函数复杂,核心是 “主串遍历 + 子串比对”。以下是最易理解的朴素算法(标准库实现会优化,如 KMP 算法,但朴素算法更适合入门):

代码语言:javascript
复制
function strstr(haystack: const char*, needle: const char*) -> char*:
    if haystack == NULL || needle == NULL:
        return NULL
    // 特殊情况:子串为空,返回主串
    if *needle == '\0':
        return (char*)haystack
    
    // 主串遍历:到主串剩余长度 < 子串长度时停止
    while *haystack != '\0':
        const char *h = haystack  // 主串当前比对位置
        const char *n = needle    // 子串当前比对位置
        
        // 子串比对:逐字符匹配
        while *h != '\0' && *n != '\0' && *h == *n:
            h = h + 1
            n = n + 1
        
        // 若子串遍历完(说明完全匹配)
        if *n == '\0':
            return (char*)haystack
        
        // 主串指针后移,继续下一轮比对
        haystack = haystack + 1
    
    // 未找到子串
    return NULL

4. 典型使用场景

  • 关键词匹配:判断文本中是否包含指定关键词(如日志中找 “error”);
  • 提取子串后续内容:如从"http://www.example.com"中提取"www.example.com"(找"://"后截取);
  • 子串替换的前置步骤:替换子串前,需先用 strstr 找到子串位置。

5. 示例代码(关键词匹配与内容提取)

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

// 从URL中提取域名(假设URL格式为"http://xxx"或"https://xxx")
char *get_domain(const char *url) {
    if (url == NULL) return NULL;
    // 先找"http://",若找不到再找"https://"
    char *http_pos = strstr(url, "http://");
    if (http_pos != NULL) {
        return http_pos + 7;  // "http://"长度为7
    }
    char *https_pos = strstr(url, "https://");
    if (https_pos != NULL) {
        return https_pos + 8;  // "https://"长度为8
    }
    return (char*)url;  // 非HTTP/HTTPS协议,返回原URL
}

int main() {
    // 场景1:关键词匹配(日志中找"error")
    const char *log = "2024-05-20: info: start; 2024-05-20: error: file not found";
    if (strstr(log, "error") != NULL) {
        printf("日志中包含错误信息:%s\n", strstr(log, "error"));
    } else {
        printf("日志无错误\n");
    }

    // 场景2:提取URL域名
    const char *urls[] = {
        "http://www.baidu.com",
        "https://github.com",
        "ftp://ftp.example.com",  // 非HTTP/HTTPS
        NULL
    };
    for (int i = 0; urls[i] != NULL; i++) {
        char *domain = get_domain(urls[i]);
        printf("URL:%20s → 域名:%s\n", urls[i], domain);
    }

    return 0;
}

运行结果

6. 注意事项

  • 子串为空的处理:根据 C 标准,若needle为空,返回haystack,而非 NULL;
  • 主串 / 子串无结束符:会导致比对越界,触发程序崩溃;
  • 性能问题:朴素算法在主串和子串均为 “AAAAA...B” 时,时间复杂度会退化为 O (n*m)(n 为主串长度,m 为子串长度),标准库(如 GCC 的 strstr)会用 KMP 或 BM 算法优化为 O (n+m);
  • 区分大小写:strstr 是大小写敏感的,若需不区分大小写,需自行实现(如先转小写再比对)。

三、三函数差异对比

为方便快速查阅,整理核心差异如下表:

对比维度

strchr

strrchr

strstr

查找目标

单个字符(int→char)

单个字符(int→char)

子串(const char*)

查找方向

左→右(正向)

右→左(反向)

左→右(正向)

返回值

首个匹配字符的指针,无则 NULL

最后一个匹配字符的指针,无则 NULL

子串起始位置的指针,无则 NULL

特殊输入 1(c='\0')

返回字符串末尾指针

返回字符串末尾指针

不适用(子串用 needle 参数)

特殊输入 2(needle 空)

不适用(无 needle 参数)

不适用(无 needle 参数)

返回 haystack 本身

时间复杂度(最坏)

O (n)(n 为主串长度)

O(n)

O (n*m)(朴素实现)/O (n+m)(优化)

典型场景

验证字符存在、单字符分割

提取文件名、获取后缀

关键词匹配、子串提取

大小写敏感性

敏感(字符直接比对 ASCII)

敏感

敏感

四、常见问题与避坑指南

1. 空指针与越界问题

  • 问题:传入str=NULL或haystack=NULL,程序崩溃;

解决:调用函数前必须判断指针非空,如:

代码语言:javascript
复制
if (str == NULL) {
    printf("错误:字符串指针为空\n");
    return -1;
}

2. 字符类型转换问题

  • 问题:用strchr(str, 'a')正常,但用strchr(str, EOF)时匹配错误;
  • 原因:EOF是int类型(值 - 1),直接传入时会被转换为char(若char为有符号,-1 会变成0xFF);
  • 解决:无需额外处理,函数内部已做(char)c转换,只需按正常方式传参(如strchr(str, 'a')或strchr(str, '\n'))。

3. 字符串无结束符问题

  • 问题:定义字符串时用char str[] = {'a','b','c'}(无'\0'),调用函数时遍历越界;
  • 解决:确保字符串以'\0'结尾,推荐用char str[] = "abc"(编译器自动添加'\0')。

五、经典面试题

问:strchr 的参数c为什么声明为int而非char?

核心原因是兼容EOF(End of File,值为 - 1):

  • 若c声明为char,当传入EOF时,由于char是 8 位类型(有符号char范围为 - 128~127),-1 会被正常存储,但当char为无符号时(部分编译器默认),-1 会被转换为0xFF(255),导致与字符串中的正常字符混淆;
  • 声明为int后,EOF(-1)可完整存储,函数内部再通过(char)c将其转换为char类型比对,既兼容EOF,又支持正常字符查找。

问:如何用 strrchr 提取文件路径中的文件名(需处理边界情况)?

步骤如下:

1. 判空:若路径指针为 NULL,返回 NULL;

2. 反向查找:用strrchr(path, '/')找到最后一个'/'的位置;

3. 处理边界:

  • 若找到'/':若'/'是路径最后一个字符(如"/a/b/"),返回空字符串;否则返回'/'的下一个位置(slash_pos + 1);
  • 若未找到'/':路径本身就是文件名,直接返回原路径;

代码实现:

代码语言:javascript
复制
char *get_filename(const char *path) {
    if (path == NULL) return NULL;
    char *slash = strrchr(path, '/');
    if (slash == NULL) {
        return (char*)path;  // 无'/',返回原路径
    }
    // 若'/'是最后一个字符(如"/a/b/")
    if (*(slash + 1) == '\0') {
        return (char*)"";
    }
    return slash + 1;
}

问:strstr 查找空字符串(needle="")时返回什么?为什么?

返回主串haystack的指针(即主串的起始地址)。

原因依据 C 语言标准(ISO C99 及后续):

  • 空字符串是 “零长度字符串”,根据字符串理论,空字符串是任何字符串的子串,且其起始位置就是主串的起始位置;
  • 若返回 NULL,会与 “未找到子串” 的逻辑冲突,因此标准规定返回haystack本身,确保逻辑自洽。

(注:实际开发中很少传入空字符串,但面试中常考此标准细节。)

问:strchr(s, ‘\0’)的返回值是什么?它有意义吗?​

答:​​ 返回值是一个指向字符串 s末尾的 \0字符的指针。这是有意义的,并且是符合C标准定义的。因为 \0是字符串的一部分,strchr的职责就是查找字符,包括空字符。这个操作可以用来获得字符串的结束位置。

问:strstr函数在查找空字符串(“”)时会返回什么?为什么?​

答:​​ 根据C语言标准,strstr(haystack, "")会返回指向 haystack的指针(即主串本身)。因为空字符串被定义为是任何字符串的子串,并且它出现在主串的开头位置。这是一个需要特别注意的边界情况。

问:如何自己实现一个 strrchr函数?请描述思路。​

​答:​​ 思路如下:

  1. 检查输入字符串是否为 NULL
  2. 初始化一个指针 last_occurrenceNULL,用于记录最后一次匹配的位置。
  3. 遍历整个字符串,直到遇到 \0
  4. 在遍历过程中,每当遇到与目标字符 c匹配的字符时,就将 last_occurrence更新为当前位置的地址。
  5. 遍历完成后,还需要特别检查目标字符 c是否是 \0。如果是,则返回当前指向 \0的指针(标准规定)。
  6. 最后返回 last_occurrence。如果从未匹配过,它仍然是 NULL,符合函数规范。

strchr、strrchr 和 strstr 是 C 语言字符串处理的 “基石函数”,三者各有侧重:

  • 需找单个字符首次出现,用 strchr;
  • 需找单个字符最后出现,用 strrchr;
  • 需找子串首次出现,用 strstr。

掌握它们的实现逻辑、使用场景和边界处理,不仅能提升开发效率,更能在面试中展现对 C 标准库的深度理解。实际开发中,优先使用标准库函数(而非自行实现),因为标准库经过了性能优化和兼容性测试,稳定性更有保障。


博主简介 byte轻骑兵,现就职于国内知名科技企业,专注于嵌入式系统研发,深耕 Android、Linux、RTOS、通信协议、AIoT、物联网及 C/C++ 等领域。乐于技术分享与交流,欢迎关注互动! 📌 主页与联系方式

  • CSDN:https://blog.csdn.net/weixin_37800531
  • 知乎:https://www.zhihu.com/people/38-72-36-20-51
  • 微信公众号:嵌入式硬核研究所
  • 邮箱:byteqqb@163.com(技术咨询或合作请备注需求)

⚠️ 版权声明 本文为原创内容,未经授权禁止转载。商业合作或内容授权请联系邮箱并备注来意。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、函数家族总览
  • 二、逐个拆解:从原理到实践
    • 2.1 strchr:正向查找单个字符
    • 2.2 strrchr:反向查找单个字符
    • 2.3 strstr:正向查找子串
  • 三、三函数差异对比
  • 四、常见问题与避坑指南
  • 五、经典面试题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档