【C语言】常见的内存函数使用(memcpy、memmove、memcmp以及memset的详细讲解)

本篇要分享的是常见的内存函数

前面分享的函数都是和字符串相关,但是当我们在操作数据的时候不仅仅要操作字符数据

接下来分享几个与内存相关的函数

目录

本篇要分享的是常见的内存函数

1.memcpy

2.memmove

自定函数模拟实现memmove函数

3.memcmp

4.memset


这些函数根本不会在乎需要拷贝什么类型的数据,交给这个函数就能帮你完成任务

1.memcpy

下面对函数作用的描述是把source指针指向的数据拷贝num个字节到destination空间里去,

那么直接上代码

#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	memcpy(arr2, arr1, 20);
	return 0;
}

上面代码的意思就是将arr1中的20个字节的数据拷贝到arr2数组中去

那我们调试起来验证一下

这是还没进入函数时arr2数组的内容

 再往下走一步

我们就会看到arr2数组内的数据就变成了arr1中20个字节的内容 

那这就是memcpy函数的简单的使用

那么我们用这个函数来处理浮点数的数据可以吗?

完全可以

测试下面的代码

void test2()
{
	float arr1[] = { 1.0,2.0,3.0,4.0,5.0 };
	float arr2[8] = { 0 };
	memcpy(arr2,arr1,12);
}
int main()
{
	test2();
	return 0;
}

我们将整型类型的数据都换成了浮点类型的数据

调试起来看看

 可以看到arr2数组已经初始化完成;

再往下走一步;

 此时我们就会看到arr1中的12个字节的数据,也就是前三个数据已经拷贝到了arr2数组当中 

所以我们就直到memcpy函数不在乎拷贝什么数据类型,他只会拷贝多少个字节的数据。

那我们在回头观察一下函数参数和返回类型时目标空间的起始地址,参数是void*类型的指针,那么我们之前也了解过void*类型的指针其实就是通用类型,可以接受任何类型数据的地址,这也是memcpy函数能够拷贝任何类型数据的原因。

那接下来我们不妨自己自定义函数模拟一个memcpy的函数

void* my_memcpy(void* dest,const void* src,size_t num)
{


void test3()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	my_memcpy(arr2,arr1,20);
}
int main()
{
	test3();
	return 0;  
}

那这个主函数相信你大家不难看懂;

void*类型的指针dest指向arr2数组中的首元素的地址,第二个参数void*类型的指针src指向arr1数组中的首元素的地址,第三个参数是整型参数意为传递20个字节;

接下来我们就想到拷贝的内容时直接将他解应用就可以了如下操作

但是我们会看到编译器会报错

这里要说明的是void*类型的指针不能直接解应用,而是要将其强制转化成想要转化成的类型

但是这个函数在拷贝的时候是要考虑到各种类型的,而且如果在使用的时候你想拷贝int类型,但是却在函数的第三个参数中输入了7,每次处理int类型的数据时会处理4个字节,所以第二次处理就会处理八个字节的数据,超过了7个字节,所以我们不妨将其强制转化成char*类型的指针,让他一次只处理一个字节的数据,并对其解应用

代码变成了这个样子

但是这样写还是会出错;

因为这样强制类型的转化只是临时改变了其类型,等再次需要调用函数时可能还会变成void*类型的指针,会出现错误,所以我们对其进行优化

void* my_memcpy(void* dest,const void* src,size_t num)
{
        *(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
}

 那么这么写的好处是,我们可以将强制转化后的类型保存下来,让其赋值给dest,这样的写法就很完美啦;

那这样只能处理一次,我们要处理num个字节的数据,不妨再加入while循环,

void* my_memcpy(void* dest,const void* src,size_t num)
{
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}

}

最后还需要加上一个返回值,必须是void*类型,但是这时候dest已经随着强制类型的转化离起始位置很远,所以我们不妨再创建一个指针变量在函数体的开始将其起始位置保存下来

void* my_memcpy(void* dest,const void* src,size_t num)
{
	void* p = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return dest;
}

 让代码调试起来

 可以看到arr2数组中的前二十个字节的数据已经被被改变了

那以后想要拷贝哪些内容直接使用memcpy会是一个不错的选择;

不过当我们如果要拷贝字符串的时候最好还是最好使用strcpy,因为memcpy函数中有一个操作是类型的强转换,如果处理数据较多的话会浪费很多时间,所以说尽量使用最匹配的函数。

当我们在使用memcpy出现了类似以下这种情况时;

void test4()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr1+2,arr1,20);

}
int main()
{
	test4();
	return 0;  
}

我写这段代码的目的是是想让memcpy函数从第三个数开始拷贝;让3,4,5,6,7变成1,2,3,4,5

 但是我们调试起来看到arr1数组中的元素并没有按我们想象中的变化

那么原理是怎么样的呢?

我们在拷贝内容的时候同时也会改变arr1中的内容,所以就等于一边改变arr1数组中的内容一边将其拷贝到arr1数组中,并且只拷贝20个字节的内容,所以改变后的arr1的内容是1,2,1,2,3,4,5,6,7,8,我们将前20个字节的内容拷贝到了arr+2后面的内容,所以就成为了如上图所示的数组内容

 所以我们发现在内存重叠的时候使用memcpy可能实现意想不到的后果;

建议在内存重叠的情况下使用memmove函数

2.memmove

