当前位置: 首页 > news >正文

2.库函数的模拟实现

目录

以下三个需轻松手撕:

1. memcpy

2. memmove

3. strstr

下面三个出现的相对较少:

1.strlen

2. strcpy

3. strcmp


 

以下三个需轻松手撕:

1. memcpy

void *my_memcpy(void *dest, const void *src, size_t count)

函数memcpy从src的位置开始向后复制count个字节的数据到dest位置。这个函数在遇到 '\0' 的时候并不会停下来。如果src和dest有任何重叠,复制结果都是未定义的。

  • dst: 目标内存区域的指针,表示数据将要被复制到的位置。

  • src: 源内存区域的指针,表示数据来源的位置。

  • count: 要复制的字节数。

首先要有一个返回变量 ret,保存目标内存区域的起始地址,因为后面会对 dst++,防止这个位置丢失。

然后断言 dst 和 src 都不为空。

然后开始循环复制count个字节,也就是走 count 步,while(count--)。

在循环中,要将src指向的内容复制到dst中,然后各自移向下个字节。

void *my_memcpy(void *dst, const void *src, size_t count)
{
    void* ret = dst;
    assert(dst && src);
    while (count--)
    {
        *(char*)dst = *(char*)src;
        dst = (char*)dst + 1 ;
        src = (char*)src + 1;
    }
    return ret;
}
  • 由于 void* 类型不能直接解引用,因此需要将其强制转换为 char* 类型(因为 char 类型的大小为一个字节,适合逐字节复制)。
  • void* 不能直接进行指针算术运算(如 +-),因为 void 类型没有大小。

  • 必须将其转换为具体类型的指针(如 char*)才能进行指针算术运算。

为什么 dstsrcvoid* 类型?

  • void* 是一种通用指针类型,可以指向任何类型的数据(如 charintstruct 等)。

  • 通过使用 void*my_memcpy 函数可以处理任意类型的内存区域,而不需要为每种数据类型编写单独的函数。

返回 void* 类型的设计使得函数可以支持链式调用

2. memmove

void * memmove(void* dst, const void* src, size_t count)
  • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
  • 如果源空间和目标空间出现重叠,就得使用memmove进行处理
  • 复制的源区间是 [src, src + count)

  • 复制的目标区间是 [dst, dst + count)

如何实现可以重叠:分情况讨论,一种就是没有重叠正常处理,另一种是重叠了进行特殊处理。

void *ret = dst;  保存目标内存区域的起始地址,用于返回

1. 检查内存区域是否重叠:

if (dst <= src || (char*)dst >= ((char*)src + count))
  • void* 指针之间可以直接进行比较(如 ==, !=, <, <=, >, >=),因为比较的是指针的地址值,而不是指针所指向的数据。所以dst <= src 可以不加char*。

  • 如果涉及运算,必须将 void* 转换为具体类型的指针(如 char*)才能进行算术运算。

  • dst <= src:如果目标内存区域的起始地址小于或等于源内存区域的起始地址,说明没有重叠,或者重叠部分不会影响复制。如下例子,不管count多大都不会影响复制。

    src
     |
 a b c d e f g
 |
dst
  • (char*)dst >= ((char*)src + count):如果目标内存区域的起始地址大于或等于源内存区域的结束地址(即 src + count),说明没有重叠。如下例,例如 count = 2,不影响复制。

  src src+count
   |   |
 a b c d e f g
         |
        dst

如果没有重叠重叠不影响复制:

while (count--)
{
    *(char*)dst = *(char*)src;
    dst = (char*)dst + 1;
    src = (char*)src + 1;
}

2.如果重叠了并且会影响复制怎么办:

比如这种情况:dst > src 并且dst < src+count。

src src+count
 |     |
 a b c d e f g
     |     |
    dst dst+count

如果还和上面一样进行复制,c变成a,d变成b,e就变成a了,我们想要得复制结果是e是c,因为重叠影响了复制,导致内容被覆盖了。

需要从高地址到低地址逐字节复制,以避免覆盖源内存区域的数据。

如果我们找到dst的末尾位置,从末尾逐步向前复制就可以解决这个问题。

先把c赋给e,dst 和 src 各自减一,b赋给d,a赋给c,同样是进行count步。

void * memmove(void *dst, const void* src, size_t count)
{
    void *ret = dst;
    if(dst<=src || (char*)dst >= ((char*)src+count))
    {
        //低到高
        while (count--)
        {
            *(char*)dst = *(char*)src;
            dst = (char*)dst + 1 ;
            src = (char*)src + 1;
        }
    }
    else
    {
        //把指针放到高处
        dst = (char*)dst + count-1;
        src = (char*)src + count-1;
        //高到低
        while (count--)
        {
            *(char*)dst = *(char*)src;
            dst = (char*)dst - 1 ;
            src = (char*)src - 1;
        }
    }
    return ret;
}

3. strstr

