字符串和字符串函数

深入理解C语言:字符函数与字符串函数全解析及底层模拟实现

在之前的《深入理解指针》系列中,我们系统梳理了指针的核心知识,强调过"指针的核心在于对内存布局和类型步长的精准把控"。数组名退化为指针、指针相减算元素个数、编译期与运行期的区别,这些知识点在处理字符串时体现得淋漓尽致。

本篇博客,我们将围绕C语言标准库中关于字符和字符串的常用函数,从函数的使用、底层原理到模拟实现,带你彻底吃透字符串处理逻辑。所有代码均保证可以直接编译运行。


目录

一、字符分类函数

C语言标准库 <ctype.h> 提供了一系列用于判断字符类型的函数。这些函数接收一个整型参数(通常为字符的ASCII码,或为了兼容 EOF 被提升为 int),返回非零值(真)或0(假)。

常见函数及其判断条件:

  • iscntrl:任何控制字符
  • isspace:空白字符(空格、换页 \f、换行 \n、回车 \r、制表符 \t、垂直制表符 \v)
  • isdigit:十进制数字 0 ~ 9
  • isxdigit:十六进制数字(0-9, a-f, A-F)
  • islower:小写字母 a ~ z
  • isupper:大写字母 A ~ Z
  • isalpha:字母 a ~ z 或 A ~ Z
  • isalnum:字母或数字
  • ispunct:标点符号
  • isgraph:任何图形字符
  • isprint:任何可打印字符(包括图形字符和空白字符)

代码示例与说明:

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

int main(void) {
    char ch = 'A';
    // islower 判断是否是小写字母,如果是返回非0,不是返回0
    if (isupper(ch)) {
        printf("%c 是大写字母\n", ch);
    }
    if (isdigit('5')) {
        printf("'5' 是数字字符\n");
    }
    
    // 实际应用场景:统计字符串中各类字符的个数
    const char *str = "C99 & C11";
    int digit_count = 0;
    int alpha_count = 0;
    while (*str) {
        if (isdigit(*str)) digit_count++;
        if (isalpha(*str)) alpha_count++;
        str++;
    }
    printf("字符串中共有 %d 个数字字符, %d 个字母字符\n", digit_count, alpha_count);
    return 0;
}

二、字符转换函数

C语言提供了两个用于大小写转换的函数:

  • int tolower(int c):将大写字母转换为小写字母(若参数不是大写字母,返回原值)。
  • int toupper(int c):将小写字母转换为大写字母(若参数不是小写字母,返回原值)。

代码示例与说明:

在初学阶段,我们可能会用小写字母减去32(ASCII码)来实现转大写。但有了转换函数,代码更加规范、可读性更强,且不受平台字符集差异的影响。

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

int main(void) {
    // 将字符串中的小写字母转大写,其他字符不变
    char str[] = "Test String.\n";
    int i = 0;
    char c;
    while (str[i]) {
        c = str[i];
        if (islower(c)) {
            // 使用 toupper 函数直接转换,替代 c -= 32
            c = toupper(c); 
        }
        putchar(c);
        i++;
    }
    return 0;
}

三、strlen的使用和模拟实现

3.1 函数介绍与易错点

size_t strlen(const char *str);

  • 字符串以 '\0' 作为结束标志,strlen 返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0')。
  • 注意 :函数的返回值为 size_t,是无符号整数 (易错)。如果你计算 strlen(str2) - strlen(str1),结果永远是非负的,即使 str2 比 str1 短,相减也会得到巨大的正数。
c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    const char* str1 = "abcdef";
    const char* str2 = "bbb";
    // 易错点:无符号整数相减,结果永远大于0
    if (strlen(str2) - strlen(str1) > 0) {
        printf("str2>str1\n"); 
    } else {
        printf("str1>str2\n");
    }
    // 实际输出:str2>str1(因为无符号数相减变成了一个巨大的正数)
    return 0;
}

3.2 模拟实现的三种方式

方式1:计数器方式

最直观的写法,通过遍历字符数组并递增计数器,直到遇到 \0。

c 复制代码
#include <assert.h>
int my_strlen(const char * str) {
    int count = 0;
    assert(str); // 断言防止空指针
    while(*str) {
        count++;
        str++;
    }
    return count;
}