从函数名字上可能会觉得和刚才的memcpy没有关系

但是我们从c++官网上观察一下

 他的返回类型和函数参数的类型van♂全一致;

 这时候使用memmove函数就会发现可以达到我们上述所说的目的

那我们不妨

自定函数模拟实现memmove函数

那么还是一样,我们一步一步来,

先将函数返回类型和返回参数写起来

void* my_memmove(void* dest, void* src, size_t num)
{

}

再次分析

我们是要将arr1数组中从3开始,数20个字节的元素,用原先的arr1中数组的元素将其覆盖,

那既然我们使用memcpy拷贝时会覆盖掉前面的一部分数据,那我们不妨先从后面拷贝其他的数据,再拷贝前面的数据。

但是我们还要分情况讨论

还是按照上面的数组举例,如果我们想要将arr1数组中的3,4,5,6,7拷贝到前五个元素的位置上呢?那就是要从前向后拷贝;

所以要分情况讨论

两种情况如何区分呢?给大家简单画个图

 第一种情况就是将蓝色框的数据放到红色框里去,这就需要从后往前拷贝数据

第二种情况是将蓝色框放到红色框里去,这种情况就需要从前向后拷贝数据

那第一种我们对比函数参数来看,数据来源于src指针,那目的地就是dest;

 第二种情况如下

 所以可以用if语句来判断

if(dest<src)
从前向后
else 
从后向前

那么集体要怎么实现呢

其实仔细观察你就会发现从前向后的方式和memcpy没有区别,完全可以直接剪下来

void* my_memmove(void* dest, void* src, size_t num)
{
    if(dest<src)
    {
            while (num--)
	    {
		    *(char*)dest = *(char*)src;
		    dest = (char*)dest + 1;
		    src = (char*)src + 1;
	    }
    }
    else 
    从后向前
}

从后向前循环依然是while(num--)

那我们既然是从后往前拷贝,那就要访问最后一个数的地址,那我们不妨将第一个元素的地址加上num;num开始是20进入到循环,num--变成19,dest是起始地址0,加上num就等于19,就是数组中第20个元素,也是最后一个元素,那么我们这样就访问到了最后一个元素的地址

*((char*)dest + num) = *((char*)src + num);

我们不妨将这个地址解应用并且将指向src的指针,也就是指向原数组的指针解应用赋值给目标空间,这样就完成了将源空间的值给了目标地址的操作,依然要加入while循环

当然函数返回值是一个指针类型,要指向数组首元素,所以依然要重新定义一个指针变量来存放首地址,在函数末尾将其返回。

最后代码如下

void* my_memmove(void* dest, void* src, size_t num)
{
    void* ret = dest;
	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;

}

 那么最终的调试结果出来大家也可以看到成功的将1 2 3 4 5 放到了3 4 5 6 7的位置,

我们再将函数实参内容调换一下位置

监视的内容也得到了,我们将后面的内容拷贝到了前面

那memcpy和memmove的区别就很好理解了

memcpy拷贝的是不重叠的内存

memmove可以拷贝重叠的内存

当然也可以粗暴的理解为memmove使用的范围要大于memcpy

3.memcmp

那这个函数可能大家也都猜得到,是两块内存进行比较

 他的返回类型是int类型的整形,参数是两个任意类型的指针(因为不知道要比较的内存是什么类型),和一个size_t(无符号整形)类型的字节数;

比较的是从ptr1和ptr2指针开始的num个字节;

那和strcmp函数也是有相似之处的,就比如说下面的表格

ptr1指针指向的内存小于ptr2指针指向的内存中的数据,返回小于零的值;

反之则返回大于0的值;相等就返回零

 代码简单使用一下

  此时比较的是前16个字节的数据,也就是1 2 3 4 那上下都相等,返回0;

修改一下比较的对象

 此时比较的是17个字节的数据,那多出一个字节该怎么比较呢?

就要提到我们所学过的大端储存方式和小端储存方式了

在vs的环境下是小端储存方式(低位字节数据存放在高地址,高位字节数据存放在低地址),

我们将内容数组内容展示出来

 那第十七个比特位就是05和06进行比较,返回值就为小于零的数;

4.memset

memset我们在之前也有接触过

 了解一下这个函数的返回类型和参数;

不难看到返回参数是void*类型,第一个参数是void*类型的指针,第二个参数是int类型,第三个是无符号整形;

他的作用就是在第一个参数指向的数组中将num个字节的内容都改变成value,

简单使用代码如下

#include<stdio.h>
int main()
{
	char arr[] = "hello world";
	memset(arr, 'x', 5);
	printf("%s\n",arr);
	return 0;
}

 可以看到"hello world "变成了"xxxxx world"

那这个字符串的作用就是将arr数组中的前5个元素都改成x,通过代码简单的使用应该不难理解。

这个函数唯一要注意的是最后一个参数是以字节为单位来设置的。

那如果要改动数组,将数组 的所有元素改成1 呢?

 很遗憾不能将数组中的元素改为1,因为memset是以字节为操作单位的,所以编译器会将01 01 01  01看作一个整形,换做十进制我们再打印出来

 我们看到是是个非常大的数字;所以无法用memset来设置整型数组的内容

以上就是所要分享的内存函数的内容,希望对你有所帮助

猜你喜欢

转载自blog.csdn.net/wangduduniubi/article/details/129560860
今日推荐