news 2026/1/23 6:27:19

【C语言深度解析】手把手实现字符串函数与内存函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C语言深度解析】手把手实现字符串函数与内存函数

在C语言中,字符串函数和内存函数是使用频率极高的基础工具。C标准库(<string.h>)提供了strlenstrcpymemcpy等一系列函数,但很多初学者仅停留在“会用”的层面,对其底层实现逻辑一知半解。
手动实现这些函数不仅能帮助我们深入理解C语言的指针操作、内存布局等核心知识点,还能应对校招面试、笔试中的高频考点。本文将手把手拆解字符串函数与内存函数的实现思路,逐行解析代码细节,并提供完整可运行的代码。

1. 字符串函数实现与核心解析

字符串函数的操作对象是'\0'结尾的字符数组,实现过程中需重点关注'\0'的处理和空指针防护。

1.1 字符串长度计算:my_strlen(三种实现方案)

strlen的核心功能是计算字符串中有效字符的个数(不包含结束符'\0'),以下提供三种经典实现方式,各有优劣。

实现1:计数器法(最直观)
#include<assert.h>// 用于assert断言#include<stddef.h>// 用于size_t类型// 计数器法实现strlensize_tmy_strlen1(constchar*str){assert(str!=NULL);// 空指针检查,避免程序崩溃size_tcnt=0;// 计数器,size_t是无符号整数类型(适配长度计算)while(*str!='\0')// 遍历到'\0'停止{cnt++;// 计数器自增str++;// 指针后移,访问下一个字符}returncnt;}

核心解析

  • assert(str != NULL):防护空指针传入,若str为NULL,程序会直接终止并提示错误,便于调试。
  • size_t:无符号整数类型(对应unsigned int),因为字符串长度不可能为负数,比int更贴合场景。
  • 循环逻辑:逐字符遍历,直到遇到'\0',计数器累加得到字符串长度。
实现2:递归法(无额外变量)
// 递归法实现strlensize_tmy_strlen2(constchar*str){assert(str!=NULL);if(*str=='\0')return0;// 递归终止条件:遇到'\0'返回0returnmy_strlen2(str+1)+1;// 递归调用,指针后移,返回值累加1}

核心解析

  • 递归思想:把“求字符串长度”拆解为“当前字符(1个) + 剩余字符串长度”。
  • 优势:无需额外定义计数器变量;劣势:字符串过长时可能导致栈溢出(递归深度过大)。
实现3:指针相减法(最高效)
// 指针相减法实现strlensize_tmy_strlen3(constchar*str){assert(str!=NULL);constchar*p=str;// 保存字符串起始地址while(*p!='\0')// 遍历到'\0'停止,此时p指向'\0'p++;returnp-str;// 指针相减:得到两个指针之间的元素个数(即字符串长度)}

核心解析

  • 指针特性:同类型指针相减,结果为两个指针之间的元素个数(不是字节数)。
  • 优势:无需计数器,无需递归调用,执行效率最高,是工业级实现的首选。

1.2 字符串拷贝:my_strcpy

strcpy的核心功能是将源字符串(src,包含'\0')完整拷贝到目标字符串(dest)中,返回目标字符串的起始地址。

