1、memcpy 函数
![](https://i-blog.csdnimg.cn/blog_migrate/eae1541bf032b4ab7d2622df14841c12.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e6b3d5e4a569fa96d794f6e53472aff3.png)
memcpy函数的参数涉及到了空类型指针void*,即“通用类型的指针”,它可以接受任意类型数据的地址。为什么要用void*呢?因为该函数的设计者并不知道未来程序员使用memcpy拷贝的是什么类型的数据。
注意:void指针不能直接解引用和进行加减运算!
怎么自己实现一个memcpy函数呢?
#include<stdio.h>
#include<assert.h>
//memcpy函数返回的是目标空间的起始地址
void* my_memcpy(void* dest, const void* src, unsigned int num)
{
void* ret = dest;
assert(dest&&src);
//*(char*)dest = *(char*)src;//强制类型转换是临时的
//(char*)dest++;
//(char*)src++;//不可以,强制类型转换是临时的,第一行代码虽然已经转化为char*了,但是
//这里又变回void*,仍然不可以++;
/**(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;*/ //可能可以,但是并不能保证它永远是正确的
/*
*(char*)dest++ = *(char*)src++; //没有问题
*/
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//这里。反之dest是一个void*,是可以接收char*的;
src = (char*)src + 1;
}
return ret;
}
想要拷贝字符串,除了用strcpy,用该函数也可以,但是因为该函数包含了强制类型转换,太浪费时间了,所以还是优选strcpy。
注意,内存函数和字符串函数是一个包含的关系!
void test()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
my_memcpy(arr+2,arr,20);
int i=0;
for(i=0;i<10;i++)
{
printf("%d ",arr[i]);
}
}
现在我们想要把一个数组中的数据截取一段,放到另一段同在一个数组的数据段上,memcpy就不可以实现了。因为它一边拷贝,一边将需要拷贝的源数据改变了。
所以,在内存重叠的时候,使用memcpy函数会出现意想不到的效果。那么,这个时候,我们就将引出memmove函数。
2、memmove 函数
![](https://i-blog.csdnimg.cn/blog_migrate/1ba2add6c5208eb405aa0cffd35c4afd.png)
实现自己的memmove函数:
因为当内存重叠时,一部分源数据拷贝过去时会覆盖掉还未进行拷贝的源数据,所以应当思考dest和src的位置关系。何时从前向后拷贝?何时从后向前拷贝?
![](https://i-blog.csdnimg.cn/blog_migrate/2b0d40596ce1b4fa25d01ed805ba99c1.png)
这时候有两种方法可供选择:
![](https://i-blog.csdnimg.cn/blog_migrate/87f6ee7cec52bfe916221633bf81c05a.png)
(1)、1和3从前——>后,2从后——>前;
(2)、2和3从后——>前,1从前——>后;
void* my_memmove(void* dest, const void* src, unsigned int num)
{
void* ret = dest;
assert(dest && src);
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 ret;
}
但是,事实上,C语言自带的memcpy函数对于内存重叠的情况也可以正确获得结果。但是不能保证每个编译器都是这样。
C语言规定:memcpy拷贝不重叠的内存;
memmove拷贝重叠的内存;
memmove>memcpy
只是把它们的功能变成一样的。
3、memcmp 函数
![](https://i-blog.csdnimg.cn/blog_migrate/5b76bbb36a7177b5839c47cfdce79fa6.png)
#include<string.h>
#include<assert.h>
void test()
{
int arr1[] = { 1,2,3,4,5};
int arr2[] = { 1,2,3,4,6};
int ret = memcpy(arr1,arr2,16);
printf("%d\n",ret);
}
int main()
{
test();
return 0;
}
#include<string.h>
#include<assert.h>
void test()
{
int arr1[] = { 1,2,3,4,5};
int arr2[] = { 1,2,3,4,6};
int ret = memcpy(arr1,arr2,17);
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 05 00 00 00
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 06 00 00 00
printf("%d\n",ret);//-1
}
int main()
{
test();
return 0;
}
4、memset 函数
![](https://i-blog.csdnimg.cn/blog_migrate/0945cf04b2bddd21e9f3bed1c4cdcd13.png)
void test()
{
char arr[] = "hello world!";
memset(arr, 'x', 5);//以字节为单位来设置
printf("%s\n", arr);
}
void test()
{
int arr1[10] = { 0};
memset(arr1, 1, 10);//错误的!
//如果把10改为sizeof(arr)呢?
//按%p打印
/*for(i=0;i<10;i++)
{
printf("%p ",arr[i]);
}*/
//01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
//它把每个字节都设置为1.
//这种写法无法将数组的每个元素设置成1!
printf("%s\n", arr1);
}
int main()
{
test1();
test2();
return 0;
}
补充一下:printf("%p ",arr[i]); 的打印意义:
%p是打印地址(指针地址)的,按照的是十六进制的形式,有多少位打印多少位。
大家应该都知道,在32位编译器的指针变量为4个字节(32位),64位编译器的指针
变量为8个字节(64位)。所以,在32位编译器下,使用%p打印指针变量,会显示
32位的地址(16进制的);在64位编译器下,使用%p打印指针变量,会显示64
位的地址(16进制的),左边空缺的会补0。
%x:无符号十六进制整数(小写字母,不会如上那样补零);
%X:无符号十六进制整数(大写字母,不会如上那样补零);
%x和%X和%p的相同点都是十六进制,不同点是%p按编译器位数长短(32位/64位)输出地址,不够的补零。
如上,如果需要打印的是数组元素的地址:
for(i = 0 ; i < 5 ; i++)
{
printf("%p ",&arr[i]);
}
所以,如果我们上面那个代码是按照“%x”来打印的,那么就会是:
1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101
而如果是按照“%p”来打印,前面会补充0:
01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
还有一点要注意的是,memset(void *s, int value ,size_t num);中所赋值(the
specified value (interpreted as an unsigned char))实际范围应该在0~~255,
因为该函数只能取value的后八位赋值给你所输入的范围的每个字节,比如int a[5]
赋值memset(a,-1,sizeof(int )*5)与memset(a,511,sizeof(int )*5) 所赋值的结果是
一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)
而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为
(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其
结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论
value多大只有后八位二进制有效,而后八位二进制的范围在(0~255)中改。而对字
符数组操作时则取后八位赋值给字符数组,其八位值作为ASCII码。
函数原型图片截图来源于网址:https://cplusplus.com