用于在字符串 str1 中查找子字符串 str2 的第一次出现位置。如果找到,返回 str2str1 中的起始地址;如果找不到,返回 NULL

const char* my_strstr(const char* str1, const char* str2)
{
    const char* s1 = NULL;
    const char* s2 = NULL;
    const char* cur = str1;
  • str1 是主字符串,str2 是要查找的子字符串。

  • s1s2 是临时指针,用于在查找过程中遍历 str1str2

  • cur 是当前查找的起始位置,初始化为 str1 的起始地址

if (*str2 == '\0')//特殊场景
    return str1;
  • 如果 str2 是空字符串(即 *str2 == '\0'),则直接返回 str1,因为空字符串是任何字符串的子字符串。
    while (*cur)
    {
        s1 = cur;
        s2 = str2;
        while (*s1 && *s2  && *s1 == *s2)
        {
            s1++;
            s2++;
        }
        if (*s2 == '\0')
            return cur;
        cur++;
    }

循环遍历 str1,直到 cur 指向的字符为 '\0'(即字符串结束)。

如果cur++,说明从当前cur开始没找到str2,所以去下一个位置做开始进行寻找。每次循环开始时,s1 被设置为 curs2 被设置为 str2,准备开始新一轮的匹配

  • 如果 *s1*s2 相等且都不为 '\0',则继续比较下一个字符。

  • 如果 *s1*s2'\0',或者字符不相等,则退出循环。

  • 只有由于 str2 遍历完导致退出循环这种情况下,才说明str1中是有str2的,开始位置就是cur

    • 如果*s1是'\0',说明str2还没走完,str1已经走完了,一定不符合

    • 如果字符不相等,那cur去下个位置,s2回到str2开头重新寻找

最后如果str1遍历完都没能返回结果,那就说明不存在,返回NULL。

const char* my_strstr(const char* str1, const char* str2)
{
    const char* s1 = NULL;
    const char* s2 = NULL;
    const char* cur = str1;

    assert(str1 && str2);

    if (*str2 == '\0')//特殊场景
        return str1;

    while (*cur)
    {
        s1 = cur;
        s2 = str2;
        while (*s1 && *s2  && *s1 == *s2)
        {
            s1++;
            s2++;
        }
        if (*s2 == '\0')
            return cur;

        cur++;
    }

    return NULL;
}

下面三个出现的相对较少:

1.strlen

int my_strlen1(const char* str)
{
    int count = 0;
    while(*str)
    {
        count++;
        str++;
    }
    return count;
}

int my_strlen2(const char* str)
{
    //不能创建临时变量
    if(*str == '\0')
        return 0;
    return 1 + my_strlen2(str+1);
}

2. strcpy

char *my_strcpy(char *dst, const char *src)
{
    char *ret = dst;
    assert(dst && src);
    while((*dst++ == *src++))
        ;
    return ret;
}

3. strcmp

int strcmp(const char *str1, const char *str2)
{
    while(*str1 == *str2)
    {
        if(*str1 == '\0')
            return 0;
        str1++;
        str2++;
    }
    if(*str1 > *str2)
        return 1;
    else
        return -1;
}

相关文章:

  • ES怎么通过客户端操作和查询/curl操作指令
  • DeepBI驱动的动态预算与库存联动调整策略
  • 当AI回答问题时,它的“大脑”里在炒什么菜?
  • LoRa无线通讯边缘网关-EG2000-数据上云和远程组网
  • Android电量与流量优化
  • npm、pnpm、cnpm、yarn、npx之间的区别
  • 我的创作纪念日:730天的技术写作之旅
  • 11 | 给 Gin 服务器添加中间件
  • 晨控CK-FR08与汇川H5U系列PLC配置EtherNet/IP通讯连接手册
  • 六、OpenGL中EBO的使用及本质
  • 【Godot4.3】斜抛运动轨迹曲线点求取函数
  • 时间序列模型(1):LSTNet
  • 解决ubuntu(jetpack)系统下系统盘存储不够的
  • MongoDB备份与还原
  • 2025年第十届数维杯大学生数学建模挑战赛参赛规则
  • Windows根据文件名批量在文件夹里查找文件并复制出来,用WPF实现的详细步骤
  • 29.代码随想录算法训练营第二十九天|134. 加油站,135. 分发糖果,860. 柠檬水找零,406. 根据身高重建队列
  • [rust] rust学习
  • 【C语言系列】字符函数和字符串函数
  • QT:串口上位机
  • “75万买299元路由器”事件进展:重庆市纪委等三部门联合介入调查
  • 最美西游、三星堆遗址等入选“2025十大年度IP”
  • 上海能源科技发展有限公司原董事长李海瑜一审获刑13年
  • OpenAI与微软正谈判修改合作条款,以推进未来IPO
  • 港股持续拉升:恒生科技指数盘中涨幅扩大至6%,恒生指数涨3.3%
  • 普京提议恢复直接谈判,泽连斯基:望俄明日停火,乌愿谈判