16、详解内存操作函数(memcpy/memmove/memset/memcmp)

前面提到的字符串的函数,操作对象是字符串,或多或少都和\0打交道

那么如果我现在需要拷贝整形数组,浮点型数组,结构体数组等等,就不能用strcpy了!!!

因为字符串的函数有\0这种局限性,遇到\0拷贝就结束了,而像整形数组这种,每一个元素是占4个字节,比如1存放在内存中是01 00 00 00,还没有拷贝第二个元素的时候,遇到第二个字节的00就结束了拷贝。

1. memcpy

内存拷贝函数

void * memcpy(void* destination, const void* source, size_t num);

这里的size_t num就是需要拷贝的字节数

拷贝整形数组

int arr1[] = {1, 2, 3, 4, 5};
int arr2[5] = {0};
memcpy(arr2, arr1, sizeof(arr1));

拷贝结构体数组

typedef struct Stu
{
    char name[20];
    int age;
}Stu;

Stu arr3[] = {{"zhangsan", 20}, {"lisi", 25}};
Stu arr4[3] = {0};
memcpy(arr4, arr3, sizeof(arr3));

模拟实现

void* my_memcpy(void* dest, const void* sour, int num)
{// 关于void*,可以接收任意类型的指针变量;不能解引用操作;不可以进行加减操作;如果需要可以先进行强转
    void* ret = dest;
    assert(sour != NULL);
    assert(dest != NULL);
    while(num--)
    {
        *(char*)dest = *(char*)sour;
        ++(char*)dest;
        ++(char*)sour;
    }
    return ret;
}
    

但是这种实现,当dest和sour有关联时,拷贝就失败了,下面的例子

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 尝试把1,2,3,4,5拷贝到3,4,5,6,7的位置上
my_memcpy(arr + 2, arr, 20);
// 得到的结果并不是预想的1,2,1,2,3,4,5,8,9,10 
// 结果是1,2,1,2,1,2,1,8,9,10
// 因为我们设计的内存拷贝函数中,当两个内存空间有交集时,会修改sour,而sour其实是不允许被修改的

其实,涉及到重叠拷贝的情况,并不是memcpy的问题,我们一般会使用memmove来进行解决

2. memmove

内存移动函数:一般用来处理内存重叠的情况

void* memmove(void* dest, void* sour, size_t num);

如何使用

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 尝试把1,2,3,4,5拷贝到3,4,5,6,7的位置上
memmove(arr + 2, arr, 20);

这个时候就能解决上面的重叠内存空间的拷贝问题了。

注意:其实memcpy也能这样使用,并解决了内存空间重叠的问题,而是刚刚我们自己设计的不能解决

但是并不是说明我们设计的是错误的!!!

一个小知识:

C语言中规定,memcpy只要处理不重叠内存拷贝就可以,memmove是处理重叠内存的拷贝的;所以我们设计的my_memcpy已经满足了C语言的规定了。只不过memcpy是做了功能扩展,在vs编译器下是可以完成这件事情的!

模拟实现memmove

void* my_memmove(void* dest, void* src, size_t num)
{// 关于内存重叠,有可能是从前向后拷贝,就是我们举的例子,把12345拷贝到34567上
    // 也有可能是从后向前拷贝,把34567拷贝到12345上
    // 这两种拷贝情况其实是不一样的
    // 如果dest的地址,在src地址的左边(dest < src),那我们就从前向后拷贝
    // 如果dest的地址,在src地址的右边(dest > src),那我们就从后向前拷贝,也就是说从src+num这个地址处向前,但是要注意的是这里的src是个void*类型,不能随便加减,这里只是为了说明从哪个位置开始而已
    // 如果dest的地址在src左边很左或者在src+num右边,是都不会发生重叠的,从前向后和从后向前都一样
    // 因此,我们可以以 dest 这个位置划分边界。dest < src 从前向后, dest > src 从后向前
    // (当然也可以以dest<src和dest>src+num从前向后,中间src < dest < src+num从后向前)
    assert(dest != NULL);
    assert(src != NULL);
    void* ret = dest;
    if (dest < src) // 仅仅是比较地址的大小,是和类型没有关系的。
    {
        // 从前向后拷贝
        while (num--)
        {
            *(char*)dest = *(char*)src;
            ++(char*)dest;
        	++(char*)src;
        }
    }else
    {
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num); // 注意这里num本身是要-1才能找到正确位置的
            // 但是在while循环条件里面,已经自减过了
        }
        // 从后向前拷贝
    }
    return ret;
}

3. memset

内存设置:

void * memset(void* dest, int value, size_t count);

dest:目的空间地址

value:要设置的字符

count:要设置的字符的个数

注意,有一个用法容易用错

// 正确用法
char arr[10] = "";
memset(arr, '#', 10);

// 易错点
int arr[10] = {0};
memset(arr, 1, 10); // 注意,这个10不是10个元素的意思!!!!!
// 这个10,是10个字符也就是10个字节的意思!!!
// 所以在使用这个函数时,如果不是字符数组,想要设置,一定要注意
// 这里的结果是 01 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 ...

4. memcmp

strcmp比较的是字符串,这里比较的是内存,返回值也是>0,<0,=0

int memcmp(const void * ptr1, const void * ptr2, size_t num);

如何使用

int arr1[] = {1,2,3,4,5};
int arr2[] = {1,2,5,4,3};
// 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
// 01 00 00 00 02 00 00 00 05 00 00 00 04 00 00 00 03 00 00 00
memcmp(arr1, arr2, 8); // 比较两个元素,8个字节 结果是 0  比较9个字节 结果是<0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今儿背单词吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值