C语言学习笔记20260618-字符串库函数模拟实现
一、学习目标
深入理解C语言中四个核心字符串库函数(strlen、strcpy、strcat、strcmp)的底层实现原理,掌握计数器、递归、指针运算等多种编程技巧,并学习如何通过断言(assert)和const修饰符来编写健壮、安全的底层代码。
二、模拟实现 strlen(获取字符串长度)
2.1 核心思路与三种实现方式
strlen 用于计算字符串中有效字符的个数(不包括末尾的 '\0')。代码展示了三种经典的实现思路:
- 计数器方式 :定义一个临时变量
count,遍历字符串直到遇到 '\0',每遍历一个字符count++。这是最直观、最容易理解的暴力解法。 - 递归方式 :利用函数栈的特性。如果当前字符是 '\0',返回0;否则返回
1 + 下一个字符的长度。这种方式代码极其精简,但深层递归可能导致栈溢出。 - 指针-指针方式 :记录字符串的起始地址
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' 覆盖掉,导致后续永远找不到终止符,引发死循环或缓冲区溢出。在实际工程中,应使用更安全的strncat或snprintf。
五、模拟实现 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,这使得返回值更加规范,便于调用者进行精确的逻辑判断。