一、memcpy函数
1、认识memcpy函数
看到memcpy时,你会不会有一种似曾相识的感觉。我们在学习c语言的过程中,学过一个strcpy的字符串拷贝函数,字符串拷贝,是用来拷贝字符串的。那当我们想拷贝整型数据、或者说其他类型的数据呢?这就用到我们的memcpy函数了。
参数介绍:
我只是说想拷贝整型数据可以用memcpy,但并不是说它像strcpy函数只能拷贝字符串一样的单一。我们从memcpy函数的dest和source参数类型是void*时就可以知道,能拷贝的不止整型,所有的类型它都可以拷贝,大大提高了适用范围。
参数介绍:destination:指向要在其中复制内容的目标数组的指针,类型转换为 void* 类型的指针。简单来说,就是你想要拷贝到哪里。
source:指向要在其中复制内容的目标数组的指针,类型转换为 void* 类型的指针。简单来说,就是你想要拷贝什么。
num:要复制的字节数。
该函数的应用:使用该函数时,我们需要加上相应的头文件#include<string.h>。其实这个函数用起来与strcpy函数类似,无非是加了一个想要拷贝的字节数。
#include<stdio.h>
#include<string.h>
int main()
{
int i = 0;
int arr1[20] = { 0 };
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1, arr2, 40);
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
如上述代码:把我们想拷贝的字节数,比如将arr2中40(十个整型数据大小为40个字节)个字节的数据拷贝到arr1中,我们打印一下arr1会得到以下结果:
2、实现memcpy函数
实现这个函数,我们暂且命为my_memcpy
void* my_memcpy(void* dest, const void* src, size_t sz)
参数解释:
因为我们用之前,函数并不清楚我们想要copy过去的是什么类型的数据,所以我们用void*来作为目标数据和起始数据的数据类型,在之后方便我们转化为其它类型
起始数据src前被const变量修饰是因为,我们要改的是目标数据dest,并不希望src被改变,所以我们用const修饰,以防src被改变。
我们为什么要选择一个一个字节传的方式,举个例子:当我们遇到一个整型数组,大家都知道一个整型的大小是4个字节,如果一个整型一个整型传,但当我们想传过去7个字节时,就并不会达到我们想要的结果。
代码实现:
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t sz)
{
assert(dest&&src);
while (sz--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return 0;
}
int main()
{
int arr1[10] = { 0 };
int arr2[5] = { 1,2,3,4,5 };
int sz = sizeof(arr2) / sizeof(arr2[0]);
my_memcpy(arr1, arr2, sz*sizeof(int));
for (int i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
这个函数的实现也是很简单了,但我们在此对函数内部操作说明一下。
1、我们第三个形式参数sz,是一个一个字节传的,那我们怎样实现呢?就将我们的dest与src强制转化为就好了,无论我们想要传的数据是什么类型, 将dest与src强制转化为char类型总能实现。
2、并且,因为我们在交换dest与src的数据的时候,对其进行了解引用。所以我们的src与dest并不能为空指针,这里用assert来判断。
打印结果如下:
3、为什么会有memmove函数?
在我们上述调用memcpy函数时,我们是将B数组中的元素拷贝到A数组中。那我如果只用一个数组呢?例如:我想将A数组中下标为0、1、2的元素拷贝到下标为1、2、3的位置。
int A[7] = { 1,2,3,4,5,6,7 };
my_memcpy(A+1, A, 3*sizeof(int));
我们仍然用我们自己实现的memcpy函数,当我们将A数组中下标为0、1、2的元素拷贝到下标为1、2、3的位置时,结果是怎样的呢?
然而,这与我们想要的结果不同,我们想要的结果是1、1、2、3、5、6、7。
出现与我们结果不同的原因是,在我们把第一个元素的值拷贝给第二个元素时,覆盖了原来的元素的值。第二个元素的值改变,所以当我们再将第二个元素的值拷贝给第三个元素时,又会将第三个元素的值覆盖掉。所以才有了上述的错误结果。
标准值规定,memcpy来实现不重叠的内存的拷贝。为了解决重叠内存的拷贝,便有了memmove函数。
二、memmove函数
1、认识memmove函数
参数介绍:
destination:指向要在其中复制内容的目标数组的指针,类型转换为 void* 类型的指针。
source:指向要复制的数据源的指针,类型转换为 const void* 类型的指针。
num:要复制的字节数。size_t是无符号整型。
形参的类型与memcpy一致,作用也相同。
调用memmove函数:
我们上述在解决重叠内存的拷贝时,用memcpy是行不通的,我们在这里调用一下memmove函数,让大家清楚他的用处和使用。
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 0 };
int A[7] = { 1,2,3,4,5,6,7 };
memmove(A + 1, A, 3 * sizeof(int));
for (int i = 0; i < 7; i++)
{
printf("%d ", A[i]);
}
return 0;
}
结果:
结果显然是我们想要的,那么memmove是怎样实现的呢?
2、模拟实现memmove函数
图解:
F:倒序拷贝:当我们的数据源的起始地址在目标数组的起始地址的前面时,用倒叙拷贝。
倒序,也就是从后往前排序,例如我们上述例子,其实就是一个倒叙拷贝,先拷贝A[2]到A[3]的位置,再拷贝A[1]到A[2]的位置,最后再拷贝A[0]到A[1]的位置,这时候我们不用担心覆盖,因为我们是先将A[2]的值拷贝过去后,已经完成了它的拷贝任务,就不用再担心别人会来阻碍它完成任务了。
S:正序拷贝:也就是从前往后按顺序拷贝。当我们的目标数组dest的起始地址在数据源source的起始地址的前面时,用正叙拷贝。
这时候我们先把A[1]拷贝到A[0]中,先完成A[1]的拷贝任务,接着再让A[2]拷贝到A[1]位置上,因为A[1]的拷贝任务已经完成了,所以我们即使覆盖它也没事,后面的步骤也是如此。
代码实现:
#include<stdio.h>
#include<assert.h>
void* my_memove(void* dest, void* source, size_t sz)
{
assert(dest&&source)
void*ret=dest;
if (dest<source)
{
for (int i = 0; i < sz; i++)
{
*(char*)dest = *(char*)source;
dest = (char*)dest + 1;
source = (char*)source + 1;
}
}
else(dest > source)
{
for (int i = sz; i >0; i++)
{
*((char*)dest + sz) = *((char*)source + sz);
}
}
return ret;
}
int main()
{
int arr[7] = { 1,2,3,4,5,6,7 };
my_memmove(arr + 1, arr, 3 * sizeof(int));
for (i = 0; i < 7; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
图解思想讲完,写出代码也是易如反掌,重要的还是思想。
但是写代码时要注意,虽然source和dest是一个数组中的,但是他们的起始地址不同。
另外,平常写代码要注意严谨规范,比如assert判断是否为空指针 注意void*类型的函数的返回值
让我们的代码更加完美