荆州网络推广汕尾网站seo

张小明 2026/1/8 17:42:02
荆州网络推广,汕尾网站seo,文化传媒建设网站,济南阿里科技网站建设有限公司现代C工程实践#xff1a;简单的IniParser4——实现ini_parser 前言 在上一篇博客中#xff0c;我们已经完成了split的优化。现在我们即将开始我们工作的核心。这就是说#xff0c;在项目工程的前期#xff0c;我们把一些基建搞定了#xff0c;由于IniParser很简单#x…现代C工程实践简单的IniParser4——实现ini_parser前言在上一篇博客中我们已经完成了split的优化。现在我们即将开始我们工作的核心。这就是说在项目工程的前期我们把一些基建搞定了由于IniParser很简单所以在这个工程中我们的基建基本上算是做完了当然其他的类似IO流的封装我们不做处理我们的核心还是IniParser我们重新回顾一下需求我们要编写一个解析器逐行的解析我们的状态——确定当前行所在的section以及对应的Key value是如何的对于双引号/单引号引的内容要转义。我们重新设计一份接口/** * file ini_parse.h * author Charliechen114514 (chengh1922mails.jlu.edu.cn) * brief This is an on-for ini ASCII Parser * version 0.1 * date 2025-12-06 * * copyright Copyright (c) 2025 * */ #include optional #include string #include string_view #include unordered_map namespace cxx_utils { namespace ini_parser { /** * brief Ini parser accept parse a * simple ini file, which contains * only plain kv pairs * */ class IniParser { public: IniParser() default; IniParser(const IniParser) delete; const IniParser operator(const IniParser) delete; /** * brief parse a view-ini string * * param view * return true * return false */ bool parse(const std::string_view view); /** * brief get the value from parser map * * param section * param key * return std::optionalstd::string */ std::optionalstd::string get(const std::string section, const std::string key); /** * brief get the value from ini file * * param section * param key * param default_value * return std::string */ std::string get(const std::string section, const std::string key, const std::string default_value); /** * brief check if the mappings owns value, * briefly recommend when need to know if owns value * * param section * param key * return true * return false */ bool has(const std::string section, const std::string key); using ini_section_t std::unordered_mapstd::string, std::string; using ini_data_t std::unordered_mapstd::string, ini_section_t; /** * brief get the datas directly * * return const ini_data_t */ inline const ini_data_t data() const noexcept { return m_data; } /** * brief clear the datas, reset the ini parser * */ void clear() noexcept { return m_data.clear(); } private: ini_data_t m_data; /// data_stores private: bool consume_line(const std::string_view line, std::string current_section); }; } }我们使用bool parse(const std::string_view view);来解析给定的字符串为什么是std::string_view。我们要解析的 INI 文件长这样; 这是一个注释 [Server] ip 127.0.0.1 port 8080 [User] name John Doe ; 这是行内注释 message Hello\nWorld ; 支持转义字符回顾我们的任务我们要支持的就是Section段落比如[Server]。Key-Value键值对比如ip 127.0.0.1。注释支持;和#。复杂情况支持引号包裹的值以及引号内的转义字符如\n。如何行动起来笔者编写的时候就苦恼如果直接说给一个方案那这个跟其他教程有啥区别啊。没意思所以笔者决定倒过来像通关一样说明我们的iniparser的基本要求做到什么样子就像实际开发的时候用户需求反馈那样。定义测试用例我们来看看咱们的处理器要达到一起什么样的要求测试用例1: 空行处理TEST_CASE(Empty lines should be ignored){IniParser parser;std::string section;REQUIRE(parser.consume_line(,section)true);REQUIRE(parser.consume_line( ,section)true);REQUIRE(parser.consume_line(\t\t,section)true);}需求解读空行和只包含空白字符的行应该被忽略不影响解析结果。测试用例2: 注释行处理TEST_CASE(Comment lines should be ignored){IniParser parser;std::string section;REQUIRE(parser.consume_line(; this is comment,section)true);REQUIRE(parser.consume_line(# this is comment,section)true);REQUIRE(parser.consume_line( ; comment with spaces,section)true);}需求解读以;或#开头的行是注释应该被完全忽略。注释符前面可以有空白。测试用例3: Section解析TEST_CASE(Section parsing){IniParser parser;std::string section;REQUIRE(parser.consume_line([database],section)true);REQUIRE(sectiondatabase);REQUIRE(parser.consume_line([ server ],section)true);REQUIRE(sectionserver);}需求解读方括号内的内容是section名需要trim空白。测试用例4: Section格式错误检测TEST_CASE(Invalid section format){IniParser parser;std::string section;REQUIRE(parser.consume_line([no_closing,section)false);REQUIRE(parser.consume_line([section]garbage,section)false);}需求解读格式错误的section应该返回false包括没有闭合的]]后面有非空白、非注释字符测试用例5: 键值对解析TEST_CASE(Key-value parsing){IniParser parser;std::string sectiontest;parser.m_data[section]{};REQUIRE(parser.consume_line(keyvalue,section)true);REQUIRE(parser.m_data[section][key]value);REQUIRE(parser.consume_line(port 8080,section)true);REQUIRE(parser.m_data[section][port]8080);}需求解读keyvalue格式等号两边可以有空白需要trim。测试用例6: 带引号的值TEST_CASE(Quoted values){IniParser parser;std::string sectiontest;parser.m_data[section]{};REQUIRE(parser.consume_line(path\C:\\Users\\test\,section)true);REQUIRE(parser.m_data[section][path]C:\\Users\\test);}需求解读值可以用引号包围引号应该被移除转义字符应该被处理。测试用例7: 行内注释TEST_CASE(Inline comments){IniParser parser;std::string sectiontest;parser.m_data[section]{};REQUIRE(parser.consume_line(keyvalue ; comment,section)true);REQUIRE(parser.m_data[section][key]value);// 引号内的;不是注释REQUIRE(parser.consume_line(msg\a;b\ ; real comment,section)true);REQUIRE(parser.m_data[section][msg]a;b);}需求解读;和#可以出现在行中间作为注释但引号内的不算。测试用例8: 无效的键值对TEST_CASE(Invalid key-value pairs){IniParser parser;std::string sectiontest;REQUIRE(parser.consume_line(no_equals_sign,section)false);REQUIRE(parser.consume_line(no_key,section)false);}需求解读没有等号或key为空的行是无效的。测试用例1: 空行处理空行的处理要使用专门的trim函数搞定毕竟有时候咱们的输入不见得一定是严格格式化的所以需要做一点预处理std::string_viewtrim_view(conststd::string_view src_view,TrimPolicy policy){// if the src_view emptyif(src_view.empty()){returnsrc_view;}size_t endsrc_view.size();size_t start0;if(policyTrimPolicy::Both||policyTrimPolicy::Left){while(startendis_space_char(src_view[start])){start;}}if(policyTrimPolicy::Both||policyTrimPolicy::Right){while(endstartis_space_char(src_view[end-1])){end--;}}returnsrc_view.substr(start,end-start);}首先我们需要一个函数来去除字符串首尾的空白。这是处理空行的前提。上面就是一个example。 第一版实现// 版本1: 最简单实现boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){// 先去除首尾空白constautopreprocessed_linecxx_utils::string::trim_view(line);// 测试1: 空行处理if(preprocessed_line.empty()){returntrue;}// 测试2: 注释行处理if(preprocessed_line.front();||preprocessed_line.front()#){returntrue;}returnfalse;// 其他情况暂不处理}很快会有朋友注意到当我们看到代码中有这样的判断if(preprocessed_line.front();||preprocessed_line.front()#)这段逻辑显然可能会在多个地方使用比如行内注释检测所以我们提取一个辅助函数namespace{constexprinlineboolis_comments(conststd::string_view sv){constcharis_comments_chsv.front();if(is_comments_ch;||is_comments_ch#){returntrue;}returnfalse;}}这里笔者觉得很有必要聊下constexpr是现代C的关键字指导我们的编译器在编译期计算这个有趣的关键字会专门开博客说明inline避免多次定义注意一些老教程会告诉你内联展开现在inline没有这个意思了注意是没有这个意思了他的真正含义是避免多次定义namespace {}这个呢是匿名空间过去很多朋友使用static来限制一个更加现代和安全的做法是采用这个这个时候编译器会随机生成保证并不重复的空间名称修饰这下面包裹的符号。现在我们的代码干净了一些boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}if(is_comments(preprocessed_line)){returntrue;}returnfalse;}测试用例3 4: Section的处理流下面我们要搞的就是Section的处理了。如果说前面还在小试身手那这里显然就是一个小挑战了。之后我们入手需求先不要慌观察被建模对象才是王道我们知道在INI 文件中Section 的典型形式是[network]所以我们可以先给自己定下第一个最简单、也是最稳妥的规则✅只要一行是以[开头的就“有可能”是一个 Section 行注意这里的措辞是“有可能”因为[network这种明显是不合法的但第一步我们只负责识别候选行而不是立刻判合法性。所以熟悉STL的你一下子就能写出代码这一步通常只是if(!line.empty()line.front()[){// 可能是 section}现在我们在if模块内可以肯定的说“这是一个 Section 候选行”之后下一步就是思考咋把section搞出来呢合法的 Section 一定是[ section_name ]因此我们的解析思路非常清晰找到第一个]提取[和]中间的内容对结果做trim去掉首尾空白举个例子[ database ]最终得到的 section 名应当是database其实我们也顺手把非法的Section搞出来了——如果整行连]都找不到那它一定是非法的 Section 行。[database这种情况应当直接返回false交由上层逻辑处理错误。可是别急我们还要思考来看下面几种写法[core] ; 合法后面是注释 [core] # 合法后面是注释但下面这种就不行了[core] abc现在我们可以理清楚逻辑了笔者列在下面找到 **第一个]**后利用STL String的substr拉取内容找不到说明是非法内容我们还要检查]后面只允许出现空白字符或注释一旦出现其他内容说明这一行是非法的 Section 第二版实现boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}if(is_comments(preprocessed_line)){returntrue;}// 新增Section解析if(preprocessed_line.front()[){// 查找闭合的 ]autoend_section_pospreprocessed_line.find(]);if(end_section_posstd::string::npos){returnfalse;// 测试4: 没有闭合括号}// 提取section名称并trimautosection_svpreprocessed_line.substr(1,end_section_pos-1);section_svstring::trim_view(section_sv);// 测试4: 检查]后面是否有非空白字符除了注释if(end_section_pos1preprocessed_line.size()){for(size_t iend_section_pos1;ipreprocessed_line.size();i){constcharcpreprocessed_line[i];// 如果遇到注释符号剩余部分可以忽略if(c;||c#){break;}// 如果是非空白字符格式错误if(!std::isspace(static_castunsignedchar(c))){returnfalse;}}}// 更新当前sectioncurrent_sectionsection_sv;// 确保m_data中有这个sectionif(m_data.find(current_section)m_data.end()){m_data.emplace(current_section,ini_section_t{});}returntrue;}returnfalse;// 其他情况暂不处理}但是实际上事情还没完在检查]后面的内容时我们简单地判断c ; || c #就认为是注释。但如果写成这样呢[section] quoted;text ; actual comment这种情况下引号内的;不应该被当作注释开始。虽然这种写法很罕见但为了健壮性我们应该处理。但是很紧急吗比起来一个项目应该首先可用然后是逐步的完善健壮性而不是在最开始就给自己埋复杂度让自己的代码没有周旋的余地所以我们评估这个需求完全可以放到之后再迭代。做到键值对分割和存储在 Section 能被正确识别之后解析器接下来要面对的就是 INI 文件中最常见、也最核心的结构key value在 INI 文件中只要一行满足如下条件我们想一想这大概率就是一个键值对不是空行不是注释更不是 Section不以[开头但是最重要的显然是百分百存在一个因此最朴素的第一条规则是通过来分割 Key 和 Value。但真实世界从来不会这么“乖”。很有可能我们能拿到类似这样的东西url https://example.com?a1b2 token abcdef仔细想想这不就是ini规则中的——只按“第一个”分割Key取这个等号的左边其他的是右边。换句话说abcd应当被解析为key - a value - bcd 第三版实现boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}if(is_comments(preprocessed_line)){returntrue;}if(preprocessed_line.front()[){// ... section处理代码 (同迭代2)returntrue;}// 新增键值对解析// 先简单按分割constautosplited_kvstring::splits(preprocessed_line,);if(splited_kv.size()2){returnfalse;// 测试8: 没有等号}std::string key{string::trim_view(splited_kv[0])};// value可能包含所以重新提取constautopospreprocessed_line.find();// 我们当然可以自信的不下判断了前面挡回去了不存在的情况std::string value{string::trim_view(preprocessed_line.substr(pos1))};if(key.empty()){returnfalse;// 测试8: 空key}m_data[current_section][key]value;returntrue;}现在我们的测试器基本可以正常工作了但是我们的老问题没解决就是引用的问题“”所以我们就需要思考下面这个问题了如何处理类似msga;b ; real comment的ini呢这里面有两个;但它们的语义完全不同这是因为——第一个;在在双引号内部由此他是字符串值的一部分第二个不是他在之外必然是一个注释的起始符号如果我们之前不去处理好他们我们就会把a当值了后面拉一连串的全丢掉了。所以我们真的需要一个函数判断我们是不是在一个引号内。很多人会重复我们之前的想法——我能不能往前找最近的再往后找一个说得好你很是一个软件复用的天才但是很遗憾你没考虑嵌套单引号 vs 双引号和转义字符\等问题这也就意味着我们需要时刻判断是不是在一个引号内。最小、也是最清晰的状态模型是in_double当前是否在双引号内in_single当前是否在单引号内并且遵守一个重要约定同一时刻只可能在一种引号内单引号里的只是普通字符双引号里的也是普通字符。很自然的想法就是搞一个状态机。存住我们的状态——bool in_double false;记录我们是否处于双引号() 字符串内。bool in_single false;记录我们是否处于单引号() 字符串内。然后我们对视图从指定的位置开始扫描namespace{constexprinlineboolisPositionInsideQuotes(std::string_view line,size_t pos){boolin_doublefalse;boolin_singlefalse;for(size_t i0;iposiline.size();i){...}returnin_double||in_single;}}当我们扫描到引号字符时或我们不能直接改变状态。我们需要先检查一个非常重要的条件它是否被转义了bool escaped (i 0 line[i - 1] \\);转义的引号如果引号的前一个字符是反斜杠\那么这个引号就是被转义的例如\。处理被转义的引号只是字符串中的一个普通字符它没有能力开启或关闭一个字符串。总结只有未被转义的引号才有资格改变我们的in_double或in_single状态。namespace { constexpr inline bool isPositionInsideQuotes(std::string_view line, size_t pos) { bool in_double false; bool in_single false; for (size_t i 0; i pos i line.size(); i) { if (line[i] !in_single) { // 检查是否被转义 bool escaped (i 0 line[i - 1] \\); if (!escaped) in_double !in_double; } else if (line[i] \ !in_double) { bool escaped (i 0 line[i - 1] \\); if (!escaped) in_single !in_single; } } return in_double || in_single; } }在改变状态时代码遵循严格的互斥原则遇到双引号 ():只有当我们不在单引号内!in_single时这个双引号才有效。遇到单引号 ():只有当我们不在双引号内!in_double时这个单引号才有效。这个设计保证了字符串之间的嵌套不会互相干扰例如在Hello world这个双引号字符串中里面的单引号不会被错误地当作字符串的起始或结束。 第四版实现boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}if(is_comments(preprocessed_line)){returntrue;}if(preprocessed_line.front()[){autoend_section_pospreprocessed_line.find(]);if(end_section_posstd::string::npos){returnfalse;}autosection_svpreprocessed_line.substr(1,end_section_pos-1);section_svstring::trim_view(section_sv);// 更新使用isPositionInsideQuotes检查if(end_section_pos1preprocessed_line.size()){for(size_t iend_section_pos1;ipreprocessed_line.size();i){constcharcpreprocessed_line[i];if((c;||c#)!isPositionInsideQuotes(preprocessed_line,i)){break;}if(!std::isspace(static_castunsignedchar(c))){returnfalse;}}}current_sectionsection_sv;if(m_data.find(current_section)m_data.end()){m_data.emplace(current_section,ini_section_t{});}returntrue;}// 新增找到真正的注释位置不在引号内size_t comment_posstd::string::npos;for(size_t i0;ipreprocessed_line.size();i){if((preprocessed_line[i];||preprocessed_line[i]#)!isPositionInsideQuotes(preprocessed_line,i)){comment_posi;break;}}// 移除注释部分constautowithout_comment(comment_posstd::string::npos)?preprocessed_line:preprocessed_line.substr(0,comment_pos);// 分割键值对constautosplited_kvstring::splits(without_comment,);if(splited_kv.size()2){returnfalse;}std::string key{string::trim_view(splited_kv[0])};constautoposwithout_comment.find();if(posstd::string::npos){returnfalse;}std::string value{string::trim_view(without_comment.substr(pos1))};if(key.empty()){returnfalse;}m_data[current_section][key]value;returntrue;}处理value中的字符串在前面的步骤中我们已经能够正确识别key value的基本结构。但一个合格的 INI 解析器还必须处理一个常见且容易被忽略的细节值可能被引号包围并且内部可能包含转义字符。path C:\\Program Files\\MyApp title Hello \World\如果我们直接把等号右侧的字符串原样存下来最终得到的值显然是不正确的。因此在解析流程的最后阶段需要对 value 再做一次“语义层面的清洗”。为此我们引入第四个辅助函数std::stringunquoteAndUnescape(std::string_view raw);它的职责非常单一也非常明确移除合法的外层引号并处理内部的转义序列。当然并不是所有的值都需要处理引号例如count 42 enabled true因此函数的第一件事并不是“无脑去引号”而是检查首尾字符是否构成一对匹配的引号只有在满足这个条件时才认为这是一个被引号包围的字符串值。一旦确认成立真正的内容位于raw[1 ... raw.size() - 2]否则说明这是一个普通值可以直接进入下一阶段或原样返回。这个判断非常关键它避免了如下错误行为path C:\test\file ; 不应该被当成引号字符串之后就是我们处理转义了去掉外层引号后字符串内部仍然可能包含转义序列例如\\→\\→\n→ 换行是否支持取决于设计处理转义字符最稳妥的方式是手动遍历字符串。在处理开始时我们初始化一个空的结果字符串result然后从输入字符串的第一个字符开始向右扫描。如果当前扫描到的字符不是反斜杠\那么该字符被视为普通数据会直接追加到结果字符串的末尾并继续扫描下一个字符。然而如果当前字符是反斜杠\程序会进入转义状态必须查看紧随其后的下一个字符。如果这个序列\加上下一个字符例如\n或\\是一个合法的转义约定程序会将其作为一个整体进行转换并将转换后的单个字符追加到结果字符串中同时扫描指针会推进两个位置消耗掉\和被转义的字符。如果反斜杠出现在末尾即\是字符串的最后一个字符那么这种情况通常被视为格式不完整或非法输入具体处理方式取决于程序设计可以将其按原样\追加到结果中宽容处理或者直接报错并停止解析严格处理。通过这种机制所有具有特殊意义的转义序列都会被解析和替换而所有普通字符则被保留最终得到一个正确反映原始数据的字符串。for(size_t i0;istr.size();i){if(str[i]!\\){result.push_back(str[i]);}else{if(i1str.size()){charnextstr[i];switch(next){case\\:result.push_back(\\);break;case:result.push_back();break;casen:result.push_back(\n);break;default:result.push_back(next);break;}}}}综上这样的代码就足够了namespace{std::stringunquoteAndUnescape(conststd::string_viewsv){constsize_t nsv.size();if(n2)returnstd::string{sv};constcharfirstsv.front();constcharlastsv.back();// 检查是否被引号包围if(!((firstlast)||(first\last\)))returnstd::string{sv};// 提取引号内的内容std::string_view inner{sv.data()1,n-2};std::string out;out.reserve(inner.size());// 处理转义序列for(size_t i0;iinner.size();i){charcinner[i];if(c\\i1inner.size()){charnextinner[i1];switch(next){casen:out.push_back(\n);break;caset:out.push_back(\t);break;case\\:out.push_back(\\);break;case:out.push_back();break;case\:out.push_back(\);break;default:out.push_back(next);break;}i;// 跳过下一个字符}else{out.push_back(c);}}returnout;}} 最终版实现boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}if(is_comments(preprocessed_line)){returntrue;}if(preprocessed_line.front()[){autoend_section_pospreprocessed_line.find(]);if(end_section_posstd::string::npos){returnfalse;}autosection_svpreprocessed_line.substr(1,end_section_pos-1);section_svstring::trim_view(section_sv);if(end_section_pos1preprocessed_line.size()){for(size_t iend_section_pos1;ipreprocessed_line.size();i){constcharcpreprocessed_line[i];if((c;||c#)!isPositionInsideQuotes(preprocessed_line,i)){break;}if(!std::isspace(static_castunsignedchar(c))){returnfalse;}}}current_sectionsection_sv;if(m_data.find(current_section)m_data.end()){m_data.emplace(current_section,ini_section_t{});}returntrue;}size_t comment_posstd::string::npos;for(size_t i0;ipreprocessed_line.size();i){if((preprocessed_line[i];||preprocessed_line[i]#)!isPositionInsideQuotes(preprocessed_line,i)){comment_posi;break;}}constautowithout_comment(comment_posstd::string::npos)?preprocessed_line:preprocessed_line.substr(0,comment_pos);constautosplited_kvstring::splits(without_comment,);if(splited_kv.size()2){returnfalse;}std::string key{string::trim_view(splited_kv[0])};constautoposwithout_comment.find();if(posstd::string::npos){returnfalse;}// 最后更新使用unquoteAndUnescape处理值std::string valueunquoteAndUnescape(string::trim_src(without_comment.substr(pos1)));if(key.empty()){returnfalse;}m_data[current_section][key]value;returntrue;}完整实现代码经过5次迭代我们得到了完整的实现。这是最终的产品代码其他的接口供同志们进行练习这里不再单独讲解。#includeini_parse.h#includestring_splits.h#includestring_trim.h#includeoptional#includestring#includestring_viewnamespace{// 辅助函数1: 检查是否为注释行 (迭代1产生)constexprinlineboolis_comments(conststd::string_view sv){constcharis_comments_chsv.front();if(is_comments_ch;||is_comments_ch#){returntrue;}returnfalse;}// 辅助函数2: 检查位置是否在引号内 (迭代4产生)constexprinlineboolisPositionInsideQuotes(std::string_view line,size_t pos){boolin_doublefalse;boolin_singlefalse;for(size_t i0;iposiline.size();i){if(line[i]!in_single){boolescaped(i0line[i-1]\\);if(!escaped)in_double!in_double;}elseif(line[i]\!in_double){boolescaped(i0line[i-1]\\);if(!escaped)in_single!in_single;}}returnin_double||in_single;}// 辅助函数3: 移除引号并处理转义 (迭代5产生)std::stringunquoteAndUnescape(conststd::string_viewsv){constsize_t nsv.size();if(n2)returnstd::string{sv};constcharfirstsv.front();constcharlastsv.back();if(!((firstlast)||(first\last\)))returnstd::string{sv};std::string_view inner{sv.data()1,n-2};std::string out;out.reserve(inner.size());for(size_t i0;iinner.size();i){charcinner[i];if(c\\i1inner.size()){charnextinner[i1];switch(next){casen:out.push_back(\n);break;caset:out.push_back(\t);break;case\\:out.push_back(\\);break;case:out.push_back();break;case\:out.push_back(\);break;default:out.push_back(next);break;}i;}else{out.push_back(c);}}returnout;}}namespacecxx_utils::ini_parser{// 核心函数逐行解析boolIniParser::consume_line(conststd::string_viewline,std::stringcurrent_section){constautopreprocessed_linecxx_utils::string::trim_view(line);if(preprocessed_line.empty()){returntrue;}// 检查是否为注释行if(is_comments(preprocessed_line)){returntrue;}// 检查是否为sectionif(preprocessed_line.front()[){autoend_section_pospreprocessed_line.find(]);if(end_section_posstd::string::npos){returnfalse;// 没有闭合的]}autosection_svpreprocessed_line.substr(1,end_section_pos-1);section_svstring::trim_view(section_sv);// 检查]后面是否只有空白或注释if(end_section_pos1preprocessed_line.size()){for(size_t iend_section_pos1;ipreprocessed_line.size();i){constcharcpreprocessed_line[i];if((c;||c#)!isPositionInsideQuotes(preprocessed_line,i)){break;}if(!std::isspace(static_castunsignedchar(c))){returnfalse;}}}current_sectionsection_sv;if(m_data.find(current_section)m_data.end()){m_data.emplace(current_section,ini_section_t{});}returntrue;}// 查找行内注释的位置不在引号内size_t comment_posstd::string::npos;for(size_t i0;ipreprocessed_line.size();i){if((preprocessed_line[i];||preprocessed_line[i]#)!isPositionInsideQuotes(preprocessed_line,i)){comment_posi;break;}}constautowithout_comment(comment_posstd::string::npos)?preprocessed_line:preprocessed_line.substr(0,comment_pos);// 分割键值对constautosplited_kvstring::splits(without_comment,);if(splited_kv.size()2){returnfalse;// 没有等号}std::string key{string::trim_view(splited_kv[0])};// 提取值可能包含std::string value;{constautoposwithout_comment.find();if(posstd::string::npos)returnfalse;valueunquoteAndUnescape(string::trim_src(without_comment.substr(pos1)));}if(key.empty())returnfalse;m_data[current_section][key]value;returntrue;}// 解析整个INI文件boolIniParser::parse(conststd::string_view view){// 重置所有数据clear();m_data.emplace(,ini_section_t{});autolinescxx_utils::string::splits(view,\n);std::string current_section;for(constautol:lines){if(string::trim_view(l).empty())continue;consume_line(l,current_section);}returntrue;}// 获取配置值返回optionalstd::optionalstd::stringIniParser::get(conststd::stringsection,conststd::stringkey){autositm_data.find(section);if(sitm_data.end())returnstd::nullopt;autokitsit-second.find(key);if(kitsit-second.end())returnstd::nullopt;returnkit-second;}// 获取配置值带默认值std::stringIniParser::get(conststd::stringsection,conststd::stringkey,conststd::stringdefault_value){autositm_data.find(section);if(sitm_data.end())returndefault_value;autokitsit-second.find(key);if(kitsit-second.end())returndefault_value;returnkit-second;}// 检查键是否存在boolIniParser::has(conststd::stringsection,conststd::stringkey){autositm_data.find(section);if(sitm_data.end())returnfalse;returnsit-second.find(key)!sit-second.end();}}
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

