适用对象学过 strcpy / strcat / strcmp 的同学本章目标掌握安全版字符串函数 strstr 子串查找 strerror 错误处理引入strcpy 为什么会让人又爱又恨上一章我们学了strcpy它简单粗暴复制速度也快——但它有一个致命问题不检查目标空间是否够用。如果目标空间只有 5 字节但你拷贝了 20 字节的字符串会发生什么内存越界写入可能会把旁边的变量覆盖掉或者触发段错误Segmentation Fault。更可怕的是有时候程序不会马上崩而是在某个完全不相关的地方突然出问题——这种 Bug 极难定位。于是C 语言标准委员会给几大函数都设计了带数字版本strncpy/strncat/strncmp把拷贝/追加/比较的长度交给程序员控制——更安全但也更复杂。第一部分strncpy——安全拷贝 n 个字符1.1 函数原型char*strncpy(char*destination,constchar*source,size_tnum);第三个参数num最多拷贝num个字符。#includestdio.h#includestring.hintmain(){chararr1[20]{0};chararr2[]abcdefghi;strncpy(arr1,arr2,5);// 只拷贝前 5 个字符printf(%s\n,arr1);// 输出 abcdereturn0;}1.2 strncpy 的三个坑坑一源字符串不够 num 个字符时用\0填充chararr1[20]{0};chararr2[]hi;strncpy(arr1,arr2,10);// 源只有 2 个字符printf(%s\n,arr1);// 输出 hi后面的 8 个位置全部填 \0坑二源字符串够长时不保证在第 num 位置写\0chararr1[20]{0};chararr2[]hello;strncpy(arr1,arr2,3);// 只拷贝 3 个字符 helprintf(%s\n,arr1);// 输出 hel没有 \0arr1[3] 不是 \0⚠️提醒strncpy在源字符串长度 ≥ num 时不会自动补\0这是最容易踩的坑。如果你在后面调用printf(%s, arr1)它会继续往后读内存打印出一堆乱码。所以使用strncpy后务必手动确保目标字符串以\0结尾if(numdest_size)arr1[num]\0;1.3 strncpy vs strcpy怎么选对比strcpystrncpy安全性❌ 不检查边界✅ 可控拷贝长度何时用源字符串短小确定不确定源串长度时长度足够时的 \0一定会补不保证第二部分strncat——安全追加 n 个字符2.1 函数原型char*strncat(char*destination,constchar*source,size_tnum);追加最多num个字符但无论源多长都会追加\0这是它比strncpy更安全的地方。#includestdio.h#includestring.hintmain(){chararr1[20]hello ;chararr2[]world;strncat(arr1,arr2,3);// 只追加前 3 个字符worprintf(%s\n,arr1);// 输出 hello worreturn0;}2.2 strncat 的一个陷阱strncat追加到destination的\0位置开始所以destination本身必须以\0结尾。如果destination忘了初始化chararr1[20];// 未初始化里面是垃圾值strncat(arr1,hi,2);// 危险从哪里开始追加正确写法在使用strncat之前先确保destination有\0或者用arr1[0] \0清空。第三部分strncmp——安全比较 n 个字符3.1 函数原型intstrncmp(constchar*str1,constchar*str2,size_tnum);最多比较前num个字符返回规则同strcmp。#includestdio.h#includestring.hintmain(){chararr1[]abcdef;chararr2[]abcqw;intret1strncmp(arr1,arr2,3);// 只比较前3个abc abc → 0printf(%d\n,ret1);// 输出 0intret2strncmp(arr1,arr2,4);// 比较前4个d q → 负数printf(%d\n,ret2);// 输出负数return0;}3.2 典型应用比较字符串前缀// 判断字符串是否以 http:// 开头if(strncmp(url,http://,7)0)printf(这是 HTTP 链接\n);第四部分strstr——在字符串里找子串4.1 函数原型char*strstr(constchar*str1,constchar*str2);在str1里找str2第一次出现的位置。#includestdio.h#includestring.hintmain(){charstr[]This is a simple string;char*pchstrstr(str,simple);if(pch!NULL)printf(找到了%s\n,pch);// 输出 simple stringelseprintf(没找到\n);return0;}返回值找到则返回指向该位置的指针没找到返回NULL。4.2 手写 strstr暴力匹配核心思路双层循环外层遍历str1内层逐字符比较str1和str2。#includeassert.hchar*my_strstr(constchar*str1,constchar*str2){assert(str1!NULL);assert(str2!NULL);// 空字符串直接返回 str1标准行为if(*str2\0)return(char*)str1;constchar*s1str1;constchar*s2str2;while(*s1){// 记录当前位置constchar*curs1;// 逐字符比较while(*s1*s2*s1*s2){s1;s2;}// s2 遍历完了 → 匹配成功if(*s2\0)return(char*)cur;// 没匹配上重置 s2回到 cur 的下一个位置继续s1cur1;s2str2;}returnNULL;}第五部分strerror——把错误码翻译成人话5.1 什么是 errnoC 语言程序启动时系统会初始化一个全局变量errno定义在errno.h。当你调用标准库函数出错了函数会把对应的错误码写进errno。错误码是一个整数比如 2、5、11——直接看数字完全不知道什么意思。#includeerrno.h// 声明全局变量 errno5.2 strerror翻译错误码char*strerror(interrnum);把错误码翻译成人类可读的错误信息字符串。#includestdio.h#includestring.hintmain(){for(inti0;i10;i)printf(%d: %s\n,i,strerror(i));return0;}输出Windows VS20220: No error 1: Operation not permitted 2: No such file or directory 3: No such process 4: Interrupted function call 5: Input/output error 6: No such device or address 7: Arg list too long 8: Exec format error 9: Bad file descriptor 10: No child processes5.3 实战fopen 出错了怎么办#includestdio.h#includestring.h#includeerrno.hintmain(){FILE*pFilefopen(不存在的文件.txt,r);if(pFileNULL){printf(文件打开失败%s\n,strerror(errno));// 输出文件打开失败No such file or directoryreturn1;}// ... 正常读写文件 ...fclose(pFile);return0;}5.4 perror更简洁的写法perror是strerror的快捷版本直接把错误信息打印出来perror(文件打开失败);// 输出文件打开失败No such file or directory相当于下面这行printf(文件打开失败%s\n,strerror(errno));选哪个如果你需要打印错误用perror更简洁如果你需要存起来或者自定义前缀用strerror。第六部分经典面试题精讲面试题一strncpy 和 strcpy 的核心区别是什么strncpy 有什么坑答案strcpy以\0为结束标志拷贝整个源字符串strncpy以num为拷贝上限但有两个坑源不够长时用\0填充剩余位置有时候不是你想要的源够长时不自动补\0比strcpy更危险加分回答面试中如果能用上「源够 num 时不补\0」这个坑点面试官会觉得你理解得很深。建议使用strncpy后手动dest[num] \0收尾。面试题二strstr 返回值是什么类型的指针答案char*指向str1中首次匹配位置的指针。如果匹配失败返回NULL。charstr[]hello world;char*pstrstr(str,world);printf(%s\n,p);// worldprintf(%ld\n,p-str);// 6world 在第6个位置起进阶用法通过指针相减可以算出子串在原串中的位置。面试题三下面的代码会输出什么#includestdio.h#includestring.hintmain(){chars1[]hello;chars2[]world;charbuf[10];strncpy(buf,s1,10);strncat(buf,s2,3);printf(%s\n,buf);return0;}推演strncpy(buf, s1, 10)buf大小 10 字节s1只有 5 字符 \0所以全部拷贝再填充 4 个\0。bufhello\0\0\0\0\0strncat(buf, s2, 3)从buf的\0位置开始追加s2前 3 个字符wor。bufhellowor\0第二个\0被覆盖但后面还有 2 个\0printf(%s\n, buf)从buf[0]打印到\0输出hellowor答案hellowor本章总结安全版字符串函数函数额外参数与基础版的关键区别strncpysize_t num源够 num 时不补\0需手动处理strncatsize_t num追加 num 字符但一定会补\0strncmpsize_t num最多比较前 num 个字符strstr 用法速记找得到 → 返回指针指向匹配位置找不到 → 返回NULL指针相减 → 子串在原串中的位置错误处理速记函数用法特点errno全局变量记录最近一次错误码调用标准库函数后检查strerrorstrerror(errno)→ 字符串可存、可自定义perrorperror(提示)直接打印等价于printfstrerror