首页/新闻资讯/正文详情

C 语言字符串与内存函数:从字符处理到字节级操作

发布时间:2026/9/26 4:48:36 来源:云帆数科 栏目:资讯中心
C 语言字符串与内存函数:从字符处理到字节级操作
C 语言字符串与内存函数从字符处理到字节级操作C 语言没有独立的字符串类型。我们平时写下的字符串要么是存放在只读区域中的字符串字面量要么是以字符数组形式保存在内存中的一段连续空间。字符串函数和内存函数的本质都是按照某种约定读取、复制、比较或修改这段空间。理解这些函数不能只记住函数名和参数顺序更要明确三个问题数据是否以 \0 作为结束标志。目标空间是否足够大、是否允许修改。操作的对象是字符串还是任意字节序列。前两个问题决定字符串函数是否安全第三个问题决定应该使用字符串函数还是内存函数。一、字符串在 C 语言中的表示字符串字面量可以直接写在程序中constchar*messagehello;这里的指针保存字符串首字符的地址字符串内容通常位于不可修改的存储区域。需要修改内容时应使用字符数组charmessage[]hello;message[0]H;字符数组中的字符串必须以 ‘\0’ 结尾。对字符串来说\0 不是普通的可见字符而是表示内容结束的标记charword[]{c,o,d,e,\0};上面的数组有 5 个元素但字符串长度是 4。字符串函数通常从首地址开始逐个读取字符直到遇到 ‘\0’。如果字符数组没有结尾的 ‘\0’就不能把它当作 C 字符串传给 strlen、strcpy 或 printf(“%s”, …)。函数可能越过数组边界继续读取造成未定义行为。二、获取字符串长度strlenstrlen 用于计算字符串中 ‘\0’ 之前的字符数量不包含结束标志#includestdio.h#includestring.hintmain(void){constchar*texthello;printf(%zu\n,strlen(text));return0;}函数原型是size_tstrlen(constchar*str);参数必须指向以 ‘\0’ 结尾的有效字符串。返回值类型是 size_t它是无符号整数类型因此进行减法或比较时要特别小心size_tastrlen(abc);size_tbstrlen(abcdef);if(a-b0){/* 这里不一定代表 a 比 b 长 */}因为无符号数不能表示负数a - b 可能发生回绕。比较两个字符串的长度时可以直接写 a b不要通过两个 size_t 相减后判断正负。手动实现 strlen计数器版本#includestddef.hsize_tmy_strlen(constchar*str){size_tcount0;while(str[count]!\0){count;}returncount;}指针相减版本#includestddef.hsize_tmy_strlen(constchar*str){constchar*endstr;while(*end!\0){end;}return(size_t)(end-str);}递归版本也可以表达同样的逻辑但字符串很长时可能产生较深的调用栈因此实际工程中通常使用循环版本。三、复制字符串strcpystrcpy 从源字符串复制字符到目标数组并且会把源字符串结尾的 ‘\0’ 一起复制char*strcpy(char*destination,constchar*source);使用时必须满足以下条件source 必须指向以 ‘\0’ 结尾的字符串。destination 必须有足够空间保存源字符串和结尾的 ‘\0’。destination 必须是可写空间。源区域和目标区域不能重叠。示例#includestdio.h#includestring.hintmain(void){chardestination[32];constchar*sourceC language;strcpy(destination,source);puts(destination);return0;}目标数组的容量不能只看字符串的可见字符数量还要为 ‘\0’ 预留一个字节。对于外部输入直接使用 strcpy 很容易造成缓冲区溢出应该先检查长度或使用更明确的长度限制策略。手动实现 strcpy#includeassert.hchar*my_strcpy(char*destination,constchar*source){char*resultdestination;assert(destination!NULL);assert(source!NULL);while((*destination*source)!\0){/* copy including the terminator */}returnresult;}返回目标地址是标准库函数的一个常见设计这样可以把复制函数放进表达式中使用。四、拼接字符串strcatstrcat 会找到目标字符串末尾的 ‘\0’从那里开始追加源字符串并在新内容末尾写入新的 ‘\0’char*strcat(char*destination,constchar*source);使用时需要确保destination 本身已经是一个合法字符串。source 是以 ‘\0’ 结尾的字符串。destination 的剩余空间足够容纳追加内容和新的 ‘\0’。两个区域不能重叠。#includestdio.h#includestring.hintmain(void){charmessage[32]hello;strcat(message, world);puts(message);return0;}不要让字符串自己追加到自己例如 strcat(buffer, buffer)。源字符串和目标字符串重叠时标准并不保证结果应该改用更明确的临时缓冲区或内存移动方案。手动实现 strcat#includeassert.hchar*my_strcat(char*destination,constchar*source){char*resultdestination;assert(destination!NULL);assert(source!NULL);while(*destination!\0){destination;}while((*destination*source)!\0){/* append including the terminator */}returnresult;}五、比较字符串strcmpstrcmp 按字典序比较两个字符串从第一个字符开始逐个比较。遇到字符不同或者某个字符串先结束时比较停止intstrcmp(constchar*left,constchar*right);返回值只需要关注符号返回值小于 0表示 left 小于 right。返回值等于 0表示两个字符串内容相同。返回值大于 0表示 left 大于 right。不要把返回值当成固定的 -1、0 或 1。标准只保证正负关系不保证具体数值。字符串比较的是字符编码顺序而不是字符串长度。比如 “z” 的长度小于 “apple”但按常见字符编码顺序“z” 可能大于 “apple”。手动实现 strcmp#includeassert.hintmy_strcmp(constchar*left,constchar*right){assert(left!NULL);assert(right!NULL);while(*left!\0*left*right){left;right;}if((unsignedchar)*left(unsignedchar)*right){return-1;}if((unsignedchar)*left(unsignedchar)*right){return1;}return0;}转换为 unsigned char 可以避免某些平台上 char 为有符号类型时产生负值比较问题。六、带长度限制的字符串函数无长度限制的 strcpy、strcat 和 strcmp 使用方便但调用者必须自行保证空间和边界。带 n 的函数可以限制本次处理的最大字符数但它们并不自动解决所有安全问题。strncpychar*strncpy(char*destination,constchar*source,size_tcount);strncpy 最多复制 count 个字符。如果源字符串在 count 个字符之前遇到 ‘\0’目标区域剩余位置会补零如果源字符串长度大于或等于 count目标区域可能不会自动添加 ‘\0’。charname[8];strncpy(name,Alexander,sizeofname-1);name[sizeofname-1]\0;这种显式补终止符的写法更容易保证结果是合法字符串。不要简单地认为 strncpy 永远会返回以 ‘\0’ 结尾的字符串。strncatchar*strncat(char*destination,constchar*source,size_tcount);strncat 最多从 source 追加 count 个字符并在结果末尾写入一个 ‘\0’。目标数组必须有足够空间容纳原字符串、实际追加内容和结束标志。charmessage[32]hello ;strncat(message,world,5);需要注意count 是允许从源字符串读取的最大字符数不是目标数组的总容量。strncmpintstrncmp(constchar*left,constchar*right,size_tcount);strncmp 最多比较 count 个字符。如果在此之前发现不同字符、遇到字符串结束或者已经比较完 count 个字符函数就会停止if(strncmp(command,quit,4)0){puts(exit);}它适合比较固定前缀但如果要判断两个完整字符串是否相同必须同时考虑长度和结束符。七、查找字符串strstrstrstr 在一个字符串中查找另一个字符串第一次出现的位置char*strstr(constchar*text,constchar*pattern);找到时返回匹配位置的指针找不到时返回 NULL。如果 pattern 是空字符串标准行为是返回 text 本身#includestdio.h#includestring.hintmain(void){charmessage[]C makes memory visible;char*matchstrstr(message,memory);if(match!NULL){printf(found: %s\n,match);}return0;}返回的指针仍然指向原字符串。如果原字符串是字符数组可以通过它修改匹配位置如果原字符串是字符串字面量则不能写入。基础实现可以从主串的每一个位置开始逐字符比较#includestddef.hchar*my_strstr(constchar*text,constchar*pattern){if(*pattern\0){return(char*)text;}for(constchar*starttext;*start!\0;start){constchar*astart;constchar*bpattern;while(*a!\0*b!\0*a*b){a;b;}if(*b\0){return(char*)start;}}returnNULL;}这种实现易于理解但在大量文本匹配时可能效率较低。对性能要求较高的场景可以进一步研究 KMP 等模式匹配算法。八、切分字符串strtokstrtok 根据分隔符集合把一个字符串拆成多个标记char*strtok(char*str,constchar*separators);第一次调用时传入待切分字符串之后继续调用时传入 NULL函数会从上一次保存的位置继续查找#includestdio.h#includestring.hintmain(void){charinput[]userexample.com;constchar*separators.;for(char*tokenstrtok(input,separators);token!NULL;tokenstrtok(NULL,separators)){puts(token);}return0;}strtok 会直接修改输入字符串把分隔符替换成 ‘\0’因此不能把字符串字面量作为第一个参数。需要保留原始内容时应先复制到可修改的字符数组。传统 strtok 使用内部状态保存切分位置不适合交错切分多个字符串也不适合需要重入性的场景。具体平台如果提供 strtok_r 或其他可重入版本可以根据项目需求选择。九、错误信息strerror 与 errno许多库函数失败时会设置 errno它是一个错误码。strerror 可以把错误码转换为可读的说明#includeerrno.h#includestdio.h#includestring.hintmain(void){FILE*filefopen(missing.txt,r);if(fileNULL){fprintf(stderr,open failed: %s\n,strerror(errno));return1;}fclose(file);return0;}errno 只有在相关函数报告失败后才有意义。不要在成功调用后无条件读取它也不要把 errno 的数值直接当作用户可读的错误信息。十、字符分类与转换头文件 ctype.h 提供了一组字符分类函数和大小写转换函数isspace空白字符。isdigit十进制数字。isxdigit十六进制数字。islower、isupper小写或大写字母。isalpha字母。isalnum字母或数字。ispunct标点符号。isprint可打印字符。iscntrl控制字符。tolower、toupper大小写转换。#includectype.h#includestdio.hintmain(void){chartext[]C Language 2025!;for(size_ti0;text[i]!\0;i){unsignedcharch(unsignedchar)text[i];if(isupper(ch)){text[i](char)tolower(ch);}}puts(text);return0;}除了 EOF 之外传给这些函数的参数应该能够转换为 unsigned char。如果直接传入一个值为负的普通 char在某些平台上可能触发未定义行为。因此处理可能包含非 ASCII 字节的数据时先转换为 unsigned char 是稳妥的习惯。十一、按字节复制memcpy字符串函数以 ‘\0’ 为结束条件而 memcpy 完全按照字节数复制不会因为遇到零字节而提前停止void*memcpy(void*destination,constvoid*source,size_tcount);它可以复制字符数组、结构体或任意对象的字节表示#includestring.hstructPerson{charname[32];intage;};structPersonsource{Ada,36};structPersoncopy;memcpy(copy,source,sizeofcopy);memcpy 要求源区域和目标区域不能重叠。如果存在重叠复制结果是未定义的此时应该使用 memmove。十二、处理重叠区域memmovevoid*memmove(void*destination,constvoid*source,size_tcount);memmove 与 memcpy 的主要区别是能够正确处理重叠区域。它会根据源地址和目标地址的关系选择从前往后或从后往前复制#includestdio.h#includestring.hintmain(void){chartext[]abcdef;memmove(text2,text,4);puts(text);return0;}这里目标区域与源区域发生重叠使用 memcpy 并不安全而 memmove 会保证每个源字节在被覆盖前已经完成读取。手动实现 memmove#includestddef.hvoid*my_memmove(void*destination,constvoid*source,size_tcount){unsignedchar*dstdestination;constunsignedchar*srcsource;if(dstsrc||count0){returndestination;}if(dstsrc||dstsrccount){for(size_ti0;icount;i){dst[i]src[i];}}else{for(size_ticount;i0;--i){dst[i-1]src[i-1];}}returndestination;}真实库实现会针对平台进行优化但“无重叠时从前往后有重叠且目标在后方时从后往前”的思路是理解它的关键。十三、设置内存memsetmemset 把目标区域的每一个字节设置为同一个 unsigned char 值void*memset(void*destination,intvalue,size_tcount);#includestring.hcharbuffer[16];memset(buffer,0,sizeofbuffer);需要注意memset 按字节填充而不是把一个整数整体写入每个元素。下面的代码会把每个字节设置为 1因此整型数组中的每个元素通常会变成 0x01010101而不是整数 1intvalues[4];memset(values,1,sizeofvalues);如果想把整型数组初始化为 1应使用循环或显式赋值把内存清零则通常可以使用 memset(values, 0, sizeof values)。十四、按字节比较memcmpmemcmp 比较两个内存区域前 count 个字节intmemcmp(constvoid*left,constvoid*right,size_tcount);返回值同样只需要关注正负和零。它比较的是字节序列不是 C 字符串也不会因为遇到 ‘\0’ 停止#includestdio.h#includestring.hintmain(void){unsignedcharfirst[]{1,2,0,4};unsignedcharsecond[]{1,2,0,5};if(memcmp(first,second,sizeoffirst)0){puts(first is smaller);}return0;}对结构体直接使用 memcmp 判断是否相等要谨慎因为结构体可能包含填充字节而填充字节的内容未必相同。判断结构体逻辑是否相等通常应逐个比较有意义的成员。十五、字符串函数与内存函数的选择场景合适的函数计算以 ‘\0’ 结尾的字符串长度strlen复制完整字符串strcpy 或经过边界设计的替代方案追加字符串strcat 或长度受控的替代方案按字典序比较字符串strcmp、strncmp查找子串strstr按分隔符切分可修改字符串strtok报告 errno 对应的文字strerror复制任意字节序列memcpy复制可能重叠的内存区域memmove将字节填成同一个值memset比较任意字节序列memcmp字符串函数依赖 ‘\0’适合文本内存函数依赖显式字节数适合二进制数据、结构体和包含零字节的缓冲区。把二者混用是很多内存错误的根源。十六、实现和使用时的安全检查编写字符串和内存操作代码时可以遵循这些习惯明确源数据和目标数据的容量给字符串结束符预留空间。不把字符串字面量传给会修改内容的函数。需要处理重叠区域时使用 memmove不要使用 memcpy。使用 strncpy 后检查目标是否有 ‘\0’必要时手动补上。不要用字符串函数处理没有结束符的字符数组。使用 strlen 的返回值时注意 size_t 是无符号类型。调用 ctype.h 函数前将普通字符转换为 unsigned char。使用 memset 时记住它填充的是字节不是完整整数。用 memcmp 比较二进制数据时传入准确的字节数。模拟实现库函数时先写清参数顺序、停止条件、返回值和重叠规则。字符串函数让文本处理更方便内存函数则提供了更底层、更通用的字节操作能力。真正可靠的 C 代码不是简单地把一个函数名换成另一个函数名而是先弄清楚数据的表示方式、生命周期、容量和重叠关系再选择与问题匹配的工具。