上海高端网站制作站霸科技屏蔽wordpress更新

CSS动画极致优化:cubic-bezier缓动函数性能调优实战指南 【免费下载链接】easings.net Easing Functions Cheat Sheet 项目地址: https://gitcode.com/gh_mirrors/eas/easings.net 在追求极致用户体验的今天,CSS动画的性能优化已成为前端开发者的…

张小明 2026/1/1 13:05:40 网站建设

郑州大学科技园手机网站建设wordpress4.0 伪静态

目前越来越多的同学面临一个问题:AI率太高怎么降? 尤其是越来越多的学校发布公告对AIGC率作出要求,寻找好用的降AIGC方法和工具就成了我这段时间研究的问题。 现在降AI工具越来越多,从免费的到付费的,从低价的到高价…

张小明 2025/12/31 8:47:21 网站建设

本地服务类网站成本新河网站

第一章:AI模型Docker权限校验的核心挑战在将AI模型部署至生产环境时,Docker已成为主流的容器化方案。然而,容器内部的权限管理常被忽视,导致潜在的安全漏洞与运行时异常。特别是在涉及GPU访问、文件系统挂载和网络隔离的场景中&am…

张小明 2026/1/8 3:27:32 网站建设

网站开发需要多少钱推荐网站建设所需费用明细

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个AI驱动的软件卸载分析工具,要求:1. 使用机器学习算法分析软件安装路径和注册表项 2. 自动识别并标记潜在残留文件和关联项 3. 提供清理建议和安全评…

张小明 2026/1/6 1:12:49 网站建设

北京知名网站设计公司上海网站建设哪家快速上线

做项目的时候你可能会接触很多新技术,新设备,新功能包,新软件。不要担心,这些东西必定会有官方文档教程与github源码,与教学。 去官网找,去github搜索,他们公司肯定会出一些资料的

张小明 2025/12/22 22:17:21 网站建设

网站赏析案例石基网站建设

目录 正文 一、核心概念 1. 全局最小、局部最小、严格局部最小 2. 梯度 (1)矩阵的偏导数计算(梯度) (2)Hessian Matrix (3)方向导数与梯度 (4)可行方…

张小明 2025/12/22 22:16:20 网站建设