这段模拟 strlen 的代码可以精炼为以下四个核心关键点:

  1. 参数安全 :const 修饰防止误改原字符串,assert 断言拦截空指针(NULL),避免运行时段错误。
  2. 循环机制 :利用 while(*str) 判断 '\0' 自动终止;str++ 利用 char 类型步长为 1 的特性遍历字符串。
  3. 核心逻辑 :外部计数器 count,每循环一次自增,精准统计 '\0' 前的字符个数。
  4. 进阶优化 :严格来说,返回值应改为 size_t(无符号整数),更契合标准库设计,避免大长度溢出及无符号隐式转换陷阱。

方式2:递归方式(不创建临时变量)

利用递归压栈的特性,如果指针不是 \0,就返回 1 + my_strlen(str+1)。

c 复制代码
#include <assert.h>
int my_strlen(const char * str) {
    assert(str);
    if (*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str + 1);
}

这段递归版 my_strlen 的代码可以精炼为以下四个核心关键点:

  1. 参数安全 :const 保护源字符串不被修改,assert 拦截空指针(NULL),确保运行安全。
  2. 递归终止条件 :*str == '\0' 时返回 0,这是递归的"归"点,防止无限递归。
  3. 核心递推逻辑 :return 1 + my_strlen(str + 1)。当前字符非 \0 则长度算作 1,加上从下一个字符开始的子串长度。
  4. 进阶风险(栈溢出) :工程中严禁用递归实现 strlen 。若字符串过长,函数调用层次过深会导致栈溢出(Stack Overflow);且返回值类型 int 应改为标准库的 size_t。

方式3:指针相减(最能体现指针精髓的方式)

记录起始地址,让指针走到末尾,两者相减得到的就是元素个数。

c 复制代码
#include <assert.h>
int my_strlen(const char *s) {
    assert(s);
    const char *p = s;
    while (*p != '\0') {
        p++;
    }
    // 指针相减,得到中间经历的元素个数(char步长为1,所以个数等同字节数)
    return p - s; 
}

这段"指针相减"版 my_strlen 的代码可以精炼为以下四个核心关键点:

  1. 参数安全 :const 保护源字符串不被修改,assert 拦截空指针(NULL),确保运行安全。
  2. 指针备份 :const char *p = s; 保留原起始地址 s,用临时指针 p 去向后遍历,为最后的相减做准备。
  3. 遍历逻辑 :while (*p != '\0') p++; 让指针 p 步长为 1 向后移动,直到遇到字符串结束标志 \0。
  4. 指针运算精髓(核心) :return p - s; 利用指针相减得到元素个数 的法则。因为 char 类型步长为 1 字节,所以这里的元素个数恰好等于字符长度。(注:严格工程中返回值应改为 size_t)。

四、strcpy的使用和模拟实现

char* strcpy(char * destination, const char * source);

  • 将源字符串(包括终止空字符 \0)复制到目标数组。
  • 源字符串必须以 '\0' 结束。
  • 目标空间必须足够大且可修改。

模拟实现要点 :参数顺序(目标在前,源在后)、参数有效性判断(assert)、返回值(支持链式访问)。

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

char* my_strcpy(char *dest, const char *src) {
    char *ret = dest; // 保存目标地址首地址
    assert(dest != NULL);
    assert(src != NULL);
    
    // 将 *src 赋值给 *dest,并同时后置递增指针,最后判断赋值结果是否为 \0
    while ((*dest++ = *src++)) {
        ;
    }
    return ret; // 返回目标起始地址,支持嵌套调用
}

int main() {
    char arr1[20] = {0};
    const char* arr2 = "hello world";
    my_strcpy(arr1, arr2);
    printf("%s\n", arr1);
    return 0;
}

值得注意的几点:

  1. 目标空间必须足够大:否则越界写,未定义行为。
  2. 源串必须以 '\0' 结尾:否则循环不停止,继续越界复制。
  3. 不支持内存重叠 :dest 与 src 重叠时不能保证正确,应用 memmove。
  4. assert 发布版可能失效 :定义 NDEBUG 后断言被移除,不能只靠它判空。
  5. 核心表达式优先级 :*dest++ = *src++ 是 *(dest++) = *(src++),先赋值再后移。
  6. '\0' 也会被复制 :循环结束时 '\0' 已写入目标。
  7. 必须返回 ret :保存的是目标首地址,不能用移动后的 dest 返回,否则链式调用会错。

五、strcat的使用和模拟实现

char* strcat(char * destination, const char * source);

  • 将源字符串的副本追加到目标字符串的末尾(覆盖目标字符串的 \0,并在最后添加新的 \0)。
  • 目标字符串必须有 '\0',且目标空间必须足够大。

