1. memcpy函数
1.1 函数的声明
void * memcpy ( void * destination, const void * source, size_t num );
- destination
destination指向的是接收数据的目标,其类型转化为void*类型。
- source
source指向提供数据复制的源头, 其类型转化为void*类型。
- num
按字节为单位进行复制,size_t类型是无符号的整型类型
将destination指向的地址返回
1.2 函数的功能
- 第一段
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。
- 第二段
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
- 第三段
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
- 第四段
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小,且不能出现destination和source指向的位置重叠(如果想要重叠,memmove是一个更安全的方法)。
1.3 函数的使用
/* memcpy example */ #include <stdio.h> #include <string.h> struct { char name[40]; int age; } person, person_copy; int main () { char myname[] = "Pierre de Fermat"; /* using memcpy to copy string: */ memcpy ( person.name, myname, strlen(myname)+1 ); person.age = 46; /* using memcpy to copy structure: */ memcpy ( &person_copy, &person, sizeof(person) ); printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age ); return 0; }
1.4 函数的模拟实现(重点)
1.4.1 模拟分析
(1)首先,我们不知道要被复制的数据是什么类型,所以参数只能是void*来接收数据,既然不知道是什么数据类型,所以我们也不清楚指针要怎么跳跃去复制,所以只能一个一个字节去复制,这就必须在函数体内把destination和source指向强转成char*(这里就会有同学问了,为什么能直接用char*接收呢?答:因为char*只能接受char*的指针,而void*类型虽然不能直接访问,但是可以接收任何类型的指针)。且观察mencpy,发现需要一个目标指针destination、源头指针source和字节个数size_t num。
(2)source指针指向的内容是不需要改变的,所以我们可以加上const修饰,把里面的数据保护起来(const void* source)。
(3)为了实现链式访问,我们要将传进来的目标起始地址(destination)返回。由于这个函数在执行的时候会改变destination存储的内容,所以我们要重新创建一个void*类型的指针来代替destination指针移动。
(4)为了避免传进来的地址是空指针,我们需要用assert来断言传进来的地址不是空指针。
1.4.1 模拟实现
//自我实现memcpy的功能 void* my_memcpy(void* destination, const void* source, size_t num) { //先判断destination和source是不是为空 assert(destination && source); void* tmp = destination; while (num--) { //记住强转数据类型并不会永久改变变量的数据类型 *(char*)tmp = *(char*)source; ((char*)tmp)++; ((char*)source)++; } return destination; } int main() { int a[4] = { 0 }; int b[4] = { 1,2,3,4 }; int num = sizeof(b); my_memcpy(a, b, num); for (int i = 0;i < 4;i++) { printf("%d ", a[i]); } return 0; }
2. memmove函数
2.1 函数的声明
void * memmove ( void * destination, const void * source, size_t num );
- destination
destination指向的是接收数据的目标,其类型转化为void*类型。
- source
source指向提供数据复制的源头, 其类型转化为void*类型。
- num
按字节为单位进行复制,size_t类型是无符号的整型类型。
将destination指向的地址返回 。
2.2 函数的功能
- 第一段
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。复制就像使用了中间缓冲区一样,允许重叠复制。
- 第二段
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
- 第三段
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
- 第四段
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小。
2.3 函数的使用
/* memmove example */ #include <stdio.h> #include <string.h> int main() { char str[] = "memmove can be very useful......"; memmove(str + 20, str + 15, 11); puts(str); return 0; }
2.4 函数的模拟实现(重点)
2.4.1 模拟分析
- memcpy和memmove的比较
为什么需要memmove?就得了解memcpy和memmove的区别!
这个还要从上面的memcpy函数说起。因为memcpy函数不能将一个数组的中的数据拷贝到自身(也就是目标数据是自己,源数据也是自己,只不过是一个数组里面不同的位置的数据拷贝到另外一个位置上),如果像这样拷贝就会出现重叠拷贝,会导致结果不是我们预期的结果。
//使用我们自己的模拟的memcpy函数 int main() { int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; my_memcpy(arr + 2, arr, 24);//预期出现结果为1 2 1 2 3 4 5 6 9 10 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]);//实际出现结果 } return 0; }
- 细节分析
2.4.2 模拟实现
//自我实现memmove的功能 void* my_memmove(void* destination, const void* source, size_t num) { //先判断destination和source是不是为空 assert(destination && source); void* tmp = destination; //从前往后走 if (destination < source) { while (num--) { *(char*)tmp = *(char*)source; ((char*)tmp)++; ((char*)source)++; } } //从后往前走 else { while (num--) { *((char*)tmp + num) = *((char*)source + num); } } return destination; } int main() { int b[10] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(b, b + 3, 16);//b+3:是从4开始,预期结果:4,5,6,7,5,6,7,8,9,10 for (int i = 0;i < 10;i++) { printf("%d ", b[i]); } printf("\n"); int a[10] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(a+2, a, 24);//a+3:是从4开始,预期结果:1,2,3,1,2,3,4,8,9,10 for (int i = 0;i < 10;i++) { printf("%d ", a[i]); } }