char*my_strcpy(char*dest,constchar*src){assert(dest!=NULL&&src!=NULL);// 双空指针检查char*ret=dest;// 保存目标字符串起始地址,用于最终返回// 核心逻辑:逐字符拷贝,包括'\0'while(*dest++=*src++){}returnret;}

核心解析

  • const char* srcsrc是源字符串,加上const限制,防止函数内部修改源字符串,提高代码安全性。
  • char* ret = dest:因为dest指针会在循环中后移,需要先保存其起始地址,最终返回该地址(支持链式调用,如strlen(my_strcpy(dest, src)))。
  • 核心循环while(*dest++ = *src++) {}
    1. 赋值操作*dest = *src:将src指向的字符拷贝到dest指向的空间。
    2. 自增操作dest++src++:两个指针同时后移,准备拷贝下一个字符。
    3. 循环终止:当拷贝到src'\0'时,*dest = '\0',赋值表达式的结果为'\0'(ASCII码0),循环条件为假,循环结束。
  • 注意:目标字符串dest必须有足够的内存空间,否则会导致缓冲区溢出。

1.3 字符串比较:my_strcmp

strcmp的核心功能是按ASCII码值逐字符比较两个字符串,返回值规则如下:

  • 返回值 > 0:str1>str2
  • 返回值 < 0:str1<str2
  • 返回值 = 0:str1==str2
intmy_strcmp(constchar*str1,constchar*str2){assert(str1!=NULL&&str2!=NULL);// 循环条件:两个字符都不为'\0' 且 两个字符相等while(*str1!='\0'&&*str2!='\0'&&*str1==*str2){str1++;str2++;}return*str1-*str2;// 返回最终不相等字符的ASCII码差值}

核心解析

  • 循环逻辑:只在“两个字符都有效(非’\0’)且相等”时继续遍历,一旦不满足条件,立即退出循环。
  • 返回值:直接返回最终两个字符的ASCII码差值,天然满足strcmp的返回值规则(无需额外判断正负)。
  • 区别于字符串长度比较:strcmp不是比较字符串长度,而是比较字符的ASCII码值(如"abc" < “abd”,因为’c’ < ‘d’)。

1.4 字符串拼接:my_strcat

strcat的核心功能是将源字符串(src)拼接到目标字符串(dest)的末尾(覆盖dest原有的'\0',最终拼接后的字符串以'\0'结尾)。

char*my_strcat(char*dest,constchar*src){assert(dest!=NULL&&src!=NULL);char*ret=dest;// 保存目标字符串起始地址// 第一步:找到dest的'\0'位置while(*dest!='\0')dest++;// 第二步:从dest的'\0'位置开始,拷贝src(同strcpy逻辑)while(*dest++=*src++){}returnret;}

核心解析

  • 分两步执行:先定位dest的结束符'\0',再执行字符串拷贝(逻辑同my_strcpy)。
  • 注意:dest必须有足够的剩余空间,以容纳src的所有字符,否则会导致缓冲区溢出。

1.5 子串查找:my_strstr

my_strstr的核心功能是在主字符串(str1)中查找子字符串(str2)第一次出现的位置,返回该位置的指针;若未找到,返回NULL;若str2为空字符串,直接返回str1

char*my_strstr(constchar*str1,constchar*str2){assert(str1!=NULL&&str2!=NULL);constchar*mark=str1;// 标记str1的当前查找起始位置if(*str2=='\0')return(char*)str1;// 特殊情况:str2为空,返回str1while(*mark)// 遍历str1,直到mark指向'\0'{constchar*p1=mark;// p1:遍历str1的当前指针constchar*p2=str2;// p2:遍历str2的当前指针// 逐字符匹配str1和str2while(*p1==*p2&&*p1!='\0'&&*p2!='\0'){p1++;p2++;}// 匹配成功:p2指向str2的'\0'if(*p2=='\0')return(char*)mark;// 匹配失败:mark后移,重新开始下一轮匹配mark++;}returnNULL;// 未找到子串,返回NULL}

核心解析

  • mark指针:标记每次匹配的起始位置,若本轮匹配失败,mark后移一位,重新匹配。
  • p1p2指针:用于逐字符匹配当前轮次的str1str2,匹配成功则同时后移。
  • 匹配成功条件:p2指向str2'\0'(说明str2的所有字符都已匹配完成)。
  • 特殊处理:str2为空字符串时,直接返回str1(遵循C标准库strstr的行为)。

2. 内存函数实现与核心解析

与字符串函数不同,内存函数的操作对象是任意类型的内存块(不仅限于字符),通过void*指针实现通用性,按字节进行操作。

2.1 内存设置:my_memset

memset的核心功能是将指定内存块(ptr)的前n个字节,全部设置为指定值c(最终以unsigned char类型存储)。

void*my_memset(void*ptr,intc,size_tn){assert(ptr!=NULL);void*ret=ptr;// 保存内存块起始地址char*p=(char*)ptr;// 强转为char*,按字节操作while(n--)// 循环n次,设置n个字节{*p=(unsignedchar)c;p++;}returnret;}

核心解析

  • void* ptr:通用指针类型,可接收任意类型的内存地址(如int*char*等)。
  • char* p = (char*)ptr:将void*强转为char*,因为char类型占1个字节,便于逐字节设置内存。
  • int c:参数cint类型,但最终会转为unsigned char存储,确保取值范围在0~255之间(符合字节存储规则)。
  • 注意:memset按字节设置内存,若操作非字符类型(如int数组),需注意效果(如memset(arr, 1, 4)是将每个字节设为1,而非每个int元素设为1)。

2.2 内存拷贝:my_memcpy

memcpy的核心功能是将源内存块(src)的前n个字节,拷贝到目标内存块(dest)中,返回目标内存块的起始地址。

void*my_memcpy(void*dest,constvoid*src,size_tn){assert(dest!=NULL&&src!=NULL);void*ret=dest;char*d=(char*)dest;// 目标内存指针(按字节操作)constchar*s=(constchar*)src;// 源内存指针(按字节操作)while(n--)// 循环n次,拷贝n个字节{*d++=*s++;}returnret;}

核心解析

  • 通用性:通过void*接收任意类型的内存地址,char*实现逐字节拷贝,支持任意类型数据的内存拷贝。
  • 局限性:my_memcpy不处理重叠内存的情况(即destsrc的内存区域有重叠时,拷贝结果可能出错)。
  • strcpy的区别:memcpy不关心'\0',仅按字节数n拷贝;strcpy只拷贝字符串,遇到'\0'停止。

2.3 重叠内存移动:my_memmove

memmovememcpy的增强版,核心优势是支持重叠内存的拷贝,保证拷贝结果的正确性。

void*my_memmove(void*dest,constvoid*src,size_tn){assert(dest!=NULL&&src!=NULL);void*ret=dest;char*d=(char*)dest;constchar*s=(constchar*)src;// 情况1:dest在src前面(无重叠/前重叠),从前向后拷贝if(dest<src){while(n--)*d++=*s++;}// 情况2:dest在src后面(后重叠),从后向前拷贝else{while(n--){*(d+n)=*(s+n);}}returnret;}

核心解析

  • 重叠内存判断:通过比较destsrc的地址大小,分两种情况处理:
    1. dest < src:内存无重叠或前重叠,从前向后拷贝(同memcpy逻辑),不会覆盖未拷贝的源数据。
    2. dest > src:内存后重叠,从后向前拷贝(从第n-1个字节开始,倒序拷贝到第0个字节),避免覆盖未拷贝的源数据。
  • 为什么memcpy不支持重叠拷贝?因为memcpy始终从前向后拷贝,当destsrc后面且重叠时,前面的源数据会被先拷贝的目标数据覆盖,导致拷贝结果错误。

2.4 内存比较:my_memcmp

memcmp的核心功能是按字节比较两个内存块(ptr1ptr2)的前n个字节,返回值规则与strcmp一致。

intmy_memcmp(constvoid*ptr1,constvoid*ptr2,size_tn){assert(ptr1!=NULL&&ptr2!=NULL);constchar*p1=(constchar*)ptr1;constchar*p2=(constchar*)ptr2;while(n--){if(*p1!=*p2)return*p1-*p2;// 找到不相等字节,返回差值p1++;p2++;}return0;// 所有字节都相等,返回0}

核心解析

  • 逐字节比较:强转为char*,按字节遍历两个内存块,一旦发现不相等的字节,立即返回差值。
  • strcmp的区别:memcmp不关心'\0',会严格比较n个字节;strcmp遇到'\0'就停止比较,仅适用于字符串。
  • 通用性:支持任意类型内存块的比较(如int数组、struct结构体等)。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/20 17:44:07

TimesFM 2.5性能调优实战:从模型推理到生产部署的全链路优化

TimesFM 2.5性能调优实战&#xff1a;从模型推理到生产部署的全链路优化 【免费下载链接】timesfm TimesFM (Time Series Foundation Model) is a pretrained time-series foundation model developed by Google Research for time-series forecasting. 项目地址: https://gi…

作者头像 李华
网站建设 2026/1/23 4:10:00

专业m4s转换工具:永久保存B站珍贵视频的完整方案

专业m4s转换工具&#xff1a;永久保存B站珍贵视频的完整方案 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 还在为B站视频突然消失而担忧吗&#xff1f;那些精心收藏的缓存m4…

作者头像 李华
网站建设 2026/1/22 23:45:32

如何快速掌握YimMenu:GTA5游戏增强工具的完整实战指南

如何快速掌握YimMenu&#xff1a;GTA5游戏增强工具的完整实战指南 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimM…

作者头像 李华
网站建设 2026/1/22 6:42:24

PyTorch-CUDA-v2.9镜像 + GitHub Actions 实现CI/CD自动化

PyTorch-CUDA-v2.9镜像 GitHub Actions 实现CI/CD自动化 在深度学习项目日益复杂的今天&#xff0c;一个常见的痛点是&#xff1a;代码在本地跑得好好的&#xff0c;一上 CI 就报错——不是依赖缺失&#xff0c;就是 GPU 不可用。更糟的是&#xff0c;很多团队的持续集成流程只…

作者头像 李华
网站建设 2026/1/19 4:27:34

跨平台漫画阅读解决方案:构建无缝的数字阅读体验

跨平台漫画阅读解决方案&#xff1a;构建无缝的数字阅读体验 【免费下载链接】venera A comic app 项目地址: https://gitcode.com/gh_mirrors/ve/venera 在数字内容消费日益多元化的今天&#xff0c;漫画爱好者面临着一个普遍痛点&#xff1a;阅读体验的碎片化。不同设…

作者头像 李华
网站建设 2026/1/22 2:30:44

PyTorch-CUDA-v2.9镜像如何对接外部数据库?

PyTorch-CUDA-v2.9 镜像如何对接外部数据库&#xff1f; 在现代 AI 工程实践中&#xff0c;一个常见的挑战是&#xff1a;如何让运行在 GPU 容器中的深度学习模型&#xff0c;高效、安全地读取业务系统中存储的结构化数据&#xff1f; 设想这样一个场景&#xff1a;你正在训练…

作者头像 李华