C语言字符串函数进阶:安全版函数 + 错误处理 + 子串查找

适用对象:学过 strcpy / strcat / strcmp 的同学

本章目标:掌握安全版字符串函数 + strstr 子串查找 + strerror 错误处理


引入:strcpy 为什么会让人又爱又恨?

上一章我们学了 strcpy,它简单粗暴,复制速度也快------但它有一个致命问题:不检查目标空间是否够用

如果目标空间只有 5 字节,但你拷贝了 20 字节的字符串,会发生什么?

内存越界写入,可能会把旁边的变量覆盖掉,或者触发段错误(Segmentation Fault)。更可怕的是,有时候程序不会马上崩,而是在某个完全不相关的地方突然出问题------这种 Bug 极难定位。

于是,C 语言标准委员会给几大函数都设计了带数字版本(strncpy / strncat / strncmp ,把拷贝/追加/比较的长度交给程序员控制------更安全,但也更复杂


第一部分:strncpy------安全拷贝 n 个字符

1.1 函数原型

c 复制代码
char* strncpy(char *destination, const char *source, size_t num);

第三个参数 num:最多拷贝 num 个字符。

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

int main()
{
    char arr1[20] = {0};
    char arr2[] = "abcdefghi";
    strncpy(arr1, arr2, 5);   // 只拷贝前 5 个字符
    printf("%s\n", arr1);      // 输出 abcde
    return 0;
}

1.2 strncpy 的三个坑

坑一:源字符串不够 num 个字符时,用 \0 填充

c 复制代码
char arr1[20] = {0};
char arr2[] = "hi";
strncpy(arr1, arr2, 10);   // 源只有 2 个字符
printf("%s\n", arr1);        // 输出 hi,后面的 8 个位置全部填 \0

坑二:源字符串够长时,不保证在第 num 位置写 \0

c 复制代码
char arr1[20] = {0};
char arr2[] = "hello";
strncpy(arr1, arr2, 3);    // 只拷贝 3 个字符 "hel"
printf("%s\n", arr1);        // 输出 hel(没有 \0!arr1[3] 不是 \0)

⚠️ 提醒strncpy 在源字符串长度 ≥ num 时,不会自动补 \0 !这是最容易踩的坑。如果你在后面调用 printf("%s", arr1),它会继续往后读内存,打印出一堆乱码。所以使用 strncpy 后,务必手动确保目标字符串以 \0 结尾

c 复制代码
if (num < dest_size) arr1[num] = '\0';

1.3 strncpy vs strcpy:怎么选?

对比 strcpy strncpy
安全性 ❌ 不检查边界 ✅ 可控拷贝长度
何时用 源字符串短小确定 不确定源串长度时
长度足够时的 \0 一定会补 不保证

第二部分:strncat------安全追加 n 个字符

2.1 函数原型

c 复制代码
char* strncat(char *destination, const char *source, size_t num);

追加最多 num 个字符,但无论源多长,都会追加 \0 (这是它比 strncpy 更安全的地方)。

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

int main()
{
    char arr1[20] = "hello ";
    char arr2[] = "world";
    strncat(arr1, arr2, 3);   // 只追加前 3 个字符:"wor"
    printf("%s\n", arr1);      // 输出 hello wor
    return 0;
}

2.2 strncat 的一个陷阱

strncat 追加到 destination\0 位置开始,所以 destination 本身必须以 \0 结尾。如果 destination 忘了初始化:

c 复制代码
char arr1[20];          // 未初始化,里面是垃圾值
strncat(arr1, "hi", 2); // 危险!从哪里开始追加?

💡 正确写法 :在使用 strncat 之前,先确保 destination\0,或者用 arr1[0] = '\0' 清空。


第三部分:strncmp------安全比较 n 个字符

3.1 函数原型

c 复制代码
int strncmp(const char *str1, const char *str2, size_t num);

最多比较前 num 个字符,返回规则同 strcmp

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

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcqw";

    int ret1 = strncmp(arr1, arr2, 3);  // 只比较前3个:abc = abc → 0
    printf("%d\n", ret1);                // 输出 0

    int ret2 = strncmp(arr1, arr2, 4);  // 比较前4个:d > q → 负数
    printf("%d\n", ret2);                // 输出负数
    return 0;
}

3.2 典型应用:比较字符串前缀

c 复制代码
// 判断字符串是否以 "http://" 开头
if (strncmp(url, "http://", 7) == 0)
    printf("这是 HTTP 链接\n");

第四部分:strstr------在字符串里找子串

4.1 函数原型

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

str1 里找 str2 第一次出现的位置。

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

int main()
{
    char str[] = "This is a simple string";
    char *pch = strstr(str, "simple");

    if (pch != NULL)
        printf("找到了:%s\n", pch);  // 输出 simple string
    else
        printf("没找到\n");
    return 0;
}

💡 返回值 :找到则返回指向该位置的指针 ,没找到返回 NULL

4.2 手写 strstr:暴力匹配

核心思路:双层循环,外层遍历 str1,内层逐字符比较 str1str2

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

char* my_strstr(const char *str1, const char *str2)
{
    assert(str1 != NULL);
    assert(str2 != NULL);

    // 空字符串直接返回 str1(标准行为)
    if (*str2 == '\0')
        return (char*)str1;

    const char *s1 = str1;
    const char *s2 = str2;

    while (*s1) {
        // 记录当前位置
        const char *cur = s1;

        // 逐字符比较
        while (*s1 && *s2 && *s1 == *s2) {
            s1++;
            s2++;
        }

        // s2 遍历完了 → 匹配成功
        if (*s2 == '\0')
            return (char*)cur;

        // 没匹配上,重置 s2,回到 cur 的下一个位置继续
        s1 = cur + 1;
        s2 = str2;
    }
    return NULL;
}

