
人无完人,持之以恒,方能见真我!!!
共同进步!!
一、memcpy的使用和模拟实现
在之前我们学习了使用和模拟实现strncpy函数,它是一个字符串函数,用来按照给定的字节个数来拷贝字符串,那么问题来了我们想拷贝的不是字符串,而是整型、浮点型的数据,应该咋办捏?
这时候就要使用我们的内存函数memcpy,它的作用就要更加强大一些,因为它是对内存块的内容进行拷贝,不管内存中存放的是什么数据类型,都可以通过拷贝内存块来实现拷贝
但是使用函数memcpy需要包含的头文件还是<string.h>,接下来我们来看看这个函数的原型:
void * memcpy ( void * destination, const void * source, size_t num );
它有三个参数,可以看见,它的前两个参数不再是char* ,而是变成void* 指针了,因为我们根本就不知道拷贝的内容是什么类型的数据,所以可以使用void * 的指针,而它的第三个参数是一个无符号整型,代表了要拷贝的内存的字节数
它的返回类型变成void* 也是不知道操作类型是什么,所以使用void* ,而且它返回的就是目标空间的首地址,memcpy函数也是这样,返回目标空间的首地址,也就是这里的destination
接着我们可以总结出memcpy函数的特点:
- 函数memcpy从source的位置,也就是源空间的首地址开始,向后复制num个字节的数据到destination指向的内存位置
- 这个函数在遇到 ‘\0’ 的时候并不会停下来,它拷贝多少数据完全看第三个参数
- 如果source和destination有任何的重叠,复制的结果都是未定义的
接下来我们来简单使用一下这个函数,用它来拷贝一个整型数组,如下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[20] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1, arr2, sizeof(arr2));
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}

可以看到它已经帮我们把数据完全拷贝过来了
那我们就来试着模拟实现一下这个函数
- 函数命名:my_memcpy
- 函数参数:照抄memcpy的参数,简化一些长的名字:
void* my_memcpy ( void* dest, const void* src, size_t num )
- 函数实现:
(1)老规矩,断言指针有效性,确保不是空指针
(2)由于要返回目标空间的首地址,所以要创建一个void*的指针变量start来存储它,用于最后的返回
(3)由于不知道是什么数据,所以我们不能妄自定义一个数据类型,这里我们可以采用qsort里面的思想,将它们转为字符指针,一个字节一个字节的拷贝,如此反复就能完全拷贝内容
(4)所以我们创建一个while循环,每进行一次循环就让num --,每一次循环我们就进行一个字节的拷贝,并且拷贝完后让dest和src往后走一个字节
(5)进行一个字节的拷贝就很简单了,只需要将dest和src强制类型转换为char *就可以了所以我们这里可以采用++最原始的操作,就是给它+1,然后赋值给dest,如下:
dest = (char*)dest + 1;
(6)返回start 变量
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* start = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return start;
}

这时候我们来想想,我们的my_memcpy 能否实现自己对自己进行拷贝捏?
比如有一个数组arr,存放的是1到10的数字,能不能将从1开始的4个整型数据,拷贝到从5开始的4个整型数据,如下图:

能否使得数组中的数据变成1 2 3 4 1 2 3 4 9 10

那如果地址重复呢?

这个时候我们想要经过拷贝后,数据变成1 2 3 4 3 4 5 6 9 10 , 我们的my_memcpy能否实现呢?

不可以 那我们来看看这是为什么
到经过第三个整型的拷贝时我们发现了问题,原本该被拷贝的5现在已经变成了3,所以在7,的位置放的是3,在8的位置放的是4,所以最后整个数组变成了这个样子:

那么这时候就要用到我们的 memmove函数了,它能够实现内存重叠的拷贝,那memcpy 能否实现重叠拷贝呢?

答案是yes ,因为C语言规定了memcpy只处理没有内存重叠的情况,有内存重叠的情况交给memmove函数解决,这里的memcpy函数又为什么能够解决这个问题呢?
这个就涉及到编译器的问题了,比如C语言规定memcpy只处理没有内存重叠的情况,而VS的memcpy在处理了没有内存重叠的基础上,还实现了有内存重叠的情况
所以不用担心是不是我们的momcpy函数实现的有问题,我们实现的momcpy已经满足C语言的规定了,已经合格了,没有问题
二、memmove的使用和模拟实现
memmove函数相当于时memcpy函数的进阶版,它不仅可以实现C语言规定的memcpy函数的功能,处理没有内存重叠的情况,还能处理存在内存重叠的情况,使用它也需要包含头文件string.h
我们来看看memmove的原型:
void * memmove ( void * destination, const void * source, size_t num );
可以看到和memcpy的原型长得一模一样,它们参数的含义和返回值都相同
我们可以总结一下它们的不同:
- 和memcpy的差别就是memmove函数处理的源内存块和⽬标内存块是可以重叠的
- 如果源空间和⽬标空间出现重叠,就得使⽤memmove函数处理
接着我们首先来测试一下memmove能否实现memcpy的功能:

很明显我们看到memmove成功实现了,接着我们继续用memmove测试,让它替我们处理内存重叠的情况

那我们来模拟实现一下它
- 函数命名:my_memmove
- 函数参数:
void* my_memmove ( void* dest, const void* src, size_t num )
- 函数实现:
(1)老规矩,对dest和src断言,确保它们不是空指针
(2)然后创建一个变量start用来存储dest的值,用于最后的返回
(3)我们来想想怎么解决内存重叠的情况,根据之前的尝试,我们知道,根据memcpy那样实现肯定不行,而memcpy实现时我们采用的是从前往后拷贝,我们可以来尝试一下从后往前拷贝
(4)我们可以画一个图试试,如下:

数组的空间是连续的,并且随着下标的增大,地址也是逐渐增大的,所以我们可以发现当dest>src,就正常从前往后拷贝,当dest<src时,就从后往前拷贝
从前往后拷贝我们之前在memcpy讲过,就不再赘述了,如下:
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
要是要解决从后往后拷贝的问题,关键就在于找到dest和src空间的末尾地址,方法也很巧妙,我们可以根据while(num–),当第一次进入循环num就已经-1了,这时我们让dest和src加上num,就可以得到dest和src空间的末尾地址,这时就把src指向的内容赋值给dest指向的内容,然后随着下一次num–,dest和src加上num就跟着改变了,依次类推就可以实现从后往前拷贝,如下:
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* start = dest;
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return start;
}
运行结果:

三、memset的使用0
顾名思义,memset就是设置内存,它的作用就是一次性将num个字节全部置为某个值,使用它需要包含头文件string.h,我们来看看它的原型:
void * memset ( void * ptr, int value, size_t num );
它的第一个参数就是要设置的数组的首元素地址,第二个参数是要设置的值,第三个参数就是要设置多少个字节
所以当数组是整型数组和字符数组时,可以通过这个函数来设置它们的值

一般会用在竞赛或者项目中,需要多组输入之类的,使用完一个数组,需要把它的元素都置为0
接下来我们想想,能不能使用这个函数将数组中的所有元素更改为1,如图:

可以看到失败了,这是为什么呢?这是因为memset设置的单位是字节,而整型有4个字节,每一个字节都设置为1,这个数就很大了
接下来我们再来测试将字符数组全部弄成字符’x’

可以看到,memset连带着\0和空格都改成了字符x,当然,如果不想\0被改掉,在写最后一个参数时可以-1
到这里我们就讲完了memset,至于它的模拟实现,可以自行去实现,因为比较简单,只需要一个字节一个字节将对应的内容改成给出的数据即可,这里就不再赘述
四、memcmp的使用
它跟我们学习过的strncmp有点像,strncmp可以根据给出的字节数来比较字符串的大小,而memcmp是根据给出的字节数来比较各种类型的数据的大小,使用它需要包含头文件string.h,接下来我们来看看它的原型:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
可以看到它和strncmp的参数一模一样,第一个参数是要比较的内容的首地址,第二个也是如此,第三个参数用来指定要比较的字节的个数,而返回值也和strncmp的规则一样,前一个大就返回大于0的数,后一个大返回小于0的数,相等则返回0
接下来我们就用它来比较一下整型数组,如下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,3 };
int ret = memcmp(arr1, arr2, 4 * sizeof(int));
printf("经过4个整型的比较:");
if (ret > 0)
printf("arr1更大\n");
else if (ret < 0)
printf("arr2更大\n");
else
printf("arr1和arr2相等\n");
return 0;
}

在比较时,还需要注意一点,就是设置的要比较的字节数要有意义,比如arr2现在只有4个整型数据,如果要比较5个整型数据,也就是20个字节,就会读取到无效数据,所以最好保证字节数有意义
那么它能否比较字符串呢?

可以看到memcmp也可以比较字符串,至于memcmp的模拟实现可以自行完成,也是一个一个字节去比较
到这里我们内存函数也就介绍的差不多了,如果大家有什么疑问的话,欢迎在评论区留言

1689

被折叠的 条评论
为什么被折叠?