相关推荐

OpenClaw:基于Node.js的OS级AI智能代理框架
OpenClaw:基于Node.js的OS级AI智能代理框架

1. 项目概述:这不是“AI遥控器”,而是操作系统级的智能代理层 OpenClaw这个名字乍一听像某种开源爬虫工具,但实际它彻底改写了“AI如何与真实世界交互”的底层逻辑。它不依赖截图识别、不靠模拟点击、更不是在浏览器里跑个自动化脚本——它直… · 2026/9/26 4:48:36

XXL-Job Java实战:分布式定时任务排坑与高可用落地
XXL-Job Java实战:分布式定时任务排坑与高可用落地

1. 这不是又一份“照着抄就能跑”的XXL-Job教程,而是我踩过37个坑后重写的Java定时任务实战手册你点开这个标题,大概率正被三件事困扰:第一,公司老项目里那个没人敢动的Quartz调度模块,每次加个新任务都要提心吊胆改XM… · 2026/9/26 4:48:36

DBSCAN密度聚类在风电场景削减中的应用:从原理到实操
DBSCAN密度聚类在风电场景削减中的应用:从原理到实操

做风电调度优化这几年,我慢慢发现一个问题:场景集的质量,往往比优化算法本身更决定结果的上限。同样的机组组合模型,喂进去一千条原始风电-负荷曲线,和喂进去十条精心削减后的代表性曲线,求解速度和调度策略… · 2026/9/26 4:48:30

