内存函数详解

1、memcpy 函数

memcpy函数的参数涉及到了空类型指针void*,即“通用类型的指针”,它可以接受任意类型数据的地址。为什么要用void*呢?因为该函数的设计者并不知道未来程序员使用memcpy拷贝的是什么类型的数据。

注意:void指针不能直接解引用和进行加减运算!

怎么自己实现一个memcpy函数呢?

#include<stdio.h>
#include<assert.h>
//memcpy函数返回的是目标空间的起始地址
void* my_memcpy(void* dest, const void* src, unsigned int num)
{
    void* ret = dest;
    assert(dest&&src);

    //*(char*)dest = *(char*)src;//强制类型转换是临时的
    //(char*)dest++;
    //(char*)src++;//不可以,强制类型转换是临时的,第一行代码虽然已经转化为char*了,但是
    //这里又变回void*,仍然不可以++;

    /**(char*)dest = *(char*)src;
    ((char*)dest)++;
    ((char*)src)++;*/ //可能可以,但是并不能保证它永远是正确的

    /*
    *(char*)dest++ = *(char*)src++;  //没有问题
    */

    while (num--)
    {
        *(char*)dest = *(char*)src;
        dest = (char*)dest + 1;//这里。反之dest是一个void*,是可以接收char*的;
        src = (char*)src + 1;
    }
    return ret;
}

想要拷贝字符串,除了用strcpy,用该函数也可以,但是因为该函数包含了强制类型转换,太浪费时间了,所以还是优选strcpy。

注意,内存函数和字符串函数是一个包含的关系!

void test()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    my_memcpy(arr+2,arr,20);
    int i=0;
    for(i=0;i<10;i++)
    {
        printf("%d ",arr[i]);
    }
}

现在我们想要把一个数组中的数据截取一段,放到另一段同在一个数组的数据段上,memcpy就不可以实现了。因为它一边拷贝,一边将需要拷贝的源数据改变了。

所以,在内存重叠的时候,使用memcpy函数会出现意想不到的效果。那么,这个时候,我们就将引出memmove函数。

2、memmove 函数

实现自己的memmove函数:

因为当内存重叠时,一部分源数据拷贝过去时会覆盖掉还未进行拷贝的源数据,所以应当思考dest和src的位置关系。何时从前向后拷贝?何时从后向前拷贝?

这时候有两种方法可供选择:

(1)、1和3从前——>后,2从后——>前;

(2)、2和3从后——>前,1从前——>后;

void* my_memmove(void* dest, const void* src, unsigned int num)
{
    void* ret = dest;
    assert(dest && src);
    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;
}

但是,事实上,C语言自带的memcpy函数对于内存重叠的情况也可以正确获得结果。但是不能保证每个编译器都是这样。

C语言规定:memcpy拷贝不重叠的内存;

memmove拷贝重叠的内存;

memmove>memcpy

只是把它们的功能变成一样的。

3、memcmp 函数

#include<string.h>
#include<assert.h>
void test()
{
    int arr1[] = { 1,2,3,4,5};
    int arr2[] = { 1,2,3,4,6};
    int ret = memcpy(arr1,arr2,16);
    printf("%d\n",ret);
}
int main()
{
    test();
    return 0;
}

#include<string.h>
#include<assert.h>
void test()
{
    int arr1[] = { 1,2,3,4,5};
    int arr2[] = { 1,2,3,4,6};
    int ret = memcpy(arr1,arr2,17);
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 05 00 00 00 
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 06 00 00 00 
    printf("%d\n",ret);//-1
}
int main()
{
    test();
    return 0;
}

4、memset 函数

void test()
{
    char arr[] = "hello world!";
    memset(arr, 'x', 5);//以字节为单位来设置
    printf("%s\n", arr);
}
void test()
{
    int arr1[10] = { 0};
    memset(arr1, 1, 10);//错误的!
    //如果把10改为sizeof(arr)呢?
    //按%p打印
    /*for(i=0;i<10;i++)
     {
        printf("%p ",arr[i]);
     }*/
    //01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101 
    //它把每个字节都设置为1.
    //这种写法无法将数组的每个元素设置成1!
    printf("%s\n", arr1);
}
int main()
{
    test1();
    test2();
    return 0;
}

补充一下:printf("%p ",arr[i]); 的打印意义:

%p是打印地址(指针地址)的,按照的是十六进制的形式,有多少位打印多少位。

大家应该都知道,在32位编译器的指针变量为4个字节(32位),64位编译器的指针

变量为8个字节(64位)。所以,在32位编译器下,使用%p打印指针变量,会显示

32位的地址(16进制的);在64位编译器下,使用%p打印指针变量,会显示64

位的地址(16进制的),左边空缺的会补0。

%x:无符号十六进制整数(小写字母,不会如上那样补零);

%X:无符号十六进制整数(大写字母,不会如上那样补零);

%x和%X和%p的相同点都是十六进制,不同点是%p按编译器位数长短(32位/64位)输出地址,不够的补零。

如上,如果需要打印的是数组元素的地址:

for(i = 0 ; i < 5 ; i++)
{    
   printf("%p ",&arr[i]);
}

所以,如果我们上面那个代码是按照“%x”来打印的,那么就会是:

1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101 1010101

而如果是按照“%p”来打印,前面会补充0:

01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101

还有一点要注意的是,memset(void *s, int value ,size_t num);中所赋值(the

specified value (interpreted as an unsigned char))实际范围应该在0~~255,

因为该函数只能取value的后八位赋值给你所输入的范围的每个字节,比如int a[5]

赋值memset(a,-1,sizeof(int )*5)与memset(a,511,sizeof(int )*5) 所赋值的结果是

一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)

而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为

(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其

结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论

value多大只有后八位二进制有效,而后八位二进制的范围在(0~255)中改。而对字

符数组操作时则取后八位赋值给字符数组,其八位值作为ASCII码。

函数原型图片截图来源于网址:https://cplusplus.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值