C语言字符串与内存函数详解及模拟实现

拾柒
拾柒
发布时间:2026-10-16 | 5人浏览过
原创

掌握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++高级编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表