• C语言进阶——字符串函数&&内存函数


    🌇🌆🌃本文已收录至:C语言——梦想系列_Yohifo的博客-CSDN博客

    更多知识尽在此专栏中!

    目录

    🌇前言

    🌇正文

    🌆字符串函数

    🌉长度不可控的字符串函数

    🌃strlen 长度统计

    🌃strcpy 拷贝

    🌃strcmp 比较

    🌃strcat 追加

    🌉长度可控的字符串操作函数

    🌃strncpy 可控拷贝

    🌃strncmp 可控比较

    🌃strncat 可控追加

    🌉特殊字符串函数

    🌃strstr 寻找

    🌃strtok 分割

    🌃strerror 报错 

    🌆字符分类函数

    🌃isdigit 十进制判断

    🌃isxdigit 十六进制判断

    🌃isupper 大写判断

    🌃islower 小写判断

    🌃toupper 转为大写

    🌃tolower 转为小写

    🌆内存函数

    🌃memcpy 拷贝

    🌃memmove 移动

    🌃memcmp 比较

    🌃memset 设置 

    🌇总结


    🌇前言

    题目描述及其要求

      这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母) 来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!🎉🎉🎉

    此文章分为三部分:字符串函数、 字符分类函数、内存函数。

    通过库函数简化后的代码

    🌇正文

      首先我们从字符串函数开始介绍,顾名思义,字符串函数就是为字符串设计的函数,我们比较熟悉的有字符串长度统计函数 strlen、字符串比较函数 strcmp,除此之外还有很多实用的字符串函数,比如字符串追加、字符串分割、字符串寻找等等,话不多说,让我们直接进入主题:

    🌆字符串函数

    🌉长度不可控的字符串函数

    下面介绍的是对目标字符串操作长度不可控的函数,使用场景相对有限。

    🌃strlen 长度统计

      strlen 是用来统计字符串长度的一个库函数,因为字符串有个重要特征:以'\0'作为结束标志。strlen 正是根据这一特点,从首地址开始,逐个比对统计,得出此字符串的长度。当然如果没有结束标记,strlen 就会一直往后找,得出一个随机值。

    这是 strlen 的标准格式
    strlen 计算字符串长度

     

    使用注意事项:

    • strlen 统计的是 \0 之前出现的字符个数
    • 使用时,必须包含结束标志
    • 返回值是 size_t (unsigned int 无符号整型)

    模拟实现 strlen

      这个函数的工作原理我们已经清楚了,可以试着模仿库函数的方式,写出一个属于自己的 strlen。

      既然是模仿库函数,那么在返回类型、参数类型等方面要和库函数一致,在统计长度前,我们可以使用一个临时指针记录起始位置,当我们的源指针指向结束标志时,循环结束,将两个指针相减,就能得到元素个数(关于指针 - 指针得到元素个数) ,也就是字符串长度。下面来看看具体代码实现吧:

    1. //strlen 计算字符串长度
    2. size_t myStrlen(const char* p)
    3. {
    4. assert(p);//断言,防止空指针
    5. char* tmp = p;//记录起始位置
    6. while (*p)
    7. {
    8. p++;//在循环内指向+1操作,避免位置出错
    9. }
    10. return (size_t)(p - tmp);//指针 - 指针得到元素个数
    11. }
    12. int main()
    13. {
    14. char* pa = "Hello World!";
    15. //size_t len = strlen(pa);
    16. //printf("库函数实现结果:\n%zu\n", len);
    17. size_t len = myStrlen(pa);//此时调用我们写的函数
    18. printf("模拟函数实现结果:\n%zu\n", len);
    19. return 0;
    20. }

      同样的,我们使用之前的示例来验证此函数的可行性 ,可以看到结果与库函数一致。

    🌃strcpy 拷贝

      字符串拷贝需要两个字符串,字符数组 dest(目标) 与字符串 src(源),strcpy 中只需要这两个参数,即把源字符串内容拷贝到目标字符数组中(源字符串中的结束标志也会拷贝),其中要确保字符数组  dest 可改变,数组空间也要足够大,不然装不下源字符串就尴尬了。

    strcpy 标准格式

    使用注意事项:

    • 源字符串中必须包含 \0
    • 源字符串中的 \0 会拷贝到目标字符数组中
    • 目标空间必须足够大,能够装下源字符串
    • 目标空间必须是可修改的 

    模拟实现 strcpy 

      同样的,我们可以对这个函数进行模拟实现,拷贝的本质就是赋值,当源字符串中的首元素拷贝到目标字符数组中后仍然位于首位置,也就是说两个字符串元素拷贝位置是同步的,既然源字符串中的结束标志也要拷贝过去,那么我们就可以将其和赋值写进一个循环判断条件中(这样会构成一段非常奇妙的代码),这样一来我们整个程序的可读性就很不错了。

    1. //strcpy 字符串拷贝
    2. char* myStrcpy(char* dest, const char* src)
    3. {
    4. assert(dest && src);//断言
    5. char* tmp = dest;//记录起始位置
    6. //当*src 为结束标志并赋给 *dest时,整体为假,
    7. //循环终止,目标数组也拿到了结束标志
    8. while (*dest++ = *src++)
    9. {
    10. ;//空语句
    11. }
    12. return tmp;//返回起始地址
    13. }
    14. int main()
    15. {
    16. char arr1[20] = "xxxxxxxxx";
    17. char arr2[] = "Hello!";
    18. //printf("库函数实现结果:\n%s\n", strcpy(arr1, arr2));
    19. printf("模拟函数实现结果:\n%s\n", myStrcpy(arr1, arr2));
    20. return 0;
    21. }

      当然,我们的模拟函数也能实现需求 

    🌃strcmp 比较

      字符串比较函数是从首字符开始比较,通过字符对应的ASCII码值对比,就能知道大小,如果str1>str2,返回1,如果str1因为比较并不需要改变值,所以使用常量字符串也能比较。

    strcmp 标准格式
    strcmp 的返回值

    使用注意事项:

    • 字符串大小比较时,与长度无关
    • 从首字符开始,逐字符比较
    • 通过字符对应的ASCII码值做对比

    模拟实现 strcmp 

      我们可以通过指针的移动来模拟实现这个函数,即指向 str1 的指针 dest、指向 str2 的指针 src,对两个指针解引用后的值进行比较,如果相同就同时向后偏移,直到找到不同的值或移动到结束标志处停止,最后再分情况确定返回值就行了。

    1. //strcmp 字符串比较
    2. int myStrcmp(const char* dest, const char* src)
    3. {
    4. assert(dest && src);//断言
    5. //当找到不同数或移动到\0处时,循环停止
    6. while (*dest == *src && (*dest || *src))
    7. {
    8. dest++;//没有找到不同的元素
    9. src++;//需要向后偏移
    10. }
    11. //分情况判断,确定返回值
    12. if (*dest - *src > 0)
    13. return 1;
    14. else if (*dest - *src < 0)
    15. return -1;
    16. else
    17. return 0;
    18. }
    19. int main()
    20. {
    21. char* str1 = "BATZ";
    22. char* str2 = "BAT";
    23. printf("库函数实现结果:\n%d\n", strcmp(str1, str2));
    24. //printf("模拟实现函数结果:\n%d\n", myStrcmp(str1, str2));
    25. return 0;
    26. }

      使用模拟函数通过测试用例: 

    🌃strcat 追加

      追加,就是在目标字符数组的末尾(\0处)添加源字符串的值,比如目标字符串数组中为abcd,源字符串为1234,经过追加后,字符数组就变为了abcd1234。值得一提的是,strcat 无法自己给自己追加,因为在追加过程中,目标字符数组结束标志会被覆盖掉,导致源字符串(其实就是目标字符,因为是自己给自己追加)中的结束标志也消失了,追加过程会无法停止。

    strcat 标准格式

    使用注意事项:

    • 源字符串和目标字符数组中都必须有\0
    • 目标空间必须足够大
    • 目标空间必须可修改,所以是字符数组 

    模拟实现 strcat

      既然是在目标字符数组的末尾处追加字符,就需要把指向首地址处的指针 dest 移向尾地址,当然在移动前要保存此地址,将此时的尾地址看作首地址2,将源字符串中的元素从此处开始拷贝至目标字符数组中,这样就完成了追加的操作,最后再返回之前记录的首地址就行了。

    1. //strcat 字符串追加
    2. char* myStrcat(char* dest, const char* src)
    3. {
    4. assert(dest && src);//断言
    5. char* tmp = dest;//记录目标字符数组首地址
    6. while (*dest)
    7. {
    8. dest++;//将指针dest移动至尾元素处
    9. }
    10. //类似 strcpy 拷贝操作
    11. while (*src)
    12. {
    13. //判断条件用 *src就行了
    14. *dest++ = *src++;//确保源字符串中的每个元素都能追加上
    15. }
    16. return tmp;//返回首地址
    17. }
    18. int main()
    19. {
    20. char arr1[20] = "ABCD";
    21. char arr2[] = "1234";
    22. //printf("库函数实现结果:\n%s\n", strcat(arr1,arr2));
    23. printf("模拟函数实现结果:\n%s\n", myStrcat(arr1, arr2));
    24. return 0;
    25. }

    🌉长度可控的字符串操作函数

    下面开始介绍字符串操作函数的升级版,这些函数使用起来更加自由方便。

    🌃strncpy 可控拷贝

      相比于前面的 strcpy,strncpy 多了一个字母n,n 的含义是长度可控,即在传递参数时,需要一个额外变量控制拷贝的字节数,相较于前面的固定拷贝,这里的可控拷贝更为灵活,且如果选定的n字节中没有包含结束标志\0,则在拷贝结束后,strncpy 会自动添加一个\0。

    strncpy 标准格式

    使用注意事项:

    • 目标空间必须足够大
    • 目标空间必须可修改
    • 源字符串中不一定要有\0(函数会自己添加)

    模拟实现 strncpy

      这个模拟实现也比较简单,注意两点就行了,循环终止条件标为 k,在循环结束后,还要对 *dest 再一次进行赋值,即 \0 。这样一来就能和 strncpy 一样了。

    1. //strncpy n个字符串拷贝
    2. char* myStrncpy(char* dest, const char* src, size_t k)
    3. {
    4. assert(dest && src);//断言
    5. char* tmp = dest;//记录起始位置
    6. //循环k次,确保拷贝足够的字节数
    7. while (k--)
    8. {
    9. *dest++ = *src++;//类似拷贝的操作
    10. }
    11. *dest = '\0';//再次给目标字符数组中的元素赋值
    12. return tmp;//返回起始地址
    13. }
    14. int main()
    15. {
    16. char arr1[20] = "xxxxxxxx";
    17. char arr2[] = "Hello World!";
    18. //printf("库函数实现结果:\n%s\n", strncpy(arr1, arr2, 5));
    19. printf("模拟函数实现结果:\n%s\n", myStrncpy(arr1, arr2, 5));
    20. return 0;
    21. }

    🌃strncmp 可控比较

      同样的,strncmp 也能控制比较长度,当然控制长度不能超过源字符串的长度,不然是无意义的

    strncmp 标准格式
    返回值与 strcmp 完全一致

    使用注意事项:

    • 与 strcmp 基本一致
    • 控制比较字节数不能为负数 

    模拟实现 strncmp

      这个模拟实现也比较简单,大体思路与 strcmp 的模拟一样,只是循环判断条件变为了 k 和 *dest (当对比到结束标志处,循环也会停止)。

    1. //strncmp n个字符串比较
    2. int myStrncmp(const char* dest, const char* src, size_t k)
    3. {
    4. assert(dest && src);//断言
    5. //为何使用前置--?
    6. //因为这样能有效避免多判断一次的情况
    7. while (--k && *dest == *src)
    8. {
    9. dest++;//确保每位都能对比到
    10. src++;
    11. }
    12. //分情况返回
    13. if (*dest - *src > 0)
    14. return 1;
    15. else if (*dest - *src < 0)
    16. return -1;
    17. else
    18. return 0;
    19. }
    20. int main()
    21. {
    22. char* str1 = "BATZ";
    23. char* str2 = "BAT";
    24. //printf("库函数实现结果:\n%d\n", strncmp(str1, str2, 3));
    25. printf("模拟函数实现结果:\n%d\n", myStrncmp(str1, str2, 3));
    26. return 0;
    27. }

    🌃strncat 可控追加

      可控追加,旨在控制源字符串中字符追加数,比如目标字符数组为abcd,源字符串为1234,我们传递字节数为2,当追加结束后,目标字符数组变为abcd12,同所有可控家族成员一样,strncat 也会自动添加结束标志 \0。因此利用 strncat 自己给自己追加,能够很好的完成任务,避免结束标志吞噬问题。

    strncat 标准格式

    使用注意事项:

    • 目标字符数组中必须有\0
    • 目标空间必须足够大
    • 目标空间必须可修改
    • 源字符串中可以不包含\0

    模拟实现 strncat

      代码大体与模拟 strcat 的一致,只不过有两个地方需要注意:1.循环判断条件 2.最后 \0 的添加

    1. //strncat n个字符串追加
    2. char* myStrncat(char* dest, const char* src, size_t k)
    3. {
    4. assert(dest && src);//断言
    5. char* tmp = dest;//记录起始位置
    6. //使指针移向目标字符数组的末尾处
    7. while (*dest)
    8. {
    9. dest++;
    10. }
    11. //判断条件为k,即传入的控制字节数
    12. while (k--)
    13. {
    14. *dest++ = *src++;//确保每个元素都能追加
    15. }
    16. *dest = '\0';
    17. return tmp;//返回目标字符数组的起始地址
    18. }
    19. int main()
    20. {
    21. char arr1[20] = "xxxxxxx";
    22. char arr2[] = "Hello World!";
    23. //printf("库函数实现结果:\n%s\n", strncat(arr1, arr2, 5));
    24. printf("模拟函数实现结果:\n%s\n", myStrncat(arr1, arr2, 5));
    25. return 0;
    26. }

    🌉特殊字符串函数

    🌃strstr 寻找

      字符串寻找函数,作用是在目标字符串中寻找是否出现过源字符串,如果出现则返回第一次其在目标字符串中第一次出现的地址,如果没有出现,则返回一个空指针。

    ststr 标准格式

    使用注意事项:

    • 只要传入的字符串地址就行了
    • 这个函数没有什么需要特别注意的事项

    模拟实现 strstr

      这个函数实现起来就比较复杂了,需要用到多个指针,不断记录位置、移动位置、刷新位置,当然我们这里模拟实现的是效率比较低的算法,如果想要追求时间,可以参考参考KMP算法, 提高寻找效率。

    1. //strstr 字符串寻找
    2. char* myStrstr(const char* dest, const char* src)
    3. {
    4. assert(dest && src);//断言
    5. //特殊情况之一,如果源字符串为空,则返回目标字符串
    6. if (!*src)
    7. return (char*)dest;//强制类型转换
    8. const char* str1 = dest;//使用替身指针
    9. const char* str2 = src;//避免影响源字符串和目标字符串
    10. char* p = (char*)dest;//记录字符串、判断字符串指针
    11. while (*p)
    12. {
    13. char* s1 = (char*)str1;//比较部分的指针
    14. char* s2 = (char*)str2;
    15. //这是主要判断部分,当两个字符相等,并且不为\0
    16. //才有继续往后走的资格
    17. while (*s1 && *s2 && *s2 == *s1)
    18. {
    19. s1++;//往后移动,逐字比较
    20. s2++;
    21. }
    22. //如果是因为子串的\0结束的,就找到了目标字符串
    23. if (*s2 == '\0')
    24. return p;//直接返回记录指针即可
    25. str1++;//此时没找到,目标字符串指针需要向后移动
    26. p = (char*)str1;//记录指针矫正
    27. }
    28. return NULL;//如果走到这一步,说明没找到,返回空指针
    29. }
    30. int main()
    31. {
    32. char* str1 = "abcdef";
    33. char* str2 = "cdef";
    34. //printf("库函数实现结果:\n%s\n", strstr(str1, str2));
    35. printf("模拟函数实现结果:\n%s\n", myStrstr(str1, str2));
    36. return 0;
    37. }

    🌃strtok 分割

      字符串分割函数,顾名思义就是对字符串进行分割操作,比如字符串abcd&1234,我们把&视为分隔符,再把字符串首地址和分隔符传给 strtok 函数,就能分别得到字符串 abcd 和字符串 1234 的首地址。字符串分割函数有个值得注意的点:如果字符串中有多个分隔符,在第一次分割时传入的是首字符地址,第二次及后续分割需要传递一个空指针,因为 strtok 有记忆功能,当第一次分割结束后,它会记录下此地址,为下次分割做准备,因此需要传递有一个空指针。当然如果想要分割其他字符串,只需传递其他字符串的首地址就行了,此时记忆块会刷新。

    strtok 标准格式

    使用注意事项:

    • 目标字符数组中要包含结束标志
    • 第一次传递的是字符串首地址
    • 如果针对同一个字符串,第二次需要传递一个空指针 
    • 在使用此函数前,一般会创建一个临时变量存储目标数组值,避免分割对目标字符串造成影响

    1. //strtok 字符串分割
    2. int main()
    3. {
    4. char str1[] = "12345678@gmail.com";
    5. char* str2 = "@.";
    6. char buf[100] = { 0 };
    7. strcpy(buf, str1);
    8. char* p;
    9. for (p = strtok(buf, str2); p; )
    10. {
    11. printf("%s\n", p);
    12. p = strtok(NULL, str2);
    13. }
    14. return 0;
    15. }

    🌃strerror 报错 

      这个特殊字符串函数需要配合错误码使用;错误码:是指包含各种错误信息的数字代码,比如数字0表示没有错误,经过博主测试,C语言中有大约141个错误码。如果直接将错误码放入 strerror 中并打印,会出现相应的错误信息;当然,C语言中有一个专门的函数记录错误码,即 errno,需要引出头文件 errno.h,当程序运行出错后,errno 会获取当前的错误码,这样一来,strerror(errno) 搭配就能很好的打印出错误信息。 

    strerror 标准格式
    errno 标准格式

    使用注意事项:

    • strerror 中的参数必须是整型,字符型会按ASCII码处理
    • error 在使用时需要包含头文件 errno.h
    1. //strerror 字符串报错
    2. #include
    3. int main()
    4. {
    5. //打开不存在的文件,使程序报错
    6. FILE* pf = fopen("test.txt", "r");
    7. if (!pf)
    8. {
    9. printf("%s\n", strerror(errno));
    10. return -1;
    11. }
    12. fclose(pf);
    13. pf = NULL;
    14. return 0;
    15. }

    🌆字符分类函数

      这部分的字符串函数相对简单,无非就是通过ASCII码判断,不过前人已经打包成函数了,我们只需要调用 ctype.h 这个头文件就能直接使用。

    🌃isdigit 十进制判断

      如果成立,返回1,否则返回0。

    1. //isdigit 判断十进制
    2. #include
    3. int main()
    4. {
    5. if (isdigit('8'))
    6. printf("字符8是十进制数字");
    7. else
    8. printf("字符8不是十进制数字");
    9. return 0;
    10. }

    🌃isxdigit 十六进制判断

      如果成立,返回1,否则返回0。

    1. //isxdigit 判断十六进制
    2. #include
    3. int main()
    4. {
    5. if (isxdigit('f'))
    6. printf("字符f是十六进制数字");
    7. else
    8. printf("字符f不是十六进制数字");
    9. return 0;
    10. }

    🌃isupper 大写判断

      如果成立,返回1,否则返回0。

    1. //isupper 判断大写
    2. #include
    3. int main()
    4. {
    5. if (isupper('f'))
    6. printf("字符f大写字母");
    7. else
    8. printf("字符f不是大写字母");
    9. return 0;
    10. }

    🌃islower 小写判断

      如果成立,返回1,否则返回0。

    1. //islower 判断小写
    2. #include
    3. int main()
    4. {
    5. if (islower('f'))
    6. printf("字符f是小写字母");
    7. else
    8. printf("字符f不是小写字母");
    9. return 0;
    10. }

    🌃toupper 转为大写

      返回类型为整型,对应ASCII码值

    1. //toupper 小写转大写
    2. #include
    3. int main()
    4. {
    5. char c = 'a';
    6. printf("%c\n", toupper(c));
    7. return 0;
    8. }

    🌃tolower 转为小写

      返回类型为整型,对应ASCII码值

    1. //tolower 大写转小写
    2. #include
    3. int main()
    4. {
    5. char c = 'Z';
    6. printf("%c\n", tolower(c));
    7. return 0;
    8. }

      因为这几个函数都比较简单,所以就没有详细讲解,其实不用这些函数,我们自己也能写出解法,不过会麻烦一些,比如开头提到的那题,下面给大家做了一个汇总表格,让大家看看有哪些现成可用的库函数:

    函数当条件满足时(即所传递参数符合条件时)返回真
    iscntrl任何控制字符
    isspace空白字符,比如空格、换页、换行、回车、制表符等
    isdigit十进制数字 0~9
    isxdigit十六进制数字 0~f (或0~F都行)
    islower小写字母 a~z
    isupper大写字母 A~Z
    isalpha任意字母,即 a~z 或 A~Z
    isalnum字母或数字,即 a~z 、A~Z 或 0~9
    ispunct标点符号,即不属于数字或字母的圆形字符
    isgraph任何图形字符
    isprint任何可打印的字符,包括图形字符和空白字符
    toupper、tolower除ASCII码为0外的任何字符

    🌆内存函数

      内存操作函数比较高端,它们更像是不可控字符串函数的Pro版,因为内存函数的操作对象是所有类型,而字符串函数只是面向字符串设计的,话不多说,让我们一起看看内存函数。

    🌃memcpy 拷贝

      相当于全能版的 strcpy

    memcpy 标准格式

    使用注意事项:

    • 目标空间必须足够大
    • 目标空间必须可修改
    • 传入的字节数需要慎重考虑,这里推荐 sizeof(类型)*想传入的元素数

    模拟实现 memcpy

     跟当时我们模拟实现 strncpy 一样,需要把字节控制数 num 作为判断依据,不过因为这是全能型的拷贝函数,需要强制类型转化为 char* 型再解引用赋值,确保所有数据都能进行拷贝,同样在移动时也需要进行强制类型转化,与 strncpy 不同的是它不会自动补\0,这样就对字符串不太友好了,所以如果是专门处理字符串,还是使用字符串函数比较合适。

     值得一提的是,当我们自己给自己拷贝,并且拷贝空间与目标空间重叠时,自己设计的函数会出问题,会有值被覆盖掉,因为它默认从前往后拷贝(会产生覆盖现象)。但是微软在设计 memcpy 时留了一手,就是库函数 memcpy 也能完成这个任务,原因很简单,他们在设计时考虑到了这个问题,所以在函数执行前加了个判断,以决定是从前往后拷贝,还是从后往前拷贝。其实这个任务应该交给 memmove 完成的,但是 memcpy 的设计者实现了类 memmove 的操作,所以说库函数中的 memcpy 是个满分拷贝。至于如何实现这个功能,下面会介绍到。

    1. //memcpy
    2. void* myMemcpy(void* dest, const void* src, size_t num)
    3. {
    4. assert(dest && src);//断言
    5. void* tmp = dest;//记录
    6. //判断依据同样是传入的控制字节数
    7. while (num--)
    8. {
    9. *(char*)dest = *(char*)src;//需要进行强制类型转化
    10. ((char*)dest)++;//转化后移动,确保步长为1字节
    11. ((char*)src)++;
    12. }
    13. return tmp;//返回目标空间地址
    14. }
    15. int main()
    16. {
    17. int arr1[20] = { 0 };
    18. int arr2[] = { 1,2,3,4,5 };
    19. //memcpy(arr1, arr2,sizeof(int)*5);
    20. myMemcpy(arr1,arr2,sizeof(int)*5);//传入时推荐这种写法
    21. int i = 0;
    22. for (i = 0; i < 5; i++)
    23. {
    24. printf("%d ", arr1[i]);
    25. }
    26. char arr3[20] = "xxxxxxxxx";
    27. char arr4[] = "Hello World";
    28. //printf("%s\n", (char*)memcpy(arr3, arr4, sizeof(char) * 5));//经测试,不会自己添加\0
    29. //printf("%s\n", (char*)myMemcpy(arr4+6, arr4, sizeof(char) * 5));
    30. char arr5[] = "abcdefg123456";
    31. //printf("%s\n", (char*)memcpy(arr5 + 3, arr5, sizeof(char) * 5));//测试自己拷贝自己时
    32. //printf("%s\n", (char*)myMemcpy(arr5 + 3, arr5, sizeof(char) * 5));//出现重叠的情况
    33. return 0;
    34. }

    🌃memmove 移动

      内存移动函数,移动可以看作拷贝,memmove 包含 memcpy,能实现更多操作,可以这样比喻,memcpy 是男人,memmove 是帅气的男人。微软在VS中重写了 memcpy,使得 memcpy 也能实现 memmove 的功能,使其变成了一个满分拷贝。

    memmove 标准格式

    使用注意事项:

    • 目标空间必须足够大
    • 目标空间必须可修改
    • 传入字节数要慎重考虑

    模拟实现 memmove

       memmove 是在 memcpy 的基础进行改进的,两者在设计之初就分工明确:memcpy 实现不重叠的拷贝,memmove 实现的是有重叠的拷贝,VS中的 memcpy 做了升级,也可以实现有重叠的拷贝。其实要实现这一点也不难,只需要在拷贝前判断是需要从前往后还是从后往前进行拷贝就好了,判断依据为目标字符地址与源字符地址的大小。

    1. //memmove
    2. void* myMemmove(void* dest, const void* src, size_t num)
    3. {
    4. assert(dest && src);//断言
    5. void* tmp = dest;//记录
    6. //情况1,目标地址在源地址之前
    7. //此时执行的就是memcpy的操作
    8. //从前往后拷贝就行了
    9. if (dest < src)
    10. {
    11. while (num--)
    12. {
    13. //从前往后赋值
    14. *(char*)dest = *(char*)src;
    15. ((char*)dest)++;
    16. ((char*)src)++;
    17. }
    18. }
    19. //情况2,需要从后往前拷贝
    20. //此时可以把num当作偏移量
    21. else
    22. {
    23. while (num--)
    24. {
    25. //从后往前赋值
    26. *((char*)dest + num) = *((char*)src + num);
    27. }
    28. }
    29. return tmp;
    30. }
    31. int main()
    32. {
    33. int arr1[20] = { 0 };
    34. int arr2[10] = { 6,7,8,9,10 };
    35. memmove(arr1, arr2, sizeof(int) * 5);
    36. //myMemmove(arr1, arr2, sizeof(int) * 5);
    37. int i = 0;
    38. for (i = 0; i < 5; i++)
    39. {
    40. printf("%d ", arr1[i]);
    41. }
    42. char arr3[] = "abcdefg123456";
    43. //printf("%s\n", (char*)memmove(arr3 + 3, arr5, sizeof(char) * 5));//模拟自己拷贝自己且重叠
    44. //printf("%s\n", (char*)myMemmove(arr3 + 3, arr5, sizeof(char) * 5));//同上,测试模拟函数
    45. return 0;
    46. }

      图解从前往后拷贝与从后往前拷贝的区别:

    注:为了简化讲解,使用的是另一个示例

    🌃memcmp 比较

      内存比较函数,有点像 strncmp 的升级版,为了适用于所有数据,我们在接收参数时会使用空指针进行接收,比较时会转化为字符型指针进行解引用比较,确保每位都能对比到,返回值和 strncmp 一样。

    memcmp 标准格式
    memcmp 返回值及其意义

    使用注意事项:

    • 传递参数时,要传地址(指针)
    • 返回参数类型为整型
    • 传入字节数要慎重考虑 

    模拟实现 memcmp

      模拟实现的话也比较简单,抓住两个点就行了,1.判断条件中要包含 num 2.判断时要逐字节进行判断,即强制类型转换为 char* 后解引用。

    1. //memcmp
    2. int myMemcmp(const void* dest, const void* src, size_t num)
    3. {
    4. assert(dest && src);
    5. if (!*(char*)src && *(char*)dest)
    6. return 1;//当源空间为空,且目标空间不为空时
    7. if (!*(char*)src && !*(char*)dest)
    8. return 0;//当源空间与目标空间都为空时,避免无效循环
    9. //类似于 strncmp 的判断条件
    10. while (--num && *(char*)dest == *(char*)src)
    11. {
    12. ((char*)dest)++;//逐字符比较
    13. ((char*)src)++;
    14. }
    15. //分流判断返回
    16. if (*(char*)dest > *(char*)src)
    17. return 1;
    18. else if (*(char*)dest < *(char*)src)
    19. return -1;
    20. else
    21. return 0;
    22. }
    23. int main()
    24. {
    25. char* str1 = "BBAZ";
    26. char* str2 = "BBA";
    27. //printf("%d\n", memcmp(str1, str2, sizeof(char) * 2));
    28. printf("%d\n", myMemcmp(str1, str2, sizeof(char) * 2));
    29. return 0;
    30. }

    🌃memset 设置 

      内存设置函数,顾名思义就是对数据在内存中的存储值做修改,memset 可用于某些数据的初始化,当然内存设置这个函数也适用于所有类型的数据,因为这个函数实现起来也比较简单,无非就是逐字节进行修改,类似于 memcpy 吧,不过源字符串为传入的固定值,因此这个函数我们就进行模拟实现了。

    memset 标准格式

    使用注意事项:

    • 参数1要为指针,如果不是指针类型,就传入地址
    • 参数2为整型,代表在内存中设置后的具体值
    • 参数3需要慎重考虑,不能超过原数据的大小 


    🌇总结

      本次字符串函数&&内存函数的学习就到此为止了,回顾全文,我们从字符串类型操作开始,延伸到全数据类型的操作,中间还介绍了几个字符分类函数,这些库函数就像一件件实用的工具,是前人智慧的结晶,所以我们不仅要会用,还要用好,并将它们运用到代码中。知其然,并知其所以然,对这些库函数的模拟实现能让我们更为全面的了解实现原理,并避免犯错。 🎊🎊🎊

     如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

     如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正。

    相关文章推荐

    C语言进阶——数据在内存中的存储_Yohifo的博客-CSDN博客 

    C语言初阶——实用调试技巧_Yohifo的博客-CSDN博客

    【算法】KMP算法——解决字符串匹配问题_榶曲的博客-CSDN博客

  • 相关阅读:
    LuatOS-SOC接口文档(air780E)--pack - 打包和解包格式串
    React18 渲染列表与添加key值
    js函数参数
    Docker部署Springboot项目
    Science adv | 转录因子SPIC连接胚胎干细胞中的细胞代谢与表观调控
    从ContinuousEventTimeTrigger/ContinuousProcessingTimeTrigger代码看如何实现一个自定义的触发器
    新质生产力资讯简报合集2024年(170篇)
    系统报错“由于找不到msvcp140.dll无法继续执行代码”的处理方法
    java学习(Arrays类和System类)
    自学5个月软件测试找到一个8k的工作,我的学习方式值得你借鉴
  • 原文地址:https://blog.csdn.net/weixin_61437787/article/details/126857678