
适用对象学过数组、指针基本用法的同学本章目标掌握字符分类与转换函数 彻底理解 strlen / strcpy / strcat / strcmp 四大天王引入字符串处理为什么这么重要你正在读的这篇文章每个字都是一个字符你登录一个网站输入用户名密码背后就是字符比较你复制一段文字、搜索一个关键词全都是字符串操作。C 语言把字符串处理拆成了两个大家族ctype.h— 字符分类与转换一个字符是大写小写数字string.h— 字符串增删改查复制、拼接、比较、查长度这一篇我们先把这两个家族的顶梁柱搞定。第一部分字符分类函数ctype.h1.1 字符分类函数一览ctype.h提供了一族函数专门判断「这个字符是什么类型」。用起来特别简单——传入一个字符实际上是 int返回非零整数真或零假函数什么情况返回真iscntrl控制字符回车、换行等不可见字符isspace空白字符空格 、换页\f、换行\n、回车\r、制表符\t、垂直制表符\visdigit十进制数字0~9isxdigit十六进制数字09、af、A~Fislower小写字母a~zisupper大写字母A~Zisalpha字母islower或isupperisalnum字母或数字isalpha或isdigitispunct标点符号可打印但非字母非数字isgraph任何图形字符可见字符isprint任何可打印字符包括空格1.2 小写转大写两种写法对比方式一ASCII 差值法#includestdio.h#includectype.hintmain(){inti0;charstr[]Test String;charc;while(str[i]){cstr[i];if(islower(c))c-32;// 小写字母 ASCII - 32 对应的大写字母putchar(c);i;}return0;}输出TEST STRING方式二标准转换函数推荐#includestdio.h#includectype.hintmain(){inti0;charstr[]Test String;charc;while(str[i]){cstr[i];if(islower(c))ctoupper(c);// 直接用标准函数更安全putchar(c);i;}return0;}1.3 tolower 和 toupper 转换函数inttolower(intc);// 大写字母 → 小写字母非字母返回原值inttoupper(intc);// 小写字母 → 大写字母非字母返回原值⚠️提醒传入的虽然是int但实际只用到低字节。为什么要用int而不是char因为 EOF通常是-1也需要能传入而char是无符号的无法表示-1。技巧如果想把整个字符串转成大写直接用toupper遍历如果想把所有字母统计出来用isalpha判断即可。第二部分字符串长度 strlen2.1 strlen 三件事size_tstrlen(constchar*str);数什么从 str 出发一个字符一个字符往后数遇到第一个\0就停返回值数到的字符个数不包含\0返回值类型size_t无符号整数意味着永远不会是负数#includestdio.h#includestring.hintmain(){constchar*strabcdef;printf(%zu\n,strlen(str));// 输出 6return0;}2.2 strlen 的三个易错点易错点一字符串必须以\0结尾charbuf[3]{a,b,c};// 没有 \0// strlen(buf) → UB未定义行为会一直往后读直到碰到 \0易错点二返回值是无符号数这道题很多人会做错#includestdio.h#includestring.hintmain(){constchar*str1abcdef;// 长度 6constchar*str2bbb;// 长度 3if(strlen(str2)-strlen(str1)0)printf(str2 str1\n);// 这里会输出吗elseprintf(str1 str2\n);return0;}答案是输出str2 str1因为strlen返回的是size_t无符号数3 - 6在无符号运算中会发生下溢结果变成了一个巨大的正数远大于 0。⚠️提醒不要写if (strlen(a) strlen(b) - 3)这种式子。先把长度存到int变量里再做差比较。2.3 手写 strlen两种方法方法一计数器#includeassert.hintmy_strlen(constchar*str){assert(str!NULL);// 安检门intcount0;while(*str!\0){count;str;}returncount;}方法二指针相减两个相同类型的指针相减结果是它们之间相差的元素个数不是字节数。intmy_strlen(char*s){assert(s!NULL);char*ps;while(*p!\0)p;returnp-s;// 指针相减得到字符个数}第三部分字符串拷贝 strcpy3.1 strcpy 三件事char*strcpy(char*destination,constchar*source);做什么把 source 指向的字符串包括结尾的\0完整拷贝到 destination参数目标空间地址 源字符串地址返回值目标空间的起始地址#includestdio.h#includestring.hintmain(){chararr1[10]{0};chararr2[]hello;strcpy(arr1,arr2);// 把 hello\0 拷贝到 arr1printf(%s\n,arr1);// 输出 helloreturn0;}3.2 strcpy 四个使用条件条件说明常见错误源字符串以\0结尾不然不知道拷贝到哪里停字符数组忘加\0目标空间足够大能容纳源字符串 \0char s[5] hello越界目标空间可修改不能是常量区字符串strcpy(str, hi)目标不可写用const保护源指针防止不小心改掉源字符串手写实现时容易漏3.3 手写 strcpy标准写法#includestdio.h#includeassert.hchar*my_strcpy(char*dest,constchar*src){char*retdest;// 先记住目标起始地址最后要返回assert(dest!NULL);// 安检assert(src!NULL);while((*dest*src))// 拷贝 判断是否到 \0一步搞定;returnret;// 返回目标空间起始地址}记忆口诀strcpy五步走——记地址、断言、const 保护、while(*dest *src)、返回起始地址。第四部分字符串追加 strcat4.1 strcat 三件事char*strcat(char*destination,constchar*source);做什么找到 destination 的\0从那里开始接上 source 的内容连同 source 的\0参数目标字符串 源字符串返回值目标空间的起始地址#includestdio.h#includestring.hintmain(){chararr1[20]hello ;chararr2[]world;strcat(arr1,arr2);// 从 arr1 的 \0 处接上 worldprintf(%s\n,arr1);// 输出 hello worldreturn0;}⚠️提醒注意arr1初始化为hello 末尾有空格这样拼接后中间才有空格。strcat不会自动加空格要自己安排好。4.2 手写 strcat两步法char*my_strcat(char*dest,constchar*src){char*retdest;assert(dest!NULL);assert(src!NULL);// 第一步找到 dest 的 \0while(*dest)dest;// 第二步从 \0 处开始拷贝 srcwhile((*dest*src));returnret;}第五部分字符串比较 strcmp5.1 strcmp 三件事intstrcmp(constchar*str1,constchar*str2);做什么从左到右逐字节比较两个字符串的 ASCII 值遇到第一个不同就返回返回值 0str1大于str2 0相等 0str1小于str2#includestdio.h#includestring.hintmain(){chararr1[]abcdef;chararr2[]abq;intretstrcmp(arr1,arr2);// aa → bb → cq → 返回负数if(ret0)printf(arr1 arr2\n);elseif(ret0)printf(arr1 arr2\n);elseprintf(arr1 arr2\n);// 输出这行return0;}为什么 ‘c’ ‘q’ 就决定了结果字符串比较是逐字符进行的一旦发现有一对字符不相等立刻用这一对字符的 ASCII 差值决定结果后面的字符不再比较。5.2 手写 strcmp三行代码intmy_strcmp(constchar*str1,constchar*str2){assert(str1!NULL);assert(str2!NULL);while(*str1*str2){if(*str1\0)return0;// 完全相等str1;str2;}return*str1-*str2;// 不相等时返回差值}第六部分经典面试题精讲面试题一strlen 和 sizeof 有什么区别strlensizeof本质函数运行时计算运算符编译时求值参数char*字符串地址任意类型表达式统计内容\0之前的字符个数类型/变量占用的字节总数返回值size_t不含\0size_t含实际占用chararr[]hello;// char[6]含 \0strlen(arr)→5// 字符数sizeof(arr)→6// 整个数组字节数面试题二下面的代码有什么问题char*phello;chararr[5]world;strcpy(arr,p);// 问题在哪问题一arr只有 5 个字节p指向的字符串是hello6 个字节含\0目标空间不够越界写入。问题二p指向的是字符串常量.rodata段虽然加了const保护但万一改了就崩更重要的是strcpy的目标arr是栈上的局部数组初始化world实际上只写了 5 个字节arr[5]处没有\0连\0都没有拷贝起来更是 UB。面试题三手写 strcpy有人写了下面这个版本对吗char*my_strcpy(char*dest,constchar*src){while(*src!\0){*dest*src;dest;src;}*dest\0;// 手动补 \0returndest;// 返回 dest 的当前值}两个错误返回值错了strcpy标准要求返回目标空间的起始地址即调用前的 dest但这个版本返回了拷贝后的 dest 位置语义上多了一次赋值虽然逻辑正确但比while ((*dest *src))多写了 3 行不够简洁本章总结字符函数速查表场景函数头文件判断小写字母islower(c)ctype.h判断数字isdigit(c)ctype.h判断字母isalpha(c)ctype.h小写 → 大写toupper(c)ctype.h大写 → 小写tolower(c)ctype.h字符串四大函数对比函数做了什么注意strlen数\0前的字符数返回size_t无符号strcpy完整拷贝字符串目标必须够大strcat追加到目标\0处目标必须够大strcmp逐字节比较 ASCII第一个不同字符决定结果