模拟实现要点 :先找到目标字符串的 \0,再从该位置开始拷贝。

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

char *my_strcat(char *dest, const char *src) {
    char *ret = dest;
    assert(dest != NULL);
    assert(src != NULL);
    
    // 1. 找到dest末尾的 \0
    while (*dest) {
        dest++;
    }
    
    // 2. 开始追加拷贝,同样用到赋值后的结果判断
    while ((*dest++ = *src++)) {
        ;
    }
    return ret;
}

int main() {
    char arr1[20] = "hello ";
    const char* arr2 = "world";
    my_strcat(arr1, arr2);
    printf("%s\n", arr1); // 输出 hello world
    return 0;
}

⚠️深度思考 :字符串自己给自己追加,如何?比如 my_strcat(arr1, arr1);。

这会导致灾难性的后果。在寻找 \0 的过程中,指针指向了末尾,但随后 src 指向的首个字符也是同一位置,此时目标空间开始被覆盖,src 指针永远无法遇到 \0,导致无限循环和内存越界,程序最终崩溃。务必避免自己给自己追加!


六、strcmp的使用和模拟实现

int strcmp(const char * str1, const char * str2);

  • 比较两个字符串对应位置字符的 ASCII 码值。
  • 第一个字符串大于第二个字符串,则返回大于0的数字;等于则返回0;小于则返回小于0的数字。

模拟实现要点 :直接返回 *str1 - *str2 的差值,为避免平台 char 有符号性差异导致结果异常,通常需要强制转换为 unsigned char。

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

int my_strcmp(const char * str1, const char * str2) {
    assert(str1 != NULL);
    assert(str2 != NULL);
    
    while (*str1 == *str2) {
        if (*str1 == '\0') {
            return 0; // 走到 \0 且相等,说明完全一致
        }
        str1++;
        str2++;
    }
    // 返回差值,根据正负判断大小
    return *(const unsigned char*)str1 - *(const unsigned char*)str2;
}

int main() {
    printf("%d\n", my_strcmp("abc", "abc")); // 0
    printf("%d\n", my_strcmp("abd", "abc")); // 正数
    printf("%d\n", my_strcmp("abc", "abd")); // 负数
    return 0;
}

值得注意的点:

  1. unsigned char 强转(绝对核心) :
    *(const unsigned char*)str1 - *(const unsigned char*)str2 这是精髓。若不转,当字符 ASCII 码大于 127(如有符号 char 的负数)相减时会溢出,导致大小判断彻底反转。标准库规定必须按 unsigned char 比较。
  2. 返回值只关注正负零 :
    返回的是字符差值,不一定是 1 或 -1。调用时只能判断 <0、0、>0,绝不能写 == 1 或 == -1。
  3. 循环退出条件 :
    while (*str1 == *str2) 只有遇到不等字符或双方都是 '\0' 时退出。内部 if (*str1 == '\0') 用于捕获"完全一致"的情况并提前返回 0。
  4. 先判 \0 再后移指针 :
    顺序极其重要。如果先 str1++,就可能跳过 '\0' 判断,导致越界。

核心是转为 unsigned char 后计算差值,返回值仅凭正负零判断大小。


七、strncpy函数的使用

char * strncpy(char * destination, const char * source, size_t num);

  • 拷贝 num 个字符从源字符串到目标空间。
  • 细节 :如果源字符串的长度小于 num,则拷贝完源字符串(包含 \0)之后,在目标的后边追加 0,直到满足 num 个。
  • 危险陷阱 :如果源字符串的长度大于等于 num,则不会自动添加 \0 ,此时目标字符串可能不以 \0 结尾,直接打印会越界。
c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[20] = {0}; // 初始化为全0,确保安全
    const char* src = "hello world";
    
    // 拷贝5个字符,不会添加 \0
    strncpy(dest, src, 5);
    printf("dest = %s\n", dest); // 输出 hello
    
    // 测试补充 0 的情况
    char dest2[20] = "XXXXXXXXXX";
    strncpy(dest2, "abc", 5);
    // "abc" 长度小于 5,所以拷贝 abc 后再补两个 \0
    printf("dest2 = %s\n", dest2); // 输出 abc
    return 0;
}

** 值得注意的地方:**

  1. 绝不保证 \0 结尾(最致命的坑) :
    如果 src 长度 ≥ n \ge n ≥n,strncpy 只会拷贝 n 个字符,不会 在末尾添加 \0。此时 dest 只是一个字符数组,不是合法字符串。直接 printf 或 strlen 会越界读取,直到内存中偶然遇到 \0。
  2. 第一段代码是"幸存者偏差" :
    第一段代码看似安全,完全依赖 char dest[20] = { 0 }; 这个初始化。如果写成 char dest[20];,就是未定义行为。不能依赖这种巧合。
  3. 主动补 \0 特性 :
    当 src 长度 < n < n <n 时,strncpy 会用 \0 填充 dest 剩余的所有空间(如图中 dest2 补了 2 个 \0)。
  4. 安全使用标准范式 :
    使用 strncpy 后,必须手动添加 \0 :
    dest[n - 1] = '\0'; (或者如果 n 小于 sizeof(dest),在拷贝后手动设置)。
  5. n 不能超过 dest 总大小 :
    n 是"最大拷贝字节数",但不是目标缓冲区的大小 。如果 n > sizeof(dest),同样会缓冲区溢出。
  6. 性能陷阱 :
    如果 dest 空间巨大,而 src 很短(如 n = 4096, src = "a"),strncpy 会把剩下 4095 字节全填成 \0,造成不必要的性能浪费。

八、strncat函数的使用

char * strncat(char * destination, const char * source, size_t num);

  • 将 source 指向字符串的前 num 个字符追加到 destination 指向的字符串末尾,再追加一个 \0 字符。
  • 如果 source 指向的字符串的长度小于 num,只会将字符串中到 \0 的内容追加到末尾。
c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str1[20] = "To be ";
    char str2[20] = "or not to be";
    
    // 追加 str2 的前6个字符
    strncat(str1, str2, 6);
    printf("%s\n", str1); // 输出 To be or not
    return 0;
}

值得注意的地方:

  1. 总是自动补 \0(与 strncpy 最大区别) :
    这是 strncat 最安全的特性。即使 src 的长度超过 n,截断后它必定会 在结尾添加 '\0'。不需要像 strncpy 那样手动补 \0。
  2. n 不是目标缓冲区剩余空间 :
    n 是最多从 src 拷贝的字符数 。如果 dest 剩余空间不够(原字符串 + 追加字符 + '\0' 超出 dest 总大小),依然会缓冲区溢出 。它只是限制了追加的最大长度,并不保护 dest 的边界。
  3. src 长度小于 n 时的行为 :
    如果 src 是 "ab",n = 6,则只会追加 'a','b' 和 '\0',不会 像 strncpy 那样用 \0 填满剩余空间。
  4. dest 必须是合法字符串 :
    strncat 从 dest 的 '\0' 处开始追加。如果 dest 没有 '\0'(例如是 strncpy 截断后的字符数组),会越界寻找末尾,导致未定义行为。
  5. n 为 0 的情况 :不追加任何字符,但可能会在 dest 末尾写入 '\0'(取决于具体实现,标准未完全规定,但通常会追加 '\0')。

九、strncmp函数的使用

int strncmp(const char * str1, const char * str2, size_t num);

  • 比较 str1 和 str2 的前 num 个字符,如果相等就继续往后比较,最多比较 num 个字母。
  • 如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外一个。如果 num 个字符都相等,就是相等返回0。
c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    const char* str1 = "abcdef";
    const char* str2 = "abcxyz";
    // 比较前3个字符
    int ret1 = strncmp(str1, str2, 3);
    printf("前3个字符比较结果: %d\n", ret1); // 0
    
    // 比较前4个字符
    int ret2 = strncmp(str1, str2, 4);
    printf("前4个字符比较结果: %d\n", ret2); // 负数 ('d' < 'x')
    return 0;
}

值得注意的地方:

  1. 返回值绝不保证是 1 或 -1 :
    标准只保证 <0、0、>0。千万不要写 if (strncmp(...) == -1) ,必须写成 < 0。
  2. 遇到 '\0' 会提前终止 :
    它不会死板地比较满 n 个字符。如果其中一个字符串提前遇到了 '\0',比较就会立刻结束。例如 strncmp("ab", "abc", 5),会走到 '\0' 与 'c' 比较,返回值取决于 '\0' (0) 与 'c' 的差值。
  3. n 为 0 时直接返回 0 :
    无论字符串内容是什么,strncmp(a, b, 0) 永远返回 0。
  4. 不检查 NULL 指针 :
    如果 str1 或 str2 是 NULL,直接崩溃。不要指望它内部有断言。
  5. 用于前缀比较的绝佳工具 :
    常用于判断某个字符串是否以特定前缀开头,如 if (strncmp(cmd, "GET ", 4) == 0)。
  6. 安全性 :相比于 strcmp,strncmp 限制了最大比较长度,不容易因为一方缺少 '\0' 而导致无限越界读取,所以常用于处理可能不安全的字符数组。

