紧接字符串函数,出场的是第一个内存函数memcpy。前面讲的字符串函数是专门干关于字符串的事的,而这个函数可以干strcpy一样的事,但是区别就是它碰到\0也会继续复制。
函数的头文件是string,返回类型是void*,参数有两个,一个是目的地地址,一个是源的地址,还有一个是整型,这个整型代表的是要复制多少个字节,返回值是目的地的地址,因为源的值是不能被修改的,所以用const修饰,因为参数是void*,所以在一会儿模拟实现的时候我们就要强制转化成char*的,毕竟是修改字节。
先看一段简单的代码。
int main()
{
int arr1[10] = { 1,2,9,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, &arr1[4], 9);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
这串代码的意思就是从arr1[4]开始给arr2复制,复制9个字节,还是很好理解的,那如果不是在一个空间呢?比如我把arr1[4]的位置给arr1开始复制,会不会报错呢?答案是可能会。
如果目的地和源的内存有任何重叠的话,复制的结果都是未定义的,也就是说mencpy的行为是不可预测的,咱也不知道它会干啥,像这样。
int main()
{
char str[] = "Hello, World!";
memcpy(str + 7, str, 7);
printf("%s\n", str);
return 0;
}
结果是未知的,所以重叠的部分就不应该用mencpy了。
好了,我们现在就模拟实现一下memcpy函数。
主函数开始,传两个地址和一个整型过去,因为复制的是字节,所以我们可以使用字节数当作循环变量,for while循环都可以,因为参数都是泛型指针,所以有必要强制转化为char*指针,转化之后就是复制了,这里有个需要注意的点就是不能直接*(char*)p1++,这样系统会报错的,我们就可以换一个思路,如下。
当然,返回的地址需要用一个临时变量存起来,最后返回就行了,因为返回的是泛型指针,所以临时变量的指针类型也是void*。
void my_memcpy(void* p2, const void* p1, size_t num)
{
void* ret = p2;
for ( ; num > 0; num--)
{
*(char*)p2 = *(char*)p1;
(char*)p2 = (char*)p2 + 1;
(char*)p1 = (char*)p1 + 1;
}
return ret;
}
int main()
{
srand((unsigned int)time(NULL));
int arr1[10] = { 1,2,9,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int num = rand() % 40 + 1;
my_memcpy(arr2, arr1, num);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
读者可以自行实验一下。
memcpy是不能让同一块空间复制的,但是menmove就可以,它和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
先上代码实验一下。
int main()
{
char arr[] = "Hello world!";
memmove(arr, arr + 1, 3);
printf("%s", arr);
return 0;
}
最后的结果就是elllo world!,内存重叠了在memmove这里是没有任何问题的,但是在vs里面尝试对重叠的空间使用memcpy可能是不会报错的,这与vs有关,我们先不用在意。
现在讨论模拟实现memmove。
我们首先想为什么使用内存函数需要考虑空间是否重叠,这是因为如果重叠了就会导致内存复制的时候复制过去上一次复制留下的元素,那么解决方案是什么?是单独拿一块空间出来存储要存放的元素吗?这个实际上可以,但是不免会浪费挺多空间。较好的办法就是改变复制的顺序,如下。
假定红色区域是来源,绿色和蓝色部分是目的地,均有重叠,因为重叠无非就是前面重叠或者是后面重叠,所以有两种情况
第一种情况,绿色情况,假如从前往后复制,即从5开始复制是没有问题的,因为重叠部分5复制的时候9没有发生改变,如果是从后往前复制,即从9开始复制,那么最后复制5的时候,5已经发生了改变,复制就会出错。
第二种情况,蓝色情况,假如从前往后复制,即从5开始复制,那么在复制13那个位置的时候。9已经发生了改变,变成了5,所以复制会出错,同理,后往前复制就不会出错。
可以总结一个小规律:复制的方向取决于重叠的部分,只需要保证在复制的时候,重叠的部分还没有被复制就行,所以一共就两种情况。
void my_memmove(void* p1, const void* p2, size_t num)
{
void* ret = p2;
if (p1 < p2)//前往后
{
while (num)
{
*(char*)p1 = *(char*)p2;
(char*)p1 = (char*)p1 + 1;
(char*)p2 = (char*)p2 + 1;
num--;
}
}
else//后往前
{
(char*)p2 = (char*)p2 + num - 1;
(char*)p1 = (char*)p1 + num - 1;
while (num)
{
*(char*)p1 = *(char*)p2;
(char*)p1 = (char*)p1 - 1;
(char*)p2 = (char*)p2 - 1;
num--;
}
}
return ret;
}
int main()
{
char str1[100] = "Hello world!";//Hehellworld
my_memmove(str1 + 2, str1, 4);
printf("%s", str1);
return 0;
}
我们讨论的就是传过去的地址的大小是谁高,如果目的地的地址高于来源的地址,那么复制的方向就是从前往后,如果低于,那么复制的地址就是从后往前。特别要注意的是第二种情况,如果是从后往前,那么两个指针指向的位置都需要发生改变,所以需要先加上复制的字节数。第二种情况下,容易犯错的是指向的位置应该加字节数在建减一个1,这点可以自行实验一下。
memmove函数模拟实现就完成了。
可以这样理解,
memcpy可以实现的memmove都可以实现,唯一的区别只是内存空间不能重叠的问题。
在cplusplus中,memset的返回值void*,返回的是传进去的地址,参数有三个,泛型指针,存进去的值,设置的字节数。
这个函数的功能就是把内存中的值设置成自己想要的值,那有人问了,为什么第二个参数是int类型的,这是因为字符在计算机中实际上是以AscII值的形式读取的,所以参数类型是int类型。
int main()
{
char arr1[100] = "abcdefg";
memset(arr1,'*',6);
printf("%s", arr1);
return 0;
}
这是一个简单应用,那么现在模拟实现一下,模拟实现难度不大,每个字节设置一下就行了。
void* my_memset(void* p1, int tem, size_t num)
{
void* ret = p1;
while (num--)
{
*(char*)p1 = tem;
(char*)p1 = (char*)p1 + 1;
}
return ret;
}
int main()
{
char arr1[100] = "abcdefg";
my_memset(arr1, '*', 3);
printf("%s", arr1);
return 0;
}
根据cplusplus,memcmp的返回值是int类型的,实际上和strcmp一样,返回的值就是1 0 -1,strcmp是用来比较字符串的,memcmp就是用来比较内存的,比较的每个字节每个字节的比较的。
头文件还是string,参数有3个,分别是两块内存的地址和比较的字节数的大小,话不多说,看看简单的使用。
int main()
{
char str1[100] = "abCdefg";
char str2[100] = "abcdefg";
int ret1 = memcmp(str1,str2,3);
int ret2 = memcmp(str2, str1, 3);
int ret3 = memcmp(str1, str2, 2);
printf("%d\n", ret1);
printf("%d\n", ret2);
printf("%d\n", ret3);
return 0;
}
所以字符串函数能做到的许多内存函数也是可以做到的,模拟实现和strcmp很是相似,这里就不模拟实现了,重点是memmove的使用和模拟实现。
感谢阅读!