C 语言字符串高阶:strstr/strtok/strerror 精讲(含 strstr 模拟实现)

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

    • [前景回顾:字符串专题核心速记 📝](#前景回顾:字符串专题核心速记 📝)
    • 一、strstr:字符串查找(子串匹配)🎯
      • [1. 函数核心要点(<string.h>)](#1. 函数核心要点(<string.h>))
      • [2. 经典错误认知](#2. 经典错误认知)
      • [3. 模拟实现:暴力匹配法(笔面试首选)](#3. 模拟实现:暴力匹配法(笔面试首选))
      • [4. 进阶补充:KMP算法 💡](#4. 进阶补充:KMP算法 💡)
    • 二、strtok:字符串分割(按分隔符拆分)✂️
      • [1. 函数核心要点(<string.h>)](#1. 函数核心要点(<string.h>))
      • [2. 避坑关键:使用临时拷贝](#2. 避坑关键:使用临时拷贝)
      • [3. 常见踩坑点](#3. 常见踩坑点)
    • 三、strerror:错误码转换(调试必备)⚠️
      • [1. 函数核心要点(<string.h>+<errno.h>)](#1. 函数核心要点(<string.h>+<errno.h>))
      • [2. 实战示例:调试文件打开失败](#2. 实战示例:调试文件打开失败)
      • [3. 关键补充](#3. 关键补充)
    • [写在最后 📝](#写在最后 📝)

字符串专题收官篇来啦!继前两篇吃透字符函数、strlen,以及strcpy/strcat/strcmp后,这一篇我们聚焦字符串操作的高阶场景------子串查找(strstr)、字符串分割(strtok)、错误码处理(strerror),不仅详解函数用法和核心陷阱,还手把手教你模拟实现strstr,同时补充KMP算法进阶知识点,帮你彻底吃透C语言字符串的全场景操作!

前景回顾:字符串专题核心速记 📝

想要吃透本篇的高阶字符串函数,先回顾前两篇的关键知识点:

  1. 所有字符串库函数均围绕\0处理,\0是字符串的终止标志。
  2. 模拟实现库函数时,assert断言指针非空是保证代码健壮性的关键。
  3. 长度受限的字符串函数(如strncpy)比非受限版本更安全,是开发首选。

一、strstr:字符串查找(子串匹配)🎯

strstr是笔面试高频考点,用于在一个字符串中查找另一个字符串的首次出现位置,也是理解KMP算法的入门基础。

1. 函数核心要点(<string.h>)

c 复制代码
char* strstr(const char* str1, const char* str2);

✅ 核心规则:

  • str1:被查找的主字符串,str2:要匹配的子字符串。
  • 返回值:找到子串则返回其在str1中的起始地址;未找到返回NULL;若str2为空字符串,直接返回str1
  • 匹配逻辑:逐字符对比,匹配失败则主字符串的匹配起始位置后移一位,重新匹配。

2. 经典错误认知

c 复制代码
// 错误!strstr返回的是子串起始地址,而非索引值
char arr[] = "abcdef";
int index = strstr(arr, "cde"); // 类型不匹配,且逻辑错误

3. 模拟实现:暴力匹配法(笔面试首选)

暴力匹配法是strstr最易上手的模拟实现方式,逻辑清晰,是笔试中考察的重点:

c 复制代码
#include <stdio.h>
#include <assert.h>

const char* my_strstr(const char* str1, const char* str2) {
    assert(str1 && str2); // 断言空指针,避免程序崩溃
    
    // 特殊情况:子串为空,直接返回主字符串起始地址
    if (*str2 == '\0') return str1;
    
    const char* cur = str1; // 记录每次匹配的起始位置
    const char* s1 = NULL;  // 遍历主字符串的指针
    const char* s2 = NULL;  // 遍历子字符串的指针
    
    while (*cur) { // 主字符串未遍历完则继续
        s1 = cur;
        s2 = str2;
        
        // 逐字符匹配:字符相等且都未到'\0'
        while (*s1 && *s2 && *s1 == *s2) {
            s1++;
            s2++;
        }
        
        // 子串遍历完,说明匹配成功,返回当前起始位置
        if (*s2 == '\0') return cur;
        
        cur++; // 匹配失败,起始位置后移一位
    }
    
    return NULL; // 遍历完主字符串仍未找到,返回NULL
}

// 测试代码
int main() {
    char arr[] = "abbbcdefabcdef";
    const char* ret = my_strstr(arr, "bbc");
    if (ret) {
        printf("%s\n", ret); // 输出:bbcdefabcdef
    } else {
        printf("未找到\n");
    }
    return 0;
}

4. 进阶补充:KMP算法 💡

暴力匹配法的时间复杂度为O(n*m)(n为主串长度,m为子串长度),在数据量大时效率较低。而KMP算法通过预处理子串生成next数组,将时间复杂度优化到O(n+m),是大厂笔面试的高频进阶考点(B站「比特大博哥」有详细的KMP算法讲解,建议进阶学习)。

二、strtok:字符串分割(按分隔符拆分)✂️

strtok是处理格式化字符串的实用工具,常用于解析邮箱、手机号、日志等场景,但其使用规则特殊,极易踩坑。

1. 函数核心要点(<string.h>)

c 复制代码
char* strtok(char* str, const char* sep);

✅ 关键特性:

  • sep:分隔符集合(如@.表示分隔符是@.)。
  • str:待分割的字符串,必须可修改 (不能是const修饰或字符串常量)。
  • 调用规则:首次调用传str,后续调用传NULL(函数内部用static变量保存上次分割位置)。
  • 处理逻辑:将分隔符替换为\0,返回当前分割段的起始地址;无更多分割段时返回NULL

2. 避坑关键:使用临时拷贝

strtok会修改原字符串,因此建议先拷贝到临时数组再分割:

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char arr[] = "18054369443@128.com";
    char tmp[30] = {0};
    strcpy(tmp, arr); // 拷贝临时版本,避免修改原字符串
    
    const char* sep = "@."; // 分隔符集合:@和.
    char* p = NULL;
    
    // 循环分割:简洁且规范的写法
    for (p = strtok(tmp, sep); p != NULL; p = strtok(NULL, sep)) {
        printf("%s\n", p); // 输出:18054369443  128  com
    }
    return 0;
}

3. 常见踩坑点

c 复制代码
// 错误1:直接分割字符串常量(不可修改)
char* str = "18054369443@128.com";
strtok(str, "@."); // 运行崩溃

// 错误2:后续调用传原字符串
char tmp[30] = "18054369443@128.com";
strtok(tmp, "@.");
strtok(tmp, "@."); // 分割结果错误,应传NULL

三、strerror:错误码转换(调试必备)⚠️

strerror是C语言调试的核心工具,能将库函数调用失败的错误码转换为可读的错误信息,搭配errno全局变量使用。

1. 函数核心要点(<string.h>+<errno.h>)

c 复制代码
char* strerror(int errnum);

✅ 核心规则:

  • errnum:错误码,通常传入全局变量errnoerrno由系统维护,库函数调用失败时会赋值)。
  • 返回值:错误码对应的可读错误信息字符串地址。
  • 简化版:perror(const char* str) = printf + strerror,直接打印自定义提示+错误信息。

2. 实战示例:调试文件打开失败

c 复制代码
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    // 以读模式打开不存在的文件,fopen返回NULL
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL) {
        // 方式1:strerror + errno(自定义提示)
        printf("文件打开失败:%s\n", strerror(errno));
        
        // 方式2:perror(更简洁,推荐)
        perror("打开文件失败"); // 输出:打开文件失败: No such file or directory
        
        return 1; // 异常退出
    }
    
    // 成功打开则关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

3. 关键补充

  • errno初始值为0(无错误),仅在库函数调用失败时修改。
  • 使用perror需包含<stdio.h>头文件,无需手动传入errno,函数内部会自动获取。

写在最后 📝

至此,C语言字符串函数专题全部完结!从基础的字符函数、strlen,到核心的strcpy/strcat/strcmp,再到高阶的strstr/strtok/strerror,我们覆盖了字符串操作的全场景。

这些函数的核心共性是围绕\0处理,而"安全性"(受限/非受限函数)和"指针操作"是贯穿始终的考察重点。笔面试中,不仅要会用这些函数,更要理解模拟实现的底层逻辑------这正是考察C语言指针和内存操作能力的核心方式。

建议大家把专题中的模拟实现代码全部手动敲一遍,加深对逻辑的理解;进阶学习可重点攻克KMP算法,提升应对大厂笔面试的能力。字符串作为C语言的核心知识点,吃透这些内容,能大幅提升你的编程基础和问题解决能力!

相关推荐
PeaceKeeper76 小时前
Objective-c的内存管理以及Block
开发语言·macos·objective-c
2501_936960366 小时前
c语言期末速成8——文件
c语言·开发语言
小鸡脚来咯6 小时前
RabbitMQ详解(从入门到实战)
开发语言·后端·ruby
唐装鼠6 小时前
Rust Box<T> 和引用(deepseek)
开发语言·rust
计算机学姐6 小时前
基于php的非物质文化遗产推广系统
开发语言·vue.js·mysql·tomcat·php·postman
qq_401700416 小时前
数组指针:连续内存的操控
c语言
翔云 OCR API6 小时前
文档识别接口:赋能企业高效办公与加速信息的数字化转型
开发语言·人工智能·python·计算机视觉·ocr·语音识别
晨晖26 小时前
直接插入排序
c语言·数据结构·c++·算法
宋情写6 小时前
Java基础篇01-环境搭建+入门体验
java·开发语言