C语言学习笔记20260618-字符串库函数模拟实现
一、学习目标
深入理解C语言中四个核心字符串库函数(strlen、strcpy、strcat、strcmp)的底层实现原理,掌握计数器、递归、指针运算等多种编程技巧,并学习如何通过断言(assert)和const修饰符来编写健壮、安全的底层代码。
二、模拟实现 strlen(获取字符串长度)
2.1 核心思路与三种实现方式
strlen 用于计算字符串中有效字符的个数(不包括末尾的 ‘\\0’)。代码展示了三种经典的实现思路:
2.2 代码实现与规范
// 方式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 代码实现
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 代码实现
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 代码实现
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,这使得返回值更加规范,便于调用者进行精确的逻辑判断。




