• 函数扩展之——内存函数


    前言:小伙伴们又见面啦。

    本篇文章,我们将讲解C语言中比较重要且常用的内存函数,并尝试模拟实现它们的功能

    让我们一起来学习叭。


    目录

    一.什么是内存函数

    二.内存函数有哪些

    1.memcpy

    (1)库函数memcpy

    (2)模拟实现memcpy

    2.memmove

    (1)库函数memmove

    (2)模拟实现memmove

    3.memset

    4.memcmp

    四.总结


    一.什么是内存函数

    我们从这个名字不难看出,这将会是一个函数,还是一个对内存进行操作的函数。

    而事实上,这其实也是一种对数据进行操作的函数

    我们之前学习过:strcmp、strcpy、strlen等等,它们都是对字符进行操作的函数,统称为字符串函数。但是这些字符串函数却有着弊端,那就是它们仅仅只能操作字符串,而像整型这些其他类型的数据却无法操作。

    因此我们引出了内存函数,帮助我们对各种各样类型的数据进行操作。


    二.内存函数有哪些

    • memcpy
    • memmove             
    • memset
    • memcmp

    和字符串操作函数类似,内存函数也是由内存的英文"memory",和后边的操作方式的英文拼接而成,同时使用它们都需要头文件#include

    下面我们来逐个讲解这四个内存函数的具体用法。


    1.memcpy

    和strcpy类似,memcpy也是数据拷贝,将一个数组里的数据拷贝到另一个数组中去。

    先来认识一下memcpy的函数头:

     这个函数有三个参数:

    destination        代表着目的地

    source        代表着源头

    num        约束着我们要操作的数据数目

    有没有小伙伴们知道,为什么数组指针参数的返回值以及函数的返回值都要是void*类型呢???

    我们已经知道,memcpy是用来拷贝各种各样的数据类型的函数,那么它的参数就不能是单一的char*或者int*,而void*则充当一个中转站,他可以接收任意类型的数据也可以在函数内部通过强制类型转换成各种各样的数据类型

    size_t是无符号整型,因为我们拷贝字符串不可能说拷贝负数个或者小数个,所以用它来接收。

    而我们的源头source我们是不希望它有任何变化或者被修改的,所以要用一个const来修饰。

    事实上我们这篇文章讲到的所有内存函数的参数都是如此。


    (1)库函数memcpy

    下面我们来看一下memory的具体用法:

    1. #include
    2. #include
    3. int main()
    4. {
    5. int arr1[10] = { 0 };
    6. int arr2[5] = { 1,2,3,4,5 };
    7. memcpy(arr1, arr2, 20);
    8. for (int i = 0; i < 10; i++)
    9. {
    10. printf("%d ", arr1[i]);
    11. }
    12. return 0;
    13. }

    来看这样一个简单的代码及其结果,我们用memcpy成功的实现了整型数据的拷贝。

    这时候有小伙伴们会说:你这个代码不对吧,你memcpy里的数据数目为啥是20啊???

    事实上,memcpy的数据数目代表的是字节数而5个整型的字节数刚好是20

    那为什么是字节数而不是元素的个数呢???

    下面我们就通过模拟实现一个memcpy函数来看看它的具体内部构造:

    (2)模拟实现memcpy

    自主实现memcpy,我们完全可以使用它本身的函数头,也就是:

    void* My_memcpy(void* destination, const void* source, size_t num)

    下面我们来分析怎么构造函数体:

    我们知道,任何类型的数据都有它的大小,字节数基本都不相同,但是它们都有最小的单位,那就是一个字节的char类型

    因此,我们将形参强制类型转换为char*类型一个字节一个字节的拷贝,是不是就能实现啦。

    这时候我们就悟出来了为什么数据数目是字节数了。

    下面来看具体函数体实现:

    1. #include
    2. #include
    3. void* My_memcpy(void* destination, const void* source, size_t num)
    4. {
    5. char* p = destination;
    6. while(num--)
    7. {
    8. *(char*)destination = *(char*)source;
    9. destination = (char*)destination + 1;
    10. source = (char*)source + 1;
    11. }
    12. return p;
    13. }
    14. int main()
    15. {
    16. int arr1[10] = { 0 };
    17. int arr2[5] = { 1,2,3,4,5 };
    18. My_memcpy(arr1, arr2, 20);
    19. for (int i = 0; i < 10; i++)
    20. {
    21. printf("%d ", arr1[i]);
    22. }
    23. return 0;
    24. }

    按我们前边分析的,将destination和source都转化为char*指针,再用while循环实现一个字节一个字节的拷贝,这样我们便能够实现对一组整型数据的拷贝了。来看结果:


    这时候请小伙伴们思考一个问题,我们上边实现的是将一个数组的数据拷贝到另一组数组中

    那我们能不能在一个数组的内部进行拷贝呢???

    比如说我现在有一个数组:

        int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

    我现在要将1,2,3,4,5拷贝到3,4,5,6,7的位置去得到1,2,1,2,3,4,5,8,9,10能否实现???

    事实上是不行的:

    当我们第一步把1拷贝到3的位置时,3的值已经被替换成1整个数据变成了1,2,1,4,5,6,7,8,9,10

    会导致我们得到1,2,1,2,1,2,1,8,9,10 这样一个结果。

    这时候我们的memcpy就无法实现我们想要的结果了,于是我们引出了memmove函数

    (这里要补充一点,不同的编译器memcpy的功能不完全相同,比如博主所使用的VS2019的memcpy就可以实现同一个数组元素的拷贝,有的却不行)


    2.memmove

    memmove的函数头和memcpy一模一样,只是函数体有些许不同。

     下面我们先来看作为库函数的memmove的使用

    (1)库函数memmove

    1. #include
    2. #include
    3. int main()
    4. {
    5. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    6. memmove(arr + 2, arr, 20);
    7. for (int i = 0; i < 10; i++)
    8. {
    9. printf("%d ", arr[i]);
    10. }
    11. return 0;
    12. }

    值得注意的一点是,我们知道数组名在一般情况下代表的是数组的首元素地址,因此我们向函数传递时,分别传递我们要进行操作的两个数列的首元素的地址便可。

    例如我们上述就是将数组的1-5位拷贝到3-7位,所以就将第一位和第三位的地址作为参数传过去。

    得到结果为:


     那到底怎么才能实现同一个数组内数据的相互拷贝呢???

     先来分析一下,既然我们从前往后会造成值被修改,那我们从后往前可不可以呢???

     例如我们还是将1,2,3,4,5拷贝到3,4,5,6,7上去,先将5拷贝到7,再将4拷贝到6,这样下去,确实能够完成。

     下面我们来模拟实现一下memmove的具体内部构造。


    (2)模拟实现memmove

    1. #include
    2. #include
    3. void* My_memmove(void* destination, const void* source, size_t num)
    4. {
    5. char* p = destination;
    6. while (num--)
    7. {
    8. *((char*)destination + num) = *((char*)source + num);
    9. }
    10. return p;
    11. }
    12. int main()
    13. {
    14. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    15. My_memmove(arr + 2, arr, 20);
    16. for (int i = 0; i < 10; i++)
    17. {
    18. printf("%d ", arr[i]);
    19. }
    20. return 0;
    21. }

    要注意的是,既然是从后往前一个字节一个字节的拷贝,那么我的destination和source指针都需要指向末位,所以在解引用之前要先加上num

    这样我们便实现了数组内部的拷贝。

    但是这时候问题又来了如果我想将3,4,5,6,7拷贝到1,2,3,4,5上去,从后往前还管用吗???

    显然,又出现被覆盖的情况,看来,我们上边的代码还有缺陷没有完全实现memmove的功能

    那该怎么解决呢???

    这时候我们思考一下,将前边的数据拷贝到后边要从后往前相对的将后边的数据拷贝到前边是不是要从前往后,那我们将这两者整合在一起不就好了。

    只需要一个判断条件判断是将前边的数据拷贝到后边还是将后边的数据拷贝到前边不就行啦。

    现在我们只需要解决一个问题,怎么判断呢???

    其实这个很简单,如果是将前边的数据拷贝到后边,那么源头的地址就会比目的地小,反之,将后边的数据拷贝到前边,那么源头的地址就会比目的地小。

     这时候我们只需要比较一下源头和目的地的地址就好啦。

    来看具体实现:

    1. #include
    2. #include
    3. void* My_memmove(void* destination, const void* source, size_t num)
    4. {
    5. char* p = destination;
    6. if (destination < source)
    7. {
    8. while (num--)
    9. {
    10. *(char*)destination = *(char*)source;
    11. destination = (char*)destination + 1;
    12. source = (char*)source + 1;
    13. }
    14. }
    15. else
    16. {
    17. while (num--)
    18. {
    19. *((char*)destination + num) = *((char*)source + num);
    20. }
    21. }
    22. return p;
    23. }
    24. int main()
    25. {
    26. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    27. My_memmove(arr, arr + 2, 20);
    28. for (int i = 0; i < 10; i++)
    29. {
    30. printf("%d ", arr[i]);
    31. }
    32. return 0;
    33. }

    这样我们便实现memmove的完整功能啦。 


    看到这里,小伙伴们是不是都感觉非常的累,其实博主我讲到这里也是非常的累。

    事实上对于上边的两个函数,我们更需要掌握他们的内部构造,而接下来要讲的剩下的两个函数就比较简单了,我们只需要知道怎么用它们就可以啦。


    3.memset

    这个函数叫做内存设置函数,其作用是修改内存中的若干个数据

     ptr        是我们要修改的数据起始位置

    value        是我们要改为的数据

    num        是我们要修改的数据数量

    来看实操:

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr[] = "hello world";
    6. memset(arr, '*', 5);
    7. printf("%s", arr);
    8. return 0;
    9. }

    我们要将"hello"五个字符全改为"*",便将数组首地址,"*",5作为参数传入,得到结果如下:

     值得注意的是,这个函数也是只能一个字节一个字节的操作

    1. #include
    2. #include
    3. int main()
    4. {
    5. int arr[5] = {0};
    6. memset(arr, 1, 20);
    7. return 0;
    8. }

     假如我要把这个整型数组的五个元素都改为1,一个字节一个字节的改要改20个,所以我们传入20,但是我们来看结果和内存:

    这并不是我们想要的结果,一个字节8个bite位,所以我们实际上得到的是二进制序列

    00000001 00000001 00000001 00000001,也就是十进制的16843009

    所以这个函数可以说是只能跟char型的数据挂钩啦,不要轻易用在其他类型哦。


    4.memcmp

    既然是cmp结尾,肯定也是一个比较函数,是一个内存比较函数。

     但是这个比较函数有点特殊,它可以让你指定要比较的位置和数量,可以说是strcmp pro max。

    这里值得注意的是,这个函数的返回值类型是int当前者 > 后者时,返回 1,反之返回 -1,相等则返回0

    来看具体使用:

    1. #include
    2. #include
    3. int main()
    4. {
    5. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    6. int arr2[] = { 1,2,3,4,5 };
    7. int ret = memcmp(arr1 + 1, arr2, 20);
    8. printf("%d", ret);
    9. return 0;
    10. }

     (arr1 + 1)便跑到了2的位置,2 > 1,自然会返回1。

    使用这个函数值得注意的一点是,它只会比较第一组遇见的不相同的数据而不会比较整体的数据和的大小

    1. #include
    2. #include
    3. int main()
    4. {
    5. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    6. int arr2[] = { 1,2,4,1,1 };
    7. int ret = memcmp(arr1, arr2, 20);
    8. printf("%d", ret);
    9. return 0;
    10. }

    例如上述代码,1 + 2 + 3 + 4 + 5 明显大于1 + 2 + 4 + 1 + 1但是只因为3 < 4就返回了 - 1。 

     

    四.总结

    终于终于,内存函数的讲解到这里就结束啦!!!

    感谢各位小伙伴能够耐心的看到最后,希望博主的讲解能够帮助到你们。

    本篇创作实属不易,不要忘记支持努力的博主呀,记得一键三连哦!!!

    我们下期再见啦!!!

  • 相关阅读:
    RISC-V 基础指令汇总
    MySQL 8.0 新特性之不可见主键
    智汀家庭云究竟有什么魔力?实现跨品牌联动
    常见的4种Bug 出现原因和解决方案
    最全解决docker配置kibana报错 Kibana server is not ready yet
    云积天赫AI全域营销系统,为品牌营销注入新活力
    【计算商品总价~python+】
    @Autowired与@Resource区别
    Django笔记二十四之数据库函数之比较和转换函数
    Unreal 各类指针是怎么回事
  • 原文地址:https://blog.csdn.net/2303_78442132/article/details/133064581