西莫电机论坛视频+PDF资源高效实战指南:工程师必备方法
西莫电机论坛视频+PDF资源高效实战指南:工程师必备方法

2025年西莫电机论坛的“视频PDF”资源,我几乎天天都泡在里面用。做了十几年的电机设计,我的网盘里存着从论坛上攒下来的几百份资料,很多项目方案的突破口,都是靠这些资源逼出来的。这篇文章不打算给你列一个“十大必下资料榜单”&… · 2026/9/26 7:25:09

多Agent协作系统实战:架构设计、任务调度与避坑指南
多Agent协作系统实战:架构设计、任务调度与避坑指南

1. 多Agent协作到底在解决什么问题1.1 从单Agent的瓶颈说起如果你最近半年动手搭过基于大模型的自动化流程,大概率经历过这样一个阶段:一开始用一个Agent加一堆工具,感觉无所不能,写代码、查资料、做总结都能干。但任务一复杂&… · 2026/9/26 7:25:09

R语言机器学习诊断模型实战:9种模型对比与完整流程总结
R语言机器学习诊断模型实战:9种模型对比与完整流程总结

1. 我为什么花两周把9种机器学习诊断模型全部跑了一遍先说结论:如果你也有医学或生物信息学背景,想在手头只有一份Excel表格的情况下,用机器学习做诊断模型或者预测模型,R语言是目前性价比最高的选择。我这次把9种常见模型全部跑了… · 2026/9/26 7:25:09

