我们首先知道,所谓的字符串函数: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
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<stdio.h> #include<string.h> #include<assert.h> 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
所以,memmove函数可以处理这种重叠拷贝的现象。
刚刚说过我们写的my_memcpy函数无法处理重叠拷贝的情况,其实库里的memcpy函数是可以实现重叠拷贝的情况的,请看举例:
可以看到库里的memcpy函数的确可以处理重叠拷贝的情况,但是我们刚刚写的my_memcpy函数却无法做到,这并不意味的我们✍的这个my_memcpy函数不好。
因为C语言标准规定:memcpy函数只需要能处理不重叠的内存拷贝就可以了。
而memmove函数用来处理内存重叠的拷贝。
所以说,我们写的my_memcpy函数刚刚好满足C语言的标准。并没有一些额外的功能。
下面我们来模拟实现memmove函数。
模拟实现
这里涉及到到底是从前向后拷贝还是从后向前拷贝的问题。
#include<stdio.h> #include<assert.h> 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<stdio.h> #include<assert.h> 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
memcmp:int memcmp(const void * ptr1, const void * ptr2, size_t num );(内存比较)
比较从ptr1和ptr2指针开始的num个字节
请看举例:
说明前8个字节一样。倘若我们要比较前9个字节呢?请看:
注意是小端存储。
memset
memset—内存设置
memset:void *memset(void *dest,int c,size_t count);
这里的c是你要设置的字符是什么。
这里的count是你要设置多少个字符。
请看举例:
那我们可不可以这样使用呢?请看:
#include<stdio.h> #include<string.h> 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函数可以把一块任何什么数据的空间都可以改,但是它改的单位是字节,我们要改几个字节,每个字节的内容又是什么。