第五部分:strerror------把错误码翻译成人话

5.1 什么是 errno?

C 语言程序启动时,系统会初始化一个全局变量 errno(定义在 <errno.h>)。当你调用标准库函数出错了,函数会把对应的错误码 写进 errno

错误码是一个整数,比如 2、5、11------直接看数字完全不知道什么意思。

c 复制代码
#include <errno.h>   // 声明全局变量 errno

5.2 strerror:翻译错误码

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

把错误码翻译成人类可读的错误信息字符串。

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

int main()
{
    for (int i = 0; i <= 10; i++)
        printf("%d: %s\n", i, strerror(i));

    return 0;
}

输出(Windows + VS2022):

复制代码
0: No error
1: Operation not permitted
2: No such file or directory
3: No such process
4: Interrupted function call
5: Input/output error
6: No such device or address
7: Arg list too long
8: Exec format error
9: Bad file descriptor
10: No child processes

5.3 实战:fopen 出错了怎么办?

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

int main()
{
    FILE *pFile = fopen("不存在的文件.txt", "r");

    if (pFile == NULL) {
        printf("文件打开失败:%s\n", strerror(errno));
        // 输出:文件打开失败:No such file or directory
        return 1;
    }

    // ... 正常读写文件 ...
    fclose(pFile);
    return 0;
}

5.4 perror:更简洁的写法

perrorstrerror 的快捷版本,直接把错误信息打印出来:

c 复制代码
perror("文件打开失败");
// 输出:文件打开失败:No such file or directory

相当于下面这行:

c 复制代码
printf("文件打开失败:%s\n", strerror(errno));

💡 选哪个 :如果你需要打印 错误,用 perror 更简洁;如果你需要存起来 或者自定义前缀 ,用 strerror


第六部分:经典面试题精讲

面试题一:strncpy 和 strcpy 的核心区别是什么?strncpy 有什么坑?

答案

  • strcpy\0 为结束标志,拷贝整个源字符串
  • strncpynum 为拷贝上限,但有两个坑:
    1. 源不够长时用 \0 填充剩余位置(有时候不是你想要的)
    2. 源够长时不自动补 \0 (比 strcpy 更危险)

加分回答

面试中如果能用上「源够 num 时不补 \0」这个坑点,面试官会觉得你理解得很深。建议使用 strncpy 后手动 dest[num] = '\0' 收尾。


面试题二:strstr 返回值是什么类型的指针?

答案char*,指向 str1 中首次匹配位置的指针。如果匹配失败,返回 NULL

c 复制代码
char str[] = "hello world";
char *p = strstr(str, "world");
printf("%s\n", p);   // world
printf("%ld\n", p - str);  // 6(world 在第6个位置起)

🔑 进阶用法:通过指针相减,可以算出子串在原串中的位置。


面试题三:下面的代码会输出什么?

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

int main()
{
    char s1[] = "hello";
    char s2[] = "world";
    char buf[10];

    strncpy(buf, s1, 10);
    strncat(buf, s2, 3);
    printf("%s\n", buf);
    return 0;
}

推演

  1. strncpy(buf, s1, 10)buf 大小 10 字节,s1 只有 5 字符 + \0,所以全部拷贝,再填充 4 个 \0buf = "hello\0\0\0\0\0"
  2. strncat(buf, s2, 3):从 buf\0 位置开始,追加 s2 前 3 个字符 "wor"buf = "hellowor\0"(第二个 \0 被覆盖,但后面还有 2 个 \0
  3. printf("%s\n", buf):从 buf[0] 打印到 \0,输出 hellowor

答案hellowor


本章总结

安全版字符串函数:

函数 额外参数 与基础版的关键区别
strncpy size_t num 源够 num 时不补 \0(需手动处理)
strncat size_t num 追加 num 字符,但一定会补 \0
strncmp size_t num 最多比较前 num 个字符

strstr 用法速记:

  • 找得到 → 返回指针(指向匹配位置)
  • 找不到 → 返回 NULL
  • 指针相减 → 子串在原串中的位置

错误处理速记:

函数 用法 特点
errno 全局变量,记录最近一次错误码 调用标准库函数后检查
strerror strerror(errno) → 字符串 可存、可自定义
perror perror("提示") 直接打印,等价于 printf + strerror

相关推荐
Dr.kangder1 小时前
嵌入式面试总结(十四)——中断处理
单片机·面试·职场和发展·架构·硬件架构·嵌入式
ITKEY_1 小时前
neovim基于nvim-jdtls 开发java报错
java·开发语言
無限進步D1 小时前
Java 代码块
java·开发语言
吃好睡好便好1 小时前
MATLAB仿真框图4
开发语言·matlab·仿真·分段函数
三克的油1 小时前
算法基础-1
数据结构·c++·算法
oort1231 小时前
OortCodex 是奥尔特云 OortCloudSmart 推出的国产化工程化 AI 编码 Agent(奥尔特云编码智能体)
大数据·人工智能·算法
TheBestRucy1 小时前
基于Dify的旅游攻略&王者荣耀攻略智能助手项目
服务器·开发语言·人工智能·python·算法·旅游
你想知道什么?1 小时前
朴素贝叶斯算法-学习笔记
笔记·学习·算法
一水1 小时前
Pi :内置四工具源码拆解——文件锁、变更快照与 bash 隔离
开发语言·chrome·ai·bash