在字符串操作函数中,我们了解了strlen,strcpy等函数,其中strlen函数可以将源字符串的内容拷贝到目标字符串中。考虑到strcpy函数只能拷贝字符串,今天介绍一个可以拷贝任意类型数组的函数——内存操作函数memcpy。
先来看看memcpy函数的声明:
void *memcpy( void *dest, void *src, size_t count );
其中,dest是目标数组名,src是要拷贝的源数组名,而count是要拷贝的字节数;这个函数的意义就是从数组src中复制count个字节的内容到dest中。从左向右,第一个void* 是函数返回的类型;第二、三个void* 的意思是dest,src可以是任意类型:int,float,char等。
明白了memcpy函数的参数、返回类型、作用,我们来尝试模拟这个函数。
为了区分,我们将模拟的函数称为my_memcpy,仿照memcpy,如下:
#include<assert.h>
void* my_memcpy(void* dest, void* src, size_t count)
{
assert(dest && src);//断言,确保dest和src不指向NULL,需要调用头文件
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)src;//每次拷贝一个字节
dest = (char*)dest + 1; //强制类型转换成char*,每次向后读取一个字节
src = (char*)src + 1; //强制类型转换不是永久的
}
return ret;
}
在memcpy函数的模拟中,我们需要注意的是强制类型转换的使用以及拷贝过程中是一个字节一个字节向后拷贝的。
使用memcpy函数的过程中,会出现一种情况:目标数组和源数组是同一个,那么在拷贝时就可能出现元素被覆盖的情况,例如:
#include<stdio.h>
#include<assert.h>
//memcpy的模拟
void* my_memcpy(void* dest, 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;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int len = sizeof(arr) / sizeof(arr[0]);//计算数组长度
my_memcpy(arr+2, arr, 20);
for (int i = 0; i < len; i++)//打印数组
{
printf("%d ", arr[i]);
}
return 0;
}
//预期结果:1 2 1 2 3 4 5 8 9
//实际结果:1 2 1 2 1 2 1 8 9
在这个例子中,我们模拟的my_memcpy函数显然无法得到想要的结果 ,于是有了另一个内存操作函数——memmove函数。
memmove函数可以说是memcpy函数的升级版,不仅具备memcpy函数的功能(拷贝任意类型数组),还能拷贝有数据重叠的数组,像上面的那个例子,如果使用memmove函数我们就能得到想要的结果。
既然memmove函数这么牛,我们当然要试一试模拟了。
如下图,在上面的例子中,我们要将从arr向后数的5个元素复制到从arr+2向后数的5个元素中。如果将src从前往后依次复制肯定是不行的,因为在将src中的第三个元素复制到dest中数字5时,src中的第三个元素早已被覆盖,因此应该将src从后向前复制,即从数字5复制到数字7的位置开始,这样就避免了被覆盖;
还有一种情况是dest位置不变,src的位置从arr+4开始,此时我们可以发现为了元素不被覆盖,我们需要将src从前向后复制。
两种情况的不同,我们要如何判断在拷贝时要使用哪种复制方法呢?
我们知道,数组的地址是随着下标的增长由低到高变化的,那么就有:
当dest>src时,src的拷贝是从后向前的;
当dest<src时,src的拷贝是从前向后的。
根据以上,我们可以模拟出memmove函数:
void* my_memmove(void* dest, void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
注:vs平台下,memcpy函数在拷贝有重叠数据的数组时,并不会出现模拟函数my_memcpy那样的错误,反而会像memmove函数那样打印出预期结果。