news 2026/4/18 7:57:28

【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/4/18 6:49:27

AUTOSAR环境下可重用软件组件设计实践案例

从“重复造轮子”到“一次开发&#xff0c;处处运行”&#xff1a;AUTOSAR下的软件复用实战你有没有遇到过这样的场景&#xff1f;一个团队在A项目中花了三个月写完空调压缩机控制逻辑&#xff0c;结果B项目来了&#xff0c;功能几乎一样——只是电机功率大了10%&#xff0c;传…

作者头像 李华
网站建设 2026/4/17 13:48:01

Windows 11系统精简优化:Tiny11Builder完全使用指南

Windows 11系统精简优化&#xff1a;Tiny11Builder完全使用指南 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 还在为Windows 11系统资源占用过高而困扰吗&#…

作者头像 李华
网站建设 2026/4/11 23:15:11

5分钟学会网页转Markdown神器:告别复制粘贴的终极指南

5分钟学会网页转Markdown神器&#xff1a;告别复制粘贴的终极指南 【免费下载链接】markdownload A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file. 项目地址: https://gitcode.com/gh_mirrors/ma/markdownload …

作者头像 李华
网站建设 2026/4/16 12:40:27

百度网盘Mac加速插件:提升下载体验的技术方案

还在为百度网盘Mac版的下载速度而烦恼吗&#xff1f;作为Mac用户&#xff0c;你一定经历过下载大文件时速度较慢的情况。BaiduNetdiskPlugin-macOS开源插件就是专门针对百度网盘Mac客户端的下载优化工具&#xff0c;能有效改善下载体验&#xff0c;让你的下载速度得到提升。这款…

作者头像 李华
网站建设 2026/4/18 5:40:08

PyTorch-CUDA-v2.9镜像是否支持Jupyter Lab?可自行安装扩展

PyTorch-CUDA-v2.9 镜像是否支持 Jupyter Lab&#xff1f;可自行安装扩展 在现代深度学习开发中&#xff0c;一个稳定、高效且开箱即用的环境几乎决定了项目能否快速启动。尤其是当团队成员分布在不同操作系统和硬件配置下时&#xff0c;“在我机器上能跑”这种经典问题频繁出现…

作者头像 李华
网站建设 2026/4/18 4:16:15

Photoshop图层批量导出终极指南:高效工作流必备神器

Photoshop图层批量导出终极指南&#xff1a;高效工作流必备神器 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. 项目地址: https…

作者头像 李华