用ThinkPHP打造学生成绩分析与教务管理系统
用ThinkPHP打造学生成绩分析与教务管理系统

写这套系统的时候,我手里正攥着一堆从教务处拷出来的Excel成绩单,一个班一个班地筛平均分、算及格率,数据一多表格就卡,公式一拖就错位,更别提跨学期对比学生成绩趋势这种“想想就头大”的需求。后来实在忍不了&#x… · 2026/9/26 7:25:09

Qwen-Agent本地部署实战:OpenAI兼容协议与tool call全链路调通
Qwen-Agent本地部署实战:OpenAI兼容协议与tool call全链路调通

1. 这不是“又一个部署教程”,而是把 Qwen-Agent 当成真实产品来跑通的实操记录我从去年底开始系统性地在本地跑各种大模型应用框架,从 LangChain 到 LlamaIndex,再到 Dify、FastChat、Ollama 的生态工具链,踩过太多“能启动但不能… · 2026/9/26 7:25:09

我用 go-zero 搭了一套海外短剧推荐系统:全景架构拆解
我用 go-zero 搭了一套海外短剧推荐系统:全景架构拆解

标题备选 我用 go-zero 搭了一套海外短剧推荐系统:从 API 网关到 MMoE 精排的全景架构规则先行、模型可插拔:一个短剧推荐系统的完整架构拆解go-zero gRPC ES Redis Triton:推荐系统落地全景(附踩坑清单) 摘要&… · 2026/9/26 7:25:03

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码