memcpy叫做内存拷贝,memmove叫做内存移动,memcmp叫做内存比较,大家可以想一想为什么要有这些函数呢?我们不是已经由字符串拷贝、字符串比较、字符串追加这些函数了吗?
我们首先知道,所谓的字符串函数:strcpy、strcat、strcmp、strncpy、strncat、strncmp
这些函数操作的对象是字符串,或多或少都会跟'\0'
打交道。
假设我们现在要操作的是一个数组,比如我们要拷贝整型数组、浮点型数组、又或者是结构体数组,那我们还能不能用strcpy函数来进行拷贝呢?不妨来试一下,请看:
可以清楚的看到这里会弹出警告⚠。报警告只是一方面,那它能不能把数组arr1
中的内存拷贝到数组arr2
中去呢?请看调试结果:
可以看到并没有把数组arr1的内容拷贝到数组arr2中去,**但是数组arr2中的首元素为什么会变成1呢?
**我们先来看数组arr1中的1 2 3 4 5在内存中的布局:
当前机器是小端存储
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
由此我们可以发现这里用strcpy
函数压根就做不到,stycpy的局限性就体现出来了。
由此也出现了内存函数:memcpy、memmove、memcmp、memset
。
memcpy叫做内存拷贝,只要是放到内存中的数据我们都可以进行拷贝。
1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
2.这个函数在遇到'\0'
的时候并不会停下来。
3.如果source和destination有任何的重叠,复制的结果都是未定义的。
在这之前我们还是要回顾以前的回调函数qsort
中的void*-通用类型的指针-无类型指针-即可以接受任意类型的指针
。
所以,我们来看memcpy
函数
的参数:void* memcpy(void * destination, const void * source ,size_t num);
这种参数设计的方式与函数strncpy
的参数设计方式类似,可以对照学习一下。注意这里参数num
的单位是字节。
我们来看举例:
再来举一个结构体类型的数组,请看举例:
知道这里我们就大体对函数memcpy
的功能有了一定了解,下面我们来模拟实现memcpy
函数的功能。
#include
#include
#include
struct S
{
char name[20];
int age;
};
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[5] = { 0 };
struct S arr3[] = { {"张三",20},{"李四",30} };
struct S arr4[3] = { 0 };
my_memcpy(arr2, arr1, sizeof(arr1));
my_memcpy(arr4, arr3, sizeof(arr3));
return 0;
}
然而我们写的这个模拟memcpy函数只能处理内存不重叠的情况;当内存出现重叠时,我们会有专门的函数memmove
来处理。
所以,memmove
函数可以处理这种重叠拷贝的现象。
刚刚说过我们写的my_memcpy
函数无法处理重叠拷贝的情况,其实库里的memcpy
函数是可以实现重叠拷贝的情况的,请看举例:
可以看到库里的memcpy
函数的确可以处理重叠拷贝的情况,但是我们刚刚写的my_memcpy
函数却无法做到,这并不意味的我们✍的这个my_memcpy
函数不好。
因为C语言标准规定:memcpy
函数只需要能处理不重叠的内存拷贝就可以了。
而memmove
函数用来处理内存重叠的拷贝。
所以说,我们写的my_memcpy
函数刚刚好满足C语言的标准。并没有一些额外的功能。
下面我们来模拟实现memmove
函数。
这里涉及到到底是从前向后拷贝还是从后向前拷贝的问题。
#include
#include
void* my_memmove(void* dest, const void* src, size_t count)
{
void* ret = dest;
assert(dest && src);
if (dest < src)
{
//前->后
while (count--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
}
else
{
//后->前
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这种写法也可以:
#include
#include
void* my_memmove(void* dest, const void* src, size_t count)
{
void* ret = dest;
assert(dest && src);
if (dest < src||(char*)dest>(char*)src+count)
{
//前->后
while (count--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
}
else
{
//后->前
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
memcmp:int memcmp(const void * ptr1, const void * ptr2, size_t num );
(内存比较)
比较从ptr1和ptr2指针开始的num个
字节
请看举例:
说明前8个字节一样。倘若我们要比较前9个字节呢?请看:
注意是小端存储。
memset—内存设置
memset:void *memset(void *dest,int c,size_t count);
这里的c
是你要设置的字符是什么。
这里的count
是你要设置多少个字符。
请看举例:
那我们可不可以这样使用呢?请看:
#include
#include
int main()
{
int arr[10] = {0};
memset(arr, 1, 10);
return 0;
}
上述的代码就是打错特错,千万要记住:memset(arr,1,10),这里的10并不是10个元素的意思,而是10个字节。
如果你非要看一下调试结果,可以🙆,请看:
这是怎么一回事呢?数组arr的大小是40个字节,而这里在40个字节中我们对前10个字节进行更改,而且前十个字节我们改成了1。
即:01 01 01 01 01 01 01 01 01 01 00 00 00 00...
切记:memset函数可以把一块任何什么数据的空间都可以改,但是它改的单位是字节
,我们要改几个字节,每个字节的内容又是什么。