ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

std::string 查找、截取与转换:find 返回 npos 不处理,线上直接崩

std::string 查找、截取与转换:find 返回 npos 不处理,线上直接崩 std::string 查找、截取与转换find 返回 npos 不处理线上直接崩摘要std::string 的 find 返回 npos 忘记判断、substr 越界抛异常、stoi 遇到非数字输入直接崩溃——这三个问题在生产环境里都出过事故。本文用真实场景拆解 find/rfind/substr/compare 的正确用法讲清 stoi/to_string 的异常边界并给出 trim/split/replace_all 的可复用实现。每个 API 配可运行代码和输出看完就能用。一、为什么这三个 API 最容易出事先看一段真实出事的代码// 从配置行中提取 key 和 value格式 keyvaluestd::pairstd::string,std::stringparseConfig(conststd::stringline){size_t posline.find();std::string keyline.substr(0,pos);// pos npos 时std::string valline.substr(pos1);// npos 1 溢出return{key,val};}如果传入的line里没有find返回std::string::npos。此时line.substr(0, npos)会截取到末尾不报错line.substr(npos 1)中npos 1变成 0无符号整数回绕实际从开头截取第二个 bug 极隐蔽。程序不崩但逻辑完全错了。如果换成line.at(pos)就会直接抛std::out_of_range。一句话记住find的返回值必须判断 npos不能直接参与下标运算。二、find 家族六个函数一张表说清std::string提供了六个查找函数很多人只用过find遇到复杂场景就手写循环。函数查找内容搜索方向未找到返回find子串/字符从头到尾nposrfind子串/字符从尾到头nposfind_first_of字符集合中任意一个从头到尾nposfind_last_of字符集合中任意一个从尾到头nposfind_first_not_of不在字符集合中的字符从头到尾nposfind_last_not_of不在字符集合中的字符从尾到头npos#includestring#includeiostreamintmain(){std::string urlhttps://example.com:8080/path?q1;size_t scheme_endurl.find(://);// 5size_t host_endurl.find(:,8);// 19从 8 开始找size_t path_starturl.find(/);// 7注意找的是 :// 里的 /size_t path_start2url.find(/,8);// 24从 8 之后找size_t last_doturl.rfind(.);// 15最后一个 . 在 example.com 里// find_first_of找第一个出现的任意分隔符std::string numsone,two;three four;size_t sepnums.find_first_of(,; );// 3逗号std::coutscheme_endscheme_end, host_endhost_end, last_dotlast_dot, sepsepstd::endl;// 输出scheme_end5, host_end19, last_dot15, sep3return0;}find_first_of vs find 的关键区别std::string shello world;std::couts.find(world)std::endl;// 6找整个子串 worldstd::couts.find_first_of(world)std::endl;// 1找 o因为 world 中第一个在 s 里出现的字符是 ofind_first_of(world)找的是字符集合{w,o,r,l,d}中任意一个字符在s中首次出现的位置。s[1]是e不在集合里s[2]是l在集合里所以返回 2不s[0]h不在s[1]e不在s[2]l在——返回2。实际运行后你会发现输出是2不是 1。o在s[4]l在s[2]先出现。一句话记住find找子串find_first_of找“字符集合里的任意一个”。三、substr截取子串越界会抛异常substr(pos, len)从pos开始截取len个字符返回新的std::stringstd::string shello world;std::couts.substr(0,5)std::endl;// hellostd::couts.substr(6)std::endl;// worldlen 省略取到末尾std::couts.substr(6,100)std::endl;// worldlen 超长自动截到末尾关键陷阱substr的pos参数如果 size()会抛出std::out_of_rangestd::string shello;try{s.substr(10);// pos 10 size() 5}catch(conststd::out_of_rangee){std::cout异常e.what()std::endl;}但pos size()是合法的返回空字符串std::couts.substr(s.size())std::endl;// 不抛异常安全截取模式std::stringsafeSubstr(conststd::strings,size_t pos,size_t lenstd::string::npos){if(poss.size())return;returns.substr(pos,len);}一句话记住substr的pos可以等于size()返回空串但不能大于size()抛异常。四、compare返回值不是“是否相等”compare返回一个整数语义是字典序比较返回值含义 0*this在str之前0两者等价 0*this在str之后std::string aapple;std::string bbanana;if(a.compare(b)0){std::couta 排在 b 前面std::endl;}最常见的误用用compare的返回值判断相等// ❌ 危险compare 返回的是非零值不是 1if(s.compare(hello)1){...}// 错hello 相等时返回 0不相等时可能返回任意负数/正数// ✅ 正确if(s.compare(hello)0){...}// 或者直接用 if(shello){...}Clang-Tidy 有一个专门的检查项misc-string-compare就是为了抓这种误用。什么时候该用compare当需要排序或判断字典序先后时。日常判等直接用。C20 新增的便捷函数std::string shello world;// C20if(s.starts_with(hello)){...}// 前缀检查if(s.ends_with(world)){...}// 后缀检查// C23if(s.contains(lo wo)){...}// 子串存在性在 C20 之前前缀/后缀检查需要手写// C17 写法boolstartsWith(conststd::strings,conststd::stringprefix){returns.size()prefix.size()s.compare(0,prefix.size(),prefix)0;}一句话记住compare 0才是相等判断前缀/后缀优先用 C20 的starts_with/ends_with。五、stoi / to_string异常边界必须处理5.1 to_string数值转字符串std::string s1std::to_string(42);// 42std::string s2std::to_string(3.14);// 3.140000std::string s3std::to_string(true);// 1to_string对浮点数的默认精度是 6 位不够灵活。需要控制精度时用std::ostringstream#includesstream#includeiomanipstd::ostringstream oss;ossstd::fixedstd::setprecision(2)3.14159;std::string soss.str();// 3.145.2 stoi字符串转整数三个异常std::stoi(str, pos, base)有三个参数但日常最常忽略的是异常处理#includestring#includeiostreamintmain(){// 正常情况intv1std::stoi(42);// 42intv2std::stoi(1010,nullptr,2);// 10二进制// 三种异常try{std::stoi(abc);// std::invalid_argument}catch(conststd::invalid_argumente){std::cout非数字输入std::endl;}try{std::stoi(99999999999999999999);// std::out_of_range}catch(conststd::out_of_rangee){std::cout超出 int 范围std::endl;}// 部分解析pos 告诉你解析到哪了size_t pos0;intv3std::stoi(42abc,pos);// v3 42, pos 2std::cout解析了 pos 个字符std::endl;// 2return0;}关键理解stoi遇到非数字字符时不一定抛异常。42abc会成功解析出42pos设为 2。只有第一个非空白字符就不是数字时才抛invalid_argument。std::stoi( 42);// 42跳过前导空白std::stoi(42abc);// 42, pos2部分解析std::stoi(abc42);// 抛 invalid_argument安全封装std::optionalintsafeStoi(conststd::strings){try{size_t pos0;intvstd::stoi(s,pos);if(pos!s.size())returnstd::nullopt;// 要求完整解析returnv;}catch(...){returnstd::nullopt;}}一句话记住stoi对42abc返回 42 不报错要完整解析必须检查pos s.size()。六、实战trim / split / replace_all标准库没有提供这三个最常用的字符串工具自己写一遍6.1 trimstd::stringtrim(conststd::strings){constchar*ws \t\n\r\f\v;size_t starts.find_first_not_of(ws);if(startstd::string::npos)return;size_t ends.find_last_not_of(ws);returns.substr(start,end-start1);}6.2 splitstd::vectorstd::stringsplit(conststd::strings,chardelim){std::vectorstd::stringresult;std::stringstreamss(s);std::string item;while(std::getline(ss,item,delim)){result.push_back(item);}returnresult;}6.3 replace_allstd::stringreplace_all(std::string s,conststd::stringfrom,conststd::stringto){if(from.empty())returns;size_t pos0;while((poss.find(from,pos))!std::string::npos){s.replace(pos,from.size(),to);posto.size();}returns;}一个易忽略的点replace_all的pos to.size()不能写成pos from.size()。如果to比from短会出现死循环。七、高频面试题速答Q1find没找到返回什么std::string::npos一个size_t类型的最大值常量。必须判断不能直接参与下标运算。Q2substr(pos)中pos size()会怎样抛出std::out_of_range。pos size()合法返回空字符串。Q3stoi(42abc)会抛异常吗不会。返回42pos参数设为2。只有第一个非空白字符不是数字时才抛invalid_argument。Q4compare返回 1 表示什么表示*this字典序在参数之后不是“相等”。判等用compare(...) 0或。Q5C20 有什么新函数替代手写前缀检查starts_with()和ends_with()。C23 增加了contains()。八、总结API核心要点常见坑find返回npos表示未找到不判断 npos 直接用find_first_of找字符集合任意一个误以为找子串substrpos size()抛异常npos 1回绕compare返回0 / 0 / 0用 1判等stoi部分解析不抛异常不检查posto_string浮点默认 6 位精度精度不可控下一篇进入本系列的重头戏SSO、COW 与 std::string 的真实内存布局。会用代码打印地址验证 libstdc、libc、MSVC 三家的 SSO 容量差异并解释为什么 C11 之后 COW 实现被“逼退”了。系列导航上一篇《std::string 访问与修改operator[]、at、data 与迭代器失效》下一篇《std::string 底层实现SSO、COW 与内存布局》评论区互动你被find返回npos坑过吗是在哪个场景下踩的评论区说说我看看能不能出一个“npos 避坑合集”。
返回列表