目录
1.memcpy函数
1.1函数介绍
1.2函数示范使用
1.3函数的模拟实现
1.4补充
2.memmove函数
2.1函数介绍
2.2函数的使用示范
2.3函数的模拟实现
3.memcmp(内存比较函数)
3.1函数介绍
3.2函数的示范使用,有趣的例子
4.函数补充memset(内存设置函数)
4.1函数介绍
4.2函数示范使用
5.结语
1.memcpy函数
引入:之前我们讲过字符串的拷贝函数,但是当我们要拷贝整型数据或者结构体数组等类型弟弟数据的时候,就需要一个包容性更好的函数了。
1.1函数介绍
函数头文件:string.h
函数参数:
①目的地
指向要复制内容的目标数组的指针,类型转换为 void* 类型的指针。
②源
指向要复制的数据源的指针,类型转换为 const void* 类型的指针。
③数量
要复制的字节数。 size_t 是无符号整数类型。
返回值类型:void*
返回值:返回目的字符串的地址也就是考到到某个地方,这个某个地方的地址。
函数功能:
函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
1.2函数示范使用
struct {
char name[40];
int age;
} person, person_copy;
int main()
{
char myname[] = "Pierre de Fermat";
memcpy(person.name, myname, strlen(myname) + 1);
person.age = 46;
memcpy(&person_copy, &person, sizeof(person));
printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
return 0;
}
输出:
1.3函数的模拟实现
函数模拟实现思想:我们参照库函数1来写,我们的想法是将源src的地址起的num个自己的内容拷贝到以目的地址dest为起点的空间中。那么由于我们的函数是要实现不同类型的内容都可以兼容拷贝,那么参数肯定是要设计为我们的void*类型来实现广泛接受,当我们实现的时候我们又想一个字节一个字节的遍历,那就应该将原先的void*的地址,转换为char*d的地址,以num为循环遍历拷贝条件,最后返回我们的目的空间的其实地址就行。
加下来我们看代码实现:
#include<stdio.h>
#include<assert.h>
#include <string.h>
void* my_memcpy(void* dst, const void* src, size_t count)
{
void* ret = dst;//保存目的空间地址
assert(dst);
assert(src);//断言,因为后续有指针的解引用操作,防止指针为空指针
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
struct {
char name[40];
int age;
} person, person_copy;
int main()
{
char myname[] = "woaini";
my_memcpy(person.name, myname, strlen(myname) + 1);
person.age = 46;
my_memcpy(&person_copy, &person, sizeof(person));
printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
return 0;
}
我们来看一下实现效果:
1.4补充
①关于 dst = (char*)dst + 1;指针的移动
首先我们知道void* l类型的指针是不支持指针运算的,所以要强制转换类型,然后因为char*类型的指针一步的长度刚好是一个字节,控制力度较好控制,所以转换为了字符型指针。
那么
可以这样写吗:(char*)dest++;
这种写法是错误的,因为强制类型转换是临时的,强制类型并不会改变这个指针本身弟弟类型,只是我用的时候临时转换的,后置++是先试用再++,等加加的时候,已经不能够在使用了。
那么:++(char*)dest 呢
这种写法可以但是有些编译器会报错,所以还是比较推荐第一种写法。
②我们想一下这个问题:
如果我们拷贝的空间是重复的呢,,比如我们有一个整型数组:
int arr1 = [1,2,3,4,5,6]
我们这样写:memecpy(arr1+2,arr1,60
我们原本是想得到:
1,2,1,2,3,4,5,6但是如果我们使用memcpy就会出现这样的情况
但是,当我们去编译器里实现这段代码我们来看一下结果:
我们发怎么和我们预计的不一样,这个函数好像对于重叠的内存空间也能正常拷贝呀
这就是最后要补充的点:
memcpy确实是不负责重叠拷贝的。这是因为最初设计memcpy函数的时候我们是要求只用实现不重叠拷贝就好(相当于60分),在Vs上函数实现了重叠拷贝(相当于100分),哪也可以,但是我们接下来要介绍的这个函数才是正宗的负责重叠拷贝的。我们接着next.
2.memmove函数
2.1函数介绍
函数头文件:<string.h>
函数参数:
void * destination:指向要复制内容的目标数组的指针,类型转换为 void* 类型的指针。 const void * source:指向要复制的数据源的指针,类型转换为 const void* 类型的指针。 size_t num :要复制的字节数。 size_t 是无符号整数类型。返回值:返回目标数组的地址
函数功能:将 num 字节的值从源指向的位置复制到目标指向的内存块。复制就像使用中间缓冲区一样进行,从而允许目标和源重叠。
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
2.2函数的使用示范
刚刚上面举例的数组就是一个示范,下面用一个字符数组来示范:
#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.3函数的模拟实现
对于函数的返回值由于不知道传递进入的目标首地址会是什么类型,我们就用Void*作为返回类型,对于函数参数的设计,目的数组和源数组的地址都不确定是什么类型所以设计为void*类型,由于源字符串我们不会去操作改变,所以我们用const修饰。
由于函数实现的同一片空间的内容拷贝所以请看图解实现思想:
由上图得出结论一:当我们的dst<= src,也就是说目标地址要小于源地址的时候应该先将源字符串首地址的内容拷贝到目的数组的地址中,循环拷贝num个字节。
结论②:当src<=dest<=src+num时, 目标地址要大于等于源地址并且小于等于源地址+num字节的的时候应该先将源字符串尾部地址的内容拷贝到目的数组的尾部地址中,循环拷贝num个字节。
当dest大于src+num时,随便怎么拷贝循序都行1,为了方便书写条件,我们就可以将其归结到上述两种情况其中之一,最好是第二种情况。
第一种实现:
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;//保存目的空间地址
assert(dest && src);//断言判断
if (dest < src)//虽然void*不可以解引用或者算术运算,但是可以比较大小,因为保存的是地址编号
{
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;
}
int main()
{
char str[] = "memmove can be very useful......";
my_memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
第二种实现大家可以试一下:
void * memmove ( void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count))
{
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else {
dst = (char *)dst + count - 1;
src = (char *)src + count - 1;
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
3.memcmp(内存比较函数)
3.1函数介绍
头文件包含:string.h
函数参数:
PTR1型
指向内存块的指针。
PTR2型
指向内存块的指针。
数量
要比较的字节数。
返回值:
返回值 表明 <0
在两个内存块中不匹配的第一个字节在 ptr1 中的值低于 ptr2 中的值(如果计算为 unsigned char 值) 0
两个内存块的内容相等 >0
在两个内存块中不匹配的第一个字节在 ptr1 中的值大于 ptr2 中的值(如果计算为 unsigned char 值)
函数:是根据指针指向的内容一个字节一个字节的往后面进行比较,直到比出大小或者达到要求比较的字节数num.
3.2函数的示范使用,有趣的例子
int main()
{
int arr1[] = { 1,2,1,4,5,6 };
int arr2[] = { 1,2,257 };
int ret1 = memcmp(arr1, arr2, 9);
int ret2 = memcmp(arr1, arr2, 10);
printf("%d\n%d\n", ret1, ret2);
return 0;
}
两个数组前九个字节是一模一样的,这样说明我们的这个memcmp是一个字节一个字节的比较的
4.函数补充memset(内存设置函数)
4.1函数介绍
函数功能:将指定空间的num个字节设置为指定的value值,返回的是指定空间的地址。
4.2函数示范使用
int main()
{
char arr[] = { "hello word" };
memset(arr + 1, 'x', 4);
printf("%s\n", arr);
return 0;
}
5.结语
以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,感谢大家的关注与喜欢。