前面提到的字符串的函数,操作对象是字符串,或多或少都和\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