C语言学习笔记20260618-字符串库函数模拟实现

C语言学习笔记20260618-字符串库函数模拟实现

一、学习目标

深入理解C语言中四个核心字符串库函数(strlen、strcpy、strcat、strcmp)的底层实现原理,掌握计数器、递归、指针运算等多种编程技巧,并学习如何通过断言(assert)和const修饰符来编写健壮、安全的底层代码。

二、模拟实现 strlen(获取字符串长度)

2.1 核心思路与三种实现方式

strlen 用于计算字符串中有效字符的个数(不包括末尾的 '\0')。代码展示了三种经典的实现思路:

  1. 计数器方式 :定义一个临时变量 count,遍历字符串直到遇到 '\0',每遍历一个字符 count++。这是最直观、最容易理解的暴力解法。
  2. 递归方式 :利用函数栈的特性。如果当前字符是 '\0',返回0;否则返回 1 + 下一个字符的长度。这种方式代码极其精简,但深层递归可能导致栈溢出。
  3. 指针-指针方式 :记录字符串的起始地址 s,用一个指针 p 遍历到末尾。利用C语言指针运算的特性,p - s 直接得出两个地址之间相隔的字符数。这是效率最高且最具C语言特色的写法。

2.2 代码实现与规范

c 复制代码
// 方式1:计数器方式
int my_strlen(const char* str)
{
    assert(str != NULL); // 防止传入空指针
    int count = 0;
    while (*str)
    {
        count++;
        str++;
    }
    return count;
}

// 方式2:不能创建临时变量计数器(递归)
int my_strlen(const char* str)
{
    assert(str != NULL);
    if (*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str + 1);
}

// 方式3:指针-指针的方式
int my_strlen(char* s)
{
    assert(s != NULL);
    char* p = s;
    while (*p != '\0')
        p++;
    return p - s;
}

2.3 代码优化细节

  • const 修饰符 :函数参数应使用 const char* str。因为 strlen 只是读取字符串,不需要修改它。加上 const 既能防止函数内部误修改数据,又能体现良好的编程规范。
  • 返回值类型 :标准库中 strlen 的返回值类型是 size_t(无符号整型),模拟实现时建议保持一致,以避免有符号与无符号数比较时产生的潜在警告。

三、模拟实现 strcpy(字符串拷贝)

3.1 核心思路

strcpy 将源字符串(包括 '\0')完整拷贝到目标空间。最精妙的实现是将赋值操作直接放在 while 循环的判断条件中:while ((*dest++ = *src++))

3.2 代码实现

c 复制代码
char* my_strcpy(char* dest, const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    
    char* ret = dest; // 保存目标字符串的起始地址,支持链式调用
    while ((*dest++ = *src++))
    {
        ; // 循环体为空,逻辑全在判断条件中
    }
    return ret;
}

3.3 代码解析

  • 后置++的妙用*dest++ = *src++ 利用了后置++"先使用,后自增"的特性。它先将 src 指向的字符赋值给 dest,然后两个指针同时向后移动一位。
  • 自动拷贝 '\0' :当 src 遍历到末尾的 '\0' 时,'\0' 会被赋值给 dest。在C语言中,'\0' 的 ASCII 码为 0,即逻辑"假",此时 while 循环自然终止。这行代码完美地解决了"拷贝字符"和"终止循环"两个任务。
  • const 保护源串 :源字符串 src 只读,必须用 const 修饰,防止在拷贝过程中被意外篡改。

四、模拟实现 strcat(字符串追加)

4.1 核心思路

strcat 将源字符串追加到目标字符串的末尾。实现逻辑分为两步:首先找到目标字符串的结束符 '\0',然后从该位置开始执行与 strcpy 相同的拷贝逻辑。

4.2 代码实现

c 复制代码
char* my_strcat(char* dest, const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    
    char* ret = dest; // 保存目标字符串的起始地址
    
    // 第一步:找到目标字符串的末尾('\0'的位置)
    while (*dest)
    {
        dest++;
    }
    
    // 第二步:从末尾开始拷贝源字符串(同strcpy逻辑)
    while ((*dest++ = *src++))
    {
        ;
    }
    
    return ret;
}

4.3 关键细节与隐患

  • 重叠内存隐患 :标准 strcat 不支持目标字符串和源字符串是同一块内存(即自己追加自己)。因为当第一个 while 找到 '\0' 后,第二个 while 的拷贝操作会立刻将这个 '\0' 覆盖掉,导致后续永远找不到终止符,引发死循环或缓冲区溢出。在实际工程中,应使用更安全的 strncatsnprintf

五、模拟实现 strcmp(字符串比较)

5.1 核心思路

strcmp 逐字符比较两个字符串对应位置字符的 ASCII 码值。标准规定:str1 > str2 返回大于0的数,相等返回0,小于返回小于0的数。

5.2 代码实现

c 复制代码
int my_strcmp(const char* src, const char* dst)
{
    assert(src != NULL);
    assert(dst != NULL);
    
    int ret = 0;
    // 逐字符比较,直到出现不同字符或遇到字符串末尾
    while (!(ret = *(unsigned char*)src - *(unsigned char*)dst) && *dst)
    {
        ++src;
        ++dst;
    }
    
    // 将返回值归一化为 -1, 0, 1
    if (ret < 0)
        ret = -1;
    else if (ret > 0)
        ret = 1;
    return ret;
}

5.3 代码精妙之处

  • 无符号数相减 :代码中使用了 *(unsigned char*)src - *(unsigned char*)dst。将 char* 强转为 unsigned char* 是为了防止 char 在某些编译器下是有符号数(signed char),当遇到 ASCII 码大于 127 的扩展字符时,有符号减法可能导致错误的比较结果。
  • 循环终止条件!(ret = ... ) && *dst。只要两个字符相减结果为0(即字符相同)且未遇到字符串末尾,就继续比较下一个字符。
  • 返回值归一化 :标准库只要求返回正数、0或负数。代码最后通过 if-else 将结果归一化为 -1、0、1,这使得返回值更加规范,便于调用者进行精确的逻辑判断。