十、strstr的使用和模拟实现

char * strstr(const char * str1, const char * str2);

  • 返回字符串 str2 在字符串 str1 中第一次出现的位置(指针)。
  • 如果没找到,返回 NULL。
  • 匹配过程不包含 \0,但以 \0 作为结束标志。

模拟实现要点 :指针遍历与回溯。利用 cur 指针记录当前主串对比的位置,s1 和 s2 进行逐字符匹配,如果匹配失败,cur 后移,重新开始。

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

char * my_strstr(const char * str1, const char * str2) {
    assert(str1 != NULL);
    assert(str2 != NULL);
    
    const char *cur = str1;
    const char *s1;
    const char *s2;
    
    // 如果子串为空,直接返回主串首地址
    if (*str2 == '\0') {
        return (char*)str1;
    }
    
    while (*cur) {
        s1 = cur;
        s2 = str2;
        
        // 逐个字符匹配,直到遇到不相等或 \0
        while (*s1 && *s2 && (*s1 == *s2)) {
            s1++;
            s2++;
        }
        
        // 如果 s2 走到了 \0,说明匹配成功
        if (*s2 == '\0') {
            return (char*)cur;
        }
        
        // 否则主串从下一个字符重新开始
        cur++;
    }
    return NULL;
}

int main() {
    char str[] = "This is a simple string";
    char* pch = my_strstr(str, "simple");
    if (pch != NULL) {
        printf("找到子串,位置开始于: %s\n", pch); // 输出 simple string
    }
    return 0;
}

值得注意的地方:

  1. 返回值强转 (char*) 及 const 陷阱 :
    形参是 const char*,函数返回 char*,必须强转 (char*)cur。这是 C 标准库的历史遗留设计。但调用者绝不能通过返回的指针去修改原字符串,如果原字符串在只读区,会导致段错误。
  2. 空子串的边界条件 :
    if (*str2 == '\0') return (char*)str1; 这一句极易遗漏。标准规定:空子串匹配主串开头。若不处理,外层循环 while(*cur) 可能会直接返回 NULL。
  3. 匹配成功判定的是 *s2 :
    内层循环结束后,必须判断 *s2 == '\0',而不是 *s1。若主串在匹配中途结束(如主串 "ab" 找子串 "abc"),s2 不会走到末尾,匹配失败。
  4. 暴力匹配的效率问题(最坏 O(N*M)) :
    这是暴力算法(BF算法)。遇到大量重复字符(如主串 "aaaaaaaaab" 找子串 "aaab")时,cur 每次只往后挪一步,效率极低。实际工程或标准库通常使用 KMP、BM 等 O(N) 算法。
  5. 找不到返回 NULL :
    调用者必须判空,如 main 函数中所示,否则 printf("%s", NULL) 会崩溃。

十一、strtok函数的使用

char * strtok(char * str, const char * sep);

  • sep 参数指向一个字符串,定义了用作分隔符的字符集合。
  • strtok 找到 str 中的下一个标记,并将其用 \0 结尾,返回指向这个标记的指针。
  • 注意 :strtok 会改变被操作的字符串,所以被切分的字符串一般都是临时拷贝的内容并且可修改。
  • 第一次调用需传入字符串地址,后续调用需传入 NULL。
c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char arr[] = "192.168.6.111";
    const char* sep = ".";
    char* str = NULL;
    
    // 第一次调用传入数组名
    for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep)) {
        printf("%s\n", str);
    }
    // 输出:192, 168, 6, 111
    return 0;
}

值得注意的点:

  1. 必须传可修改的字符数组(最大坑) :
    strtok 会直接修改原字符串 (把分隔符替换为 '\0')。所以这里写 char arr[] 是对的;如果写成 char* arr = "192.168...",会因修改只读内存而程序崩溃。
  2. 首次传原串,后续必须传 NULL(核心用法) :
    第一次调用传入 arr,函数内部用静态变量 记录当前位置。后续调用只能传 NULL,告诉它"从上一次的位置继续切"。传错会导致从头开始或结果异常。
  3. 不能嵌套调用 :
    在遍历 strtok 的同时,如果内部又调用了 strtok 处理另一个字符串,外层状态会被破坏。
  4. 连续分隔符会被忽略 :
    例如 "192..168",中间连续两个点,strtok 会跳过它们,不会返回空字符串。开头和结尾的分隔符也会被忽略。
  5. sep 是分隔符集合 :
    sep = "." 表示"以点号作为分隔符"。如果写成 sep = ".,",那么点号和逗号都会被视为分隔符。

十二、strerror 与 perror函数的使用

char* strerror(int errnum);

  • 将参数部分错误码对应的错误信息的字符串地址返回。
  • 错误码一般放在 <errno.h> 头文件中说明。C语言程序启动的时候就会使用一个全局变量 errno 来记录程序的当前错误码。
c 复制代码
#include <errno.h>
#include <string.h>
#include <stdio.h>

int main() {
    // 我们打印一下 0~10 这些错误码对应的信息
    int i = 0;
    for (i = 0; i <= 10; i++) {
        printf("错误码 %d: %s\n", i, strerror(i));
    }
    return 0;
}

在 Windows 11 + VS2022 环境下,会输出:No error, Operation not permitted, No such file or directory, 等等。

实际应用:尝试打开一个不存在的文件,打印错误信息。

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

int main() {
    FILE *pFile = fopen("unexist.ent", "r");
    if (pFile == NULL) {
        printf("Error opening file unexist.ent: %s\n", strerror(errno));
    }
    return 0;
}

perror函数 :相当于把上述代码中的 printf 和 strerror(errno) 结合起来。perror 打印完参数部分的字符串后,再打印一个冒号和一个空格,最后打印错误信息。

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

int main() {
    FILE *pFile = fopen("unexist.ent", "r");
    if (pFile == NULL) {
        perror("Error opening file unexist.ent");
    }
    return 0;
}
// 输出:Error opening file unexist.ent: No such file or directory

四、总结

结合之前的指针进阶知识,我们可以提炼出以下字符串处理的通关心法:

  1. sizeof 与 strlen 不可混用 :sizeof 是编译期操作符,算内存大小;strlen 是运行期库函数,找 \0 算长度。strlen 返回 size_t 无符号数,相减需谨慎。
  2. 数组名的退化 :在传参时,数组名退化为首元素地址,函数内无法通过 sizeof 获取数组真实大小,必须依赖 \0 或额外的长度参数。
  3. 指针类型决定步长与运算 :指针相减算元素,在 strlen 的第三种模拟实现中体现得淋漓尽致。在 strstr 等模拟实现中,指针的步进与回溯是匹配逻辑的关键。
  4. 模拟实现防错 :编写标准库函数的模拟实现时,务必使用 assert 进行空指针断言,使用 const 保护源字符串数据,注意参数顺序和返回值类型。
  5. 警惕字符串函数陷阱 :
    • strcat 不能自己追加自己。
    • strncpy 不一定补 \0。
    • strtok 会修改原字符串,且使用静态变量,不可重入。
    • strerror 和 perror 是排查系统调用错误的利器。

指针与字符串的结合,不仅是语法游戏,更是对计算机内存模型的深度理解。希望这篇博客能帮你在C语言的海量字符串函数中拨云见日,顺利通关!如果觉得有帮助,欢迎点赞收藏,留个关注支持一下!

相关推荐
山下梅子酒2251 小时前
洛谷-入门-B2082
c语言
扶风ff1 小时前
练题簿在线免费刷题:共享题库、学习任务、在线考试,岗位培训更方便
学习·小程序
JWASX1 小时前
Java 转 go 学习 - 并发编程(3)
学习·golang
mlidongfeng1 小时前
【学习笔记】【NV】GIN
开发语言·php
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
H.莓飛1 小时前
【数据结构】二叉树
linux·数据结构·算法
欣欣之王来了1 小时前
5G网络基础:5G的核心特性,eMBB、uRLLC、mMTC的应用场景
网络·学习·计算机网络·安全·教程
Omics Pro1 小时前
~30,000+引用!理论2005,R包2008,多组学集成AI增强
开发语言·数据库·人工智能·算法·机器学习·自然语言处理·r语言
Xudde.2 小时前
CVE-2026-24061漏洞复现
学习·安全