目录
0.简介
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。 字符串常量适用于那些对它不做修改的字符串函数。因而我们可以通过字符串函数来实现字符串的拷贝、增加、检索等操作,那么是否有那么一些函数的操作对象不局限于字符串呢?答案是有的,正是我们接下来要介绍的内存操作函数.
我们都知道,数据都是以二进制数字形式储存在内存之中,那我们便可以通过内存函数来操作内存中的值从而来实现我们需要的功能,接下来我要说的是memcpy(实现内存数据拷贝,适用于两个对象)、memmove(实现内存部分数据拷贝,适用于一个对象)、memset(内存设置)、memcmp(内存比较)。
1.memcpy
首先,我们来看函数声明
我们来先不关注函数的void*的返回类型,我们先来看这个函数的参数。
1.void*destination 这个是我们要修改内存的起始位置
2.const void*source 拷贝内容的起始位置
3size_t num拷贝内容长度(单位是字节!!!)
示例程序:
#include<string.h>
#include<stido.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 0 };
printf("%d",*(int *)memcpy(arr2, arr1, 20));
return 0;
}
代码运行结果如下:
接下来解释下为什么要用*(int *)memcpy(arr2, arr1, 20)的形式。前面说过,该函数的返回类型为void*的指针形式,而在c中,void类型指针的解引用是不被允许的,故而我们要将之强制类型转为int类型再尔解引用以此还原我们的数据。
我将模拟实现这个函数来让大家理解这个函数的实现方式:
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);//断言,避免对空指针的解引用
void* ret = dest;//记录拷贝对象的起司地址
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}//进行两个元素的交换
return ret;//返回起始位置的地址
}
在我之前博客qsort的模拟实现中写过,要操作字节单位,我们要先将参数类型设置为void*再强制转化为char*解引用,这是因为在c中char*所能操作的大小为一字节,然后我们最后再将之转换为我们所需类型数据就不会被修改了,也能达到我们目的。
最后来实验一下我们的代码是否能行:
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);//断言,避免对空指针的解引用
void* ret = dest;//记录拷贝对象的起司地址
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}//进行两个元素的交换
return ret;//返回起始位置的地址
}
#include <stdio.h>
struct {
char name[40];
int age;
} person, person_copy;
int main()
{
char myname[] = "Pierre de Fermat";
my_memcpy(person.name, myname, strlen(myname) + 1);
person.age = 46;
/* using memcpy to copy structure: */
my_memcpy(&person_copy, &person, sizeof(person));
printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
return 0;
}
代码运行结果如下
2.memmove
这时候或许有同学会这么用这个代码。
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1+2, arr1, 20);
那这样子究竟能不能行呢?答案是不一定,还得看编译器,笔者试过在vs2019这个平台使用是没有问题的,但是在某些平台,会出现1 2 1 2 1 2 1 8 9 10这种情况,那么要怎么解决呢?
这也还好解决,c库函数中有memmove这个内存操作函数,正是用于我们这种情况的,按照惯例,想看函数声明:
void * memmove ( void * destination, const void * source, size_t num );
我们能看到,memove的声明和memcpy是一样的,那么两个函数有啥区别呢?memove和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就能使用memmove函数处理。
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1+2, arr1, 20);
以上代码的结果就是正确的了,结果为:1 2 1 2 3 4 5 8 9 10 ;
我还是以模拟实现方式带大家理解这个函数:
void* my_memmove(void* dest, const void*src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest < src)//目标地址在于拷贝地址前,从前往后拷贝
{
//前->后
while (count--)
{
*(char*)dest = *(char*)(src);
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else//目标地址在后面,从后往前拷贝
{
//后->前
while (count--)
{
*((char*)dest+count) = *((char*)src + count);
}
}
return ret;//返回目标位置的起始地址
}
要避免拷贝内容错误,我们就需要通过程序先确定目标地址和拷贝对象地址的前后位置从而来从前往后拷贝或者从后往前拷贝从而避免拷贝错误。
int main()
{
char str[] = "memmove can be very useful......";
my_memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
运行以上代码,结果为 memmove can be very very useful.
3.memset
接下来就是memset这个函数,看其名,我们就能猜测到这个函数是对内存进行更改的函数。
按照惯例,先看声明
void *memset( void *dest, int c, size_t count );
函数的返回类型依旧为void*,具体原因前面已经说明,这里就不多赘述了,接下来我们来说明每个参数的意义:void*dest——目标地址; int c目标位置所要更改的值;size_t count所要更改的数。那么这个函数具体怎么样呢?且看我下面的示例程序。
int main()
{
int arr[] = { 0x11111111,0x22222222,3,4,5 };
memset(arr, 6, 20);//memset是以字节为单位来初始化内存单元的
int i = 0;
for (i = 0; i < 5; i++)
printf("%p ", arr[i]);//%p打印出来的为地址
return 0;
}
结果为 06060606 06060606 06060606 06060606 06060606。
因此需要注意的是该函数是以字节为单位来初始化内存的。
4.memcmp
按照惯例,先看声明
int memcmp ( const void * ptr1,
const void * ptr2,
size_t num }
而内存比较,是通过每个字节来比较的,如果ptr1>ptr2则返回大于零的数,反而则返回一个小于零的数,等于则返回零。我们来看看示例程序
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,0x00000001 };
int arr2[] = { 1,2,3,4,0x11223305 };
int ret = memcmp(arr1, arr2, 18);
printf("%d\n", ret);
return 0;
}
输出结果为 -1。这个函数需要注意的就是通过每个字节来比较的,如果为
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,0x11001111,3,4,0x00000001 };
int arr2[] = { 1,0x00000001,3,4,0x11223305 };
int ret = memcmp(arr1, arr2, 18);
printf("%d\n", ret);
return 0;
}
则会输出 1.