C语言字符串与内存函数详解及模拟实现
掌握C语言核心库函数是高效处理数据的基础。本文深入解析字符串处理函数(strlen、strcpy、strcmp等)与内存操作函数(memcpy、memmove)的内部逻辑与使用规范,通过代码示例演示如何正确调用这些API,并实现了它们的底层版本以揭示其工作原理。
字符串长度:strlen
strlen 函数接收一个 const char* 类型的参数,用于计算字符串的有效长度(即 '\0' 之前的字符数量)。需要注意两个关键点:一是返回类型为 size_t(无符号整数),二是不包含终止符本身。
由于返回值是无符号数,在比较不同字符串长度大小时容易引发逻辑错误。例如当 strlen(str2) - strlen(str1) 的计算结果出现负数时,被提升为无符号大数,导致判断失误。
// 易错点演示
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
// 错误逻辑:若str1比str2长,差值为负,转为unsigned后极大
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2 > str1\n");
}
else
{
printf("str1 > str2\n");
}
return 0;
}
strlen 的模拟实现
为了实现类似功能,有三种常见的思路:
法一:计数器循环
遍历指针直到遇到结束符,使用计数器累加。注意指针自增必须在解引用之后进行,或者后置自增,避免漏掉或越界。
size_t my_strlen1(const char* str)
{
assert(str);
size_t count = 0;
while (*str++)
{
count++;
}
return count;
}
法二:指针相减
记录起始地址,移动指针至结尾,最终两地址之差即为长度。此方法不能混用前置/后置自增而不判据,需先判断是否为 '\0'。
size_t my_strlen2(const char* str)
{
assert(str);
const char* start = str;
while (*str != '\0')
{
str++;
}
return (size_t)(str - start);
}
法三:递归(不使用局部变量)
size_t my_strlen3(const char* str)
{
if ('\0' == *str)
return 0;
return 1 + my_strlen3(str + 1);
}
字符串拷贝:strcpy 与 strncpy
strcpy 负责将一个字符串完整地拷贝到目标缓冲区,包括末尾的空字符。使用前必须确保目标缓冲区空间充足,否则会导致内存溢出。它返回指向目标字符串起始位置的指针,便于链式调用。
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
// 拷贝直到src遇到' '并将' '也赋值给dest
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello underworld";
char arr2[20] = "hello world";
strcpy(arr2, arr1);
return 0;
}
strncpy 相较于 strcpy 增加了一个计数参数 n,最多拷贝前 n 个字节。若源串长度不足 n,则剩余部分会被填充为 '\0';若大于等于 n,则不会追加终止符。在实际应用中需注意手动补零以确保字符串合法性。
字符串拼接:strcat 与 strncat
strcat 将源字符串的内容追加到目标字符串的末尾,首先定位目标串的 '\0',然后开始覆盖写入,直至源串结束。务必保证目标缓冲区留有足够空间容纳拼接后的新字符串。
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
// 寻找dest末尾
while (*dest)
{
dest++;
}
// 执行拷贝
while (*dest++ = *src++)
{
;
}
return ret;
}
strncat 控制最多从源串拷贝 n 个字符(不含终止符本身,但函数会自动在末尾添加一个 '\0')。如果 n 超过了源串长度,它将拷贝整个源串。
字符串比较:strcmp 与 strncmp
strcmp 按字典序逐字符比较两个字符串。若第一个不相等的字符前者ASCII码大则返回正数,小则返回负数,完全相同返回0。
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0') // 同时到达结尾
return 0;
str1++;
str2++;
}
// 返回差值指示大小关系
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
strncmp 仅比较前 n 个字符,其余规则同 strcmp。
子串查找:strstr 与 KMP优化
strstr 返回主串中第一次出现子串的位置指针,若未找到则返回 NULL。简单的暴力匹配效率较低,此处展示基于KMP算法的高效实现,利用 next 数组减少回溯。
// 简易版暴力strstr模拟
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0') return (char*)str1;
const char* s1, *s2, *cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0') // 子串全部匹配成功
{
return (char*)cp;
}
cp++;
}
return NULL;
}
KMP算法中的 get_next 预处理与匹配逻辑如下,可大幅提升长文本搜索性能:
void get_next(const char* str, int* next)
{
int len = strlen(str);
next[0] = -1;
int i = 0, k = -1;
while (i < len - 1)
{
if (k == -1 || *(str + i) == *(str + k))
{
++i;
++k;
next[i] = k;
}
else
k = next[k];
}
}
int Index_KMP(const char* str1, const char* str2, int pos)
{
int i = pos, j = 0;
int next[255];
get_next(str2, next);
int len1 = strlen(str1), len2 = strlen(str2);
while (i < len1 && j < len2)
{
if (j == -1 || *(str1 + i) == *(str2 + j))
{
++i; ++j;
}
else
j = next[j];
}
return (j >= len2) ? (i - len2) : -1;
}
字符串分割:strtok
strtok 是一种破坏性分割函数,它会修改原字符串(将分隔符替换为 '\0')。首次调用传入待分割字符串和分隔符集;后续调用应传入 NULL 以继续获取下一段。该函数不是线程安全的,多线程环境下建议使用 strtok_r。
int main()
{
char str[] = "cjh@scu.edu.cn";
char sep[] = "@.";
char* token = strtok(str, sep);
while (token != NULL)
{
printf("%s\n", token);
token = strtok(NULL, sep);
}
return 0;
}
内存拷贝:memcpy
memcpy 用于任意类型数据的固定字节数拷贝。它的功能是基于内存块的原始复制,不了解具体数据类型。需注意:源和目标内存区域绝对不能重叠,否则行为未定义。
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
内存移动:memmove
memmove 与 memcpy 原型相同,区别在于它能安全处理源和目标区域有重叠的情况。实现时根据地址相对位置决定是从前往后拷贝还是从后往前拷贝。
- 若不重叠(或目标地址低于源地址),从前向后拷贝。
- 若存在重叠且目标地址高于源地址,从后向前拷贝以防覆盖未拷贝的数据。
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest <= src || (char*)dest >= ((char*)src + count))
{
// 不重叠或目标低:正向拷贝
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
// 重叠且目标高:逆向拷贝
dest = (char*)dest + count - 1;
src = (char*)src + count - 1;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest - 1;
src = (char*)src - 1;
}
}
return ret;
}
其他辅助函数
内存比较 (memcmp)
逐字节比较两块内存区域的前 n 个字节,返回第一个不相等处的ASCII差值。常用于结构体校验或二进制数据比对。
字符分类与转换
标准库还提供了丰富的字符检测宏/函数,位于 <ctype.h>:
isalpha,isdigit,isspace等用于判断字符类别。tolower,toupper用于大小写转换。
小结:
在实际开发中,尽量优先使用标准库提供的函数(如 strcpy, memcpy),它们通常针对特定架构进行了高度优化。但在阅读源码或学习面试中,理解上述模拟实现的逻辑对于掌握内存管理与指针运算至关重要。
以上就是C语言的字符串函数与内存函数的详细内容,更多关于C/C++高级编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













