简介面向仍需维护 Visual C 6.0 老项目的 C 开发者这套资源提供 jsoncpp 源码在 VC6.0 环境下编译通过的完整调用案例重点解决解析与序列化中文字符时的乱码问题属于可直接落地的工程级示例。压缩包共 72 个文件、约 3.77MB以 24 个头文件、9 个 cpp 源文件和 6 个 inl 内联实现构成 jsoncpp 0.5.0 的核心读写模块另附 dsp、dsw 工程文件与已编译的 exe 程序方便打开后直接对照运行doc 使用说明与 txt 注意事项记录了工程配置、源码加载顺序和编码处理的关键细节。目前已有 591 人学习下载。资源内的对话框与控制台示例完整演示了从解析字符串、用 json::value 承载节点到序列化输出的链路并给出统一按 UTF-8 读写以规避中文乱码的改造思路可帮助读者避开源码集成、链接报错与中文截断等常见坑适合作为老项目接入 JSON 数据交换的参考模板。1. 老工控机上的 JSON 需求VC6.0 接 jsoncpp 这件事的定位接手一套跑在产线工控机上的 VC6.0 老程序接口方要求把上报报文从竖线分隔改成标准 JSON还要带中文设备名——这就是 jsoncpp 在这个年代仍被搜出来的真实场景。这类工程的约束很硬编译器不能换、运行库不能换、机器上装着别的老组件唯一能动的就是把 jsoncpp 这种纯 C 库塞进去然后让中文字符串在 GBK 和 UTF-8 之间来去自如。这份资源解决的正是这最后一公里一份能在 VC6 下直接编译通过的 jsoncpp 工程、一组跑得通的解析与序列化示例外加一套不靠 C11 的编码转换封装。适合两类人手里有 VC6 存量代码、必须原地加 JSON 能力的维护者以及想弄明白老编译器到底卡在哪些语法点上的开发者。2. 版本选型与工程搭建为什么 jsoncpp 0.5.0 是 VC6 的甜点版本2.1 从 1.x 往回翻源码里的 C11 依赖清单很多人第一步就翻车从仓库拉最新 tag把源码丢进 VC6编译按钮按下去报错刷满屏幕然后开始怀疑是不是自己的工程配置有问题。问题不在配置在版本。jsoncpp 从 1.x 开始全面转向 C11源码里到处是std::unique_ptr、std::make_unique、nullptr、override、范围 for、std::to_string这些东西 VC6 一个都不认识而且不是加个宏就能绕过去的——它们渗透在类型定义和成员布局里。把版本往下捋能看出每个版本的门槛版本区间关键变化VC6 可行性1.xCharReaderBuilder/StreamWriterBuilder全量 C11不可行0.10.x引入CharReader/StreamWriter抽象仍用 C03需较多改造0.6.0std::auto_ptr移除模板用法加重改造量大0.5.0Reader/Writer老三件套纯 C98 语法基本可用少量补丁0.5.0 是这条线上最后一个对老编译器友好的版本。它的核心 API 就三样Json::Reader负责把字符串解析成Json::ValueJson::Value是通用容器同时能当对象、数组、标量用Json::Writer及其子类FastWriter、StyledWriter负责把Value写回字符串。这套接口没有智能指针没有移动语义没有std::function正好落在 VC6 的能力范围内。提示选版本这一步省下来的时间比后面所有调试加起来都多。别在 1.x 上做移植梦。2.2 工程落地静态库工程、头文件布局与运行时库对齐确定版本之后工程结构要一次性摆对。我的习惯是把它编成静态库主工程只依赖.lib和头文件目录这样业务代码不会因为 jsoncpp 内部的一堆.inl被重复编译也不会出现符号冲突。目录我一般这么摆ThirdParty/ jsoncpp/ include/ json/ json.h value.h reader.h writer.h forwards.h config.h autolink.h features.h src/ json_value.cpp json_reader.cpp json_writer.cpp lib/ json_vc6.lib (Debug 用 json_vc6d.lib)新建工程时选Win32 Static Library不要选 MFC AppWizard。加入src下三个.cpp把include目录写进Project Settings - C/C - Preprocessor - Additional include directories。有三个细节决定了后面会不会崩第一json_valueiterator.inl、json_internalmap.inl这类.inl文件不要手动加进工程它们是被json_value.cpp内部#include的重复加入会出现符号重复定义。第二运行时库必须和主工程一致。静态库工程用Project Settings - C/C - Code Generation里的/MTRelease或/MTdDebug主工程也得是同一个。一边/MT一边/MD轻则LNK2005 already defined重则跨 CRT 堆释放直接崩而且是那种跑十分钟才崩一次的玄学崩溃。第三输出目录写死到lib/下Debug 和 Release 用不同文件名。VC6 的默认输出路径是Debug/主工程一改配置就找不到库报LNK1104 cannot open file。// Precompiled.h —— 静态库工程里不需要但主工程里建议统一入口 #pragma once // VC6 的 STL 会产生大量 4786 警告标识符被截断屏蔽掉 #pragma warning(disable: 4786) // 4251dll 接口导出 STL 成员静态库场景下无意义 #pragma warning(disable: 4251) // 4244类型收窄jsoncpp 里有几处 int/size_t 互转 #pragma warning(disable: 4244) #include string #include vector #include map这段预处理指令的作用不是让代码变干净而是让真正的错误不被淹没。VC6 编译 jsoncpp 一次能刷出两三百条警告其中 4786 占八成全是模板实例化后符号名超长的提示噪音。把已知噪音关掉剩下的才是有价值的信号。3. 编译期改造把 jsoncpp 源码按 VC6 的 STL 脾气改顺3.1 五类高频编译错误的定位与修法即便选了 0.5.0VC6 也不会让你一次过。它的 STL 是上世纪的 Dinkumware 实现std::string是写时复制COW的容器的erase返回void而不是迭代器limits里的numeric_limits只有残缺的几项。下面这五类错误按出现频率排序基本覆盖了 95% 的编译中断。第一类嵌套模板的右尖括号。VC6 的词法分析器会把当成右移运算符std::vectorstd::string这种写法直接语法错误。现象是error C2143: syntax error : missing ; before 。解决方式是在两个之间留空格std::vectorstd::string 。整个 jsoncpp 源码里凡是std::mapCZString, Value之外的嵌套容器声明都要手工过一遍。第二类numeric_limits缺失。源码里如果有std::numeric_limitsdouble::max()之类的调用VC6 会报error C2039: max : is not a member of std::numeric_limitsdouble。修法是直接在json/config.h里加一段特化把需要的那几个值写死// json/config.h 末尾追加VC6 的 limits 不完整手动补两个常用的 #if defined(_MSC_VER) _MSC_VER 1300 namespace std { template class numeric_limitsdouble { public: static double max() { return 1.7976931348623157e308; } static double min() { return 2.2250738585072014e-308; } static double epsilon() { return 2.2204460492503131e-016; } }; } #endif_MSC_VER 1300这个判断把改动圈死在 VC61200和 VC71300之间后面的编译器不受影响。这是我改老库时的固定习惯所有兼容补丁都挂版本宏不然将来换编译器会踩到另一批坑。第三类snprintf不存在。jsoncpp 在数值转字符串时用了snprintfVC6 只提供_snprintf而且这个函数在长度刚好时不写结尾\0行为跟标准版不一样。最省事的做法是在config.h里做一次宏映射// VC6 没有 snprintf映射到 _snprintf但要注意它不保证结尾 \0 #if defined(_MSC_VER) _MSC_VER 1300 # include stdio.h # define snprintf _snprintf #endif改完之后所有调用snprintf的地方都要保证缓冲区比实际需要的多留一个字节否则序列化出来的字符串偶尔会在末尾多出一个垃圾字符——这个 bug 极难查因为你看到的往往是JSON 解析在对方那里莫名失败。第四类成员模板特化不被接受。VC6 不支持在类模板内部做成员模板的全特化报error C2954: cannot use template to specialize member of template class。jsoncpp 里Json::Value的几处类型转换辅助函数会撞上这条。处理方式是把它从特化改写成重载语义完全一致只是写法退回到 C98 早期风格。第五类内部编译器错误 C1001。这是最不讲道理的一种。VC6 的模板实例化栈很浅嵌套超过一定层数就fatal error C1001: INTERNAL COMPILER ERROR而且报错位置指向的文件往往跟真正原因差得很远。遇到它先做减法把json_value.cpp里不用的部分注释掉比如不用的Value::iterator相关模板一行一行缩范围直到编译器缓过来。3.2 生成 json_vc6.lib 并做一次自检全部错误清零之后按F7生成静态库。VC6 不会提示成功只会在 Build 窗口最后打印json_vc6.lib - 0 error(s), 0 warning(s)。这时候别急着接业务代码先写一个二十行的自检程序验证三件事解析、构造、序列化。// SelfTest.cpp : 静态库自检编成控制台程序链接 json_vc6.lib #include json/json.h #include stdio.h #include string int main() { // 1. 解析用一段最简单的 UTF-8 字节序列 const char* text {\id\:7,\name\:\pump\}; Json::Reader reader; Json::Value root; if (!reader.parse(text, root, false)) { // 解析失败时 getFormattedErrorMessages 会带行列号 printf(parse failed: %s\n, reader.getFormattedErrorMessages().c_str()); return 1; } // 2. 构造往已有节点上挂新字段 root[enable] true; root[level] 3; // 3. 序列化FastWriter 输出紧凑格式 Json::FastWriter writer; std::string out writer.write(root); printf(%s, out.c_str()); // 4. 回读校验确认往返一致 Json::Value back; if (!reader.parse(out, back, false)) return 2; if (back[id].asInt() ! 7) return 3; if (!back[enable].asBool()) return 4; printf(selftest ok\n); return 0; }reader.parse的第三个参数collectComments建议固定传false。传true时解析器会把//和/* */注释收集进Value的内部注释表虽然功能上无害但StyledWriter再写出来的时候会把注释一起带上对方接口如果严格校验 JSON 格式会直接拒收。writer.write(root)返回的字符串末尾带一个\n这是 0.5.0 的固定行为不是 bug。自检程序里printf(%s, ...)看着正常但真拿去做 HTTP body 时某些服务端的长度校验会因为这个换行差一字节。自检跑通再往下走。这一步花十分钟能省掉后面在 MFC 对话框里排查链接错误的两小时。4. 核心接口实战解析、构造、序列化三段链路4.1 Reader / Value / Writer 的最小闭环jsoncpp 的 API 设计很朴素一个解析器对象、一个值对象、一个写出器对象三者之间靠std::string传递。理解Json::Value的多态语义是第一步——它同时是对象、数组和标量具体行为由你调用的成员函数决定。// 构造一个带数组和嵌套对象的报文 Json::Value root; // 对象场景直接下标赋值key 不存在会自动创建 root[deviceId] 1001; root[online] true; root[temperature] 36.5; // 数组场景先取引用再 append Json::Value channels root[channels]; channels.append(1); channels.append(2); channels.append(3); // 嵌套对象逐层下标 root[meta][version] 1.0; root[meta][ts] 1700000000; Json::StyledWriter writer; std::string pretty writer.write(root);root[channels]返回的是Json::Value必须用引用接住再append。如果写成Json::Value channels root[channels];拿到的是副本append只改副本root里那个数组永远是空的——这个坑后面单独说。StyledWriter输出带缩进和换行适合落盘做配置文件、适合人眼调试FastWriter输出紧凑单行适合走网络。两者序列化出来的字节内容语义等价只是空白不同。解析方向同样简单但asXxx系列函数的默认值行为要留意// 解析一段接口返回的报文 Json::Reader reader; Json::Value resp; if (!reader.parse(body, resp, false)) { // 记录错误信息parse 失败时 resp 内容未定义不要读它 LogError(reader.getFormattedErrorMessages()); return false; } // 逐字段取注意类型 int id resp[deviceId].asInt(); // 不存在时返回 0 bool on resp[online].asBool(); // 不存在时返回 false double temp resp[temperature].asDouble(); // 不存在时返回 0.0 std::string ver resp[meta][version].asString(); // 数组遍历size() 下标 const Json::Value chs resp[channels]; for (int i 0; i (int)chs.size(); i) { int ch chs[i].asInt(); }asInt()在字段不存在或者类型不匹配时不会抛异常直接返回类型零值。这既是优点也是陷阱接口少发一个字段你的业务代码拿到的就是 0不会报错可能要把 0 当成合法值处理到很后面才发现。稳妥的做法是对关键字段先isMember()判断再取值。4.2 中文防乱码GBK 与 UTF-8 的双向桥这是这个资源真正的价值点也是所有 VC6 接 JSON 的项目必踩的一脚。根源在编码模型不一致。JSON 规范要求文本是 UTF-8或 UTF-16/32实际网络交互一律按 UTF-8 处理而中文版 Windows 的 ANSI 代码页是 936也就是 GBK。VC6 源文件里写下的一号泵这个字面量在编译后的二进制里是 GBK 字节\xD2\xBB\xBA\xC5\xB1\xC3。你把它直接塞进Json::Valuejsoncpp 不会做任何校验原样拷贝序列化出来就是一段非 UTF-8 的字节流。对方按 UTF-8 解码得到的是一串问号或者乱码方块。反方向同理。对方发来 UTF-8 的\xE4\xB8\x80\xE5\x8F\xB7\xE6\xB3\xB5jsoncpp 解析后asString()返回的还是这串 UTF-8 字节你直接扔给 MFC 的SetDlgItemText或printf在 GBK 环境下显示出来就是乱码。正确的模型是内部统一 UTF-8边界统一转换Json::Value里存的永远是 UTF-8只在读文件、写文件、刷界面的地方做一次转换。转换不要用setlocalewcstombs进程级 locale 会影响其他地方直接用 Win32 的两步转换稳定可控。// encoding.h : 不依赖 C11 的 GBK - UTF-8 转换 #pragma once #include windows.h #include string #include vector // GBK(CP_ACP) - UTF-8 inline std::string GbkToUtf8(const std::string gbk) { if (gbk.empty()) return std::string(); // 第一步多字节 - 宽字符。长度传显式 size不要传 -1 // 因为 JSON 里可能含有 \u0000 转义出的空字符传 -1 会被截断 int wlen MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), NULL, 0); if (wlen 0) return std::string(); // 用 vectorwchar_t 而不是 std::wstringVC6 的 wstring 有多处实现缺陷 std::vectorwchar_t wbuf(wlen); MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), wbuf[0], wlen); // 第二步宽字符 - UTF-8 int ulen WideCharToMultiByte(CP_UTF8, 0, wbuf[0], wlen, NULL, 0, NULL, NULL); if (ulen 0) return std::string(); std::vectorchar ubuf(ulen); WideCharToMultiByte(CP_UTF8, 0, wbuf[0], wlen, ubuf[0], ulen, NULL, NULL); return std::string(ubuf[0], ulen); }这段代码有两个参数值得说清。MultiByteToWideChar的第二个参数是标志位传0表示不做特殊处理如果输入里含有非法字节序列函数会失败并让GetLastError()返回ERROR_NO_UNICODE_TRANSLATION这其实是判断上游给的到底是不是 GBK的好线索。WideCharToMultiByte的后四个参数在 UTF-8 场景下传NULL, NULL即可那两个是给 GB18030 之类的有状态编码用的。反方向的函数结构完全对称只把两个代码页对调// UTF-8 - GBK(CP_ACP)用于把 JSON 里的中文显示到 MFC 控件 inline std::string Utf8ToGbk(const std::string utf8) { if (utf8.empty()) return std::string(); int wlen MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), NULL, 0); if (wlen 0) return std::string(); std::vectorwchar_t wbuf(wlen); MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), wbuf[0], wlen); int glen WideCharToMultiByte(CP_ACP, 0, wbuf[0], wlen, NULL, 0, NULL, NULL); if (glen 0) return std::string(); std::vectorchar gbuf(glen); WideCharToMultiByte(CP_ACP, 0, wbuf[0], wlen, gbuf[0], glen, NULL, NULL); return std::string(gbuf[0], glen); }两个函数都用了std::vectorchar做中间缓冲而不是std::string的s[0]。原因是 VC6 的std::string是写时复制实现直接对它的内部缓冲区写入会牵扯到引用计数的处理路径在跨线程场景下有过崩溃记录。绕开它代价是拷贝一次收益是再也不用担心这一层。还有一个必须知道的点jsoncpp 解析\u4e2d\u6587这种转义序列时内部会先把码点还原成 UTF-8 字节再存进std::string。也就是说从解析到序列化整个链路里只要不做转换UTF-8 是一直保持的不会因为你中间取了一次asString()就变回 GBK。理解这一点才能判断该在哪一层做转换。4.3 封装一层 CJsonHelper把编码转换藏进接口里业务代码里到处写GbkToUtf8(...)会很难看而且容易漏。我的做法是抽一个薄封装让调用方只跟 GBK 打交道UTF-8 完全关在内部。// JsonHelper.h : 面向业务的 JSON 门面外部只传 GBK 字符串 #pragma once #include json/json.h #include string class CJsonHelper { public: // 从文件读入并解析文件本身是 GBK 编码的 bool LoadFromFile(const std::string gbkPath); // 序列化并写回文件写出去的是 GBK方便用记事本直接看 bool SaveToFile(const std::string gbkPath) const; // 业务侧取值key 和返回值都是 GBK std::string GetString(const char* key) const; void SetString(const char* key, const std::string gbkValue); int GetInt(const char* key) const; void SetInt(const char* key, int value); Json::Value Root() { return m_root; } // 需要复杂操作时直接暴露 private: Json::Value m_root; };实现里最关键的三个函数// JsonHelper.cpp #include JsonHelper.h #include encoding.h #include stdio.h std::string CJsonHelper::GetString(const char* key) const { // 内部存的是 UTF-8出口统一转回 GBK std::string utf8 m_root[key].asString(); return Utf8ToGbk(utf8); } void CJsonHelper::SetString(const char* key, const std::string gbkValue) { // 入口统一转 UTF-8保证 m_root 里永远只有 UTF-8 m_root[key] GbkToUtf8(gbkValue); } bool CJsonHelper::LoadFromFile(const std::string gbkPath) { FILE* fp fopen(gbkPath.c_str(), rb); if (fp NULL) return false; std::string raw; char buf[4096]; size_t n 0; while ((n fread(buf, 1, sizeof(buf), fp)) 0) raw.append(buf, n); fclose(fp); // 文件是 GBK先转 UTF-8 再交给 jsoncpp std::string utf8 GbkToUtf8(raw); Json::Reader reader; return reader.parse(utf8, m_root, false); }SetString和GetString这一进一出把编码边界钉死在了CJsonHelper这一层。业务代码写的helper.SetString(name, 一号泵)里那个字面量是 GBK传进去自动转 UTF-8helper.GetString(name)拿回来的也是 GBK可以直接喂给CString。中间无论经过多少次序列化、反序列化m_root里的字节始终是 UTF-8不会出现转一次没事、转两次乱码的雪崩。LoadFromFile里用fopen(..., rb)而不是r是因为 Windows 的文本模式会把\r\n转成\nJSON 字符串里的\r转义在某些边界情况下会被误伤。二进制模式读进来最安全反正后面还要整段解析。5. 避坑与排查五个在真机上翻过车的现场5.1 界面显示正常对方收到乱码现象自测时用AfxMessageBox弹出解析出来的中文显示完全正常但一发给接口方对方收到的是一堆?或者方块。原因AfxMessageBox接收的是 GBK 的CString你在弹出前做了一次Utf8ToGbk所以本地看着对。但发出去的那份std::string是直接从m_root里取的 UTF-8 字节中间没有经过 UTF-8 的序列化步骤而对方按 GBK 或者按错误的编码解码了。解决在发送链路的出口统一打印一次十六进制。一个中文字符的 UTF-8 是三个字节E4 B8 80GBK 是两个字节D2 BB。看一眼字节数就知道当前这段到底是哪种编码比盯着乱码猜快十倍。5.2 改了字段序列化出来还是旧值现象Json::Value v root[device]; v[name] x;之后序列化rootdevice.name没变。原因Json::Value的拷贝构造是深拷贝。Json::Value v root[device]拿到的是一个独立副本改它跟root没有关系。operator[]返回引用但赋值号左边一旦用了值类型引用就丢了。解决养成用引用的习惯。Json::Value v root[device];才是操作原节点的正确姿势。这条规则对append、removeMember、resize全部适用。5.3 数组下标越界不报错悄悄扩容现象读一个只有三个元素的数组访问arr[5]没崩取出来是null写root[list][10] 1序列化后前面十个元素全是null。原因Json::Value::operator[](ArrayIndex)在索引越界时会自动把数组扩容到index1中间填null。这是设计行为不是 bug。读方向同样返回一个null值而不是抛异常。解决遍历数组一律用size()做上界不要在业务代码里硬编码下标。如果是必须按下标取的场景先判断index (int)arr.size()再取。5.4 大整数精度丢失现象设备号是9007199254740993这种超过 2^53 的值经过 jsoncpp 往返一次回来变成了9007199254740992。原因jsoncpp 在不开启 64 位整数支持时所有数字都按double存。double的尾数是 52 位超过 2^53 的整数无法精确表示。解决在json/config.h里确认JSON_HAS_INT64被定义VC6 用__int64解析时用asInt64()取值。打印时注意 VC6 的printf不认%lld得用%I64d。如果设备号本身是纯标识符、不参与运算更省事的做法是让上游把它发成字符串。5.5 LNK2005 和运行十分钟才崩一次的怪问题现象编译链接阶段报LNK2005: _malloc already defined或者链接过了但程序跑一段时间在delete或free时崩。原因静态库和主工程的 C 运行时库不一致。jsoncpp 用/MT静态链接 CRT主工程用/MD动态链接 CRT一个进程里出现了两套堆管理器。内存在 A 堆申请、在 B 堆释放崩溃点随机。解决把两边工程Project Settings - C/C - Code Generation - Use run-time library设成完全一致的值。Debug 全用Debug MultithreadedRelease 全用Multithreaded。改完之后 Clean 一次再全量重编别用增量编译符号表可能残留旧的。6. 二次开发把 jsoncpp 收进自家工程的隔离与替换思路真要在存量项目里长期用别让 jsoncpp 的头文件散落在业务代码里。我的做法是加一层门面业务代码只#include JsonHelper.hjson/json.h只出现在JsonHelper.cpp和encoding.h里。这么做的直接好处是将来某天领导决定换 RapidJSON 或者升级到新版 jsoncpp改动范围收敛在三个文件内不至于翻遍整个工程。隔离之后还需要一套自检机制。老工程的痛点是改一处、崩一片尤其是编码相关的改动本地测着好部署到另一台机器上就乱。我通常会在工程里保留一个隐藏的调试入口做编码往返测试// 编码往返自测任何一次编码层改动后都跑一遍 bool EncodingSelfCheck() { const char* samples[] { 一号泵, // 纯中文 Pump-1号, // 中英混排 设备主控, // 含全角标点 // 空串边界 }; for (int i 0; i 4; i) { std::string gbk samples[i]; std::string utf8 GbkToUtf8(gbk); std::string back Utf8ToGbk(utf8); if (back ! gbk) { // 打印十六进制差异比看乱码直观 return false; } // 再过一遍 JSON 序列化确认 jsoncpp 没在中途做手脚 Json::Value v; v[k] utf8; Json::FastWriter w; std::string doc w.write(v); Json::Value r; Json::Reader rd; if (!rd.parse(doc, r, false)) return false; if (Utf8ToGbk(r[k].asString()) ! gbk) return false; } return true; }四组样本覆盖了常见的中文边界纯中文验证基本转换中英混排验证 ASCII 不会被误处理全角标点验证 GBK 的双字节区间空串验证长度为零时不走 API。这个函数跑通的成本是毫秒级但它能拦住的全是那种上线后才被发现、然后要花一整天定位的问题。再往前一步可以把配置读写、报文收发、日志输出这三条链路的编码入口收拢成统一宏链路入口内部编码出口处理配置文件读取LoadFromFileUTF-8读入时 GBK→UTF-8网络报文发送FastWriter::writeUTF-8不转换直接发日志落盘Utf8ToGbk后写GBK落盘前拷一份界面显示GetStringUTF-8→GBK控件直接用这张表贴在代码注释里比任何文档都管用因为后来接手的人只要照着它对齐方向就不会出现日志里的中文是乱的但界面是对的这类分裂问题。还有一个容易忽略的改造点把Json::FastWriter的末尾换行去掉。0.5.0 写出来永远多一个\nHTTP body 里多一个字节有的服务端会因为这个字节判定长度不符。我在CJsonHelper::SaveToFile和发送函数里统一做一次while (!s.empty() (s[s.size()-1] \n || s[s.size()-1] \r)) s.erase(s.size()-1);一次写、到处干净。从那以后我每次接老工程第一件事都是先写那个二十行的编码往返自测跑通了再动别的——因为编码这类问题一旦跨过编译期溜进运行期就成了黑匣子后悔药没处买。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
AI芯片竞争升级:从堆算力到系统工程的全栈优化 1. 峰会现场最值得关注的三个信号 这届上海AI芯片峰会,我一共泡在现场三天,听了几十场分享,跟一线的架构师、流片团队、封装供应商甚至几位投资人聊了一圈。先给没来现场的朋友剧透一个核心判断:AI芯片的叙事已经从"谁能训出… · 2026/11/2 20:00:30
新造一颗AI芯片:从场景定义到软硬协同的完整思考 最近不少朋友在聊AI芯片,有的是做算法的想往硬件摸一摸,有的是创业团队想找新方向,也有的是投资背景的人想搞清楚"一颗AI芯片到底难在哪"。我自己在芯片和AI交叉这块做过几年,被问得最多的一个问题其实是:如… · 2026/11/2 20:00:30
dbswitch数据库迁移与Docker容器化部署实战 数据库迁移这件事,难的从来不是“把数据搬过去”这个动作,而是搬完之后结构对不对、数据准不准、出了问题能不能三分钟内定位到是哪张表哪一列。dbswitch 这个工具我用了不短时间,最早它还是个只支持少数几种库互转的小工具,现在结… · 2026/11/2 20:00:30
如何读懂 Unity C 参考源码:从 .bindings.cs 到引擎桥接的完整指南 如何读懂 Unity C# 参考源码:从 .bindings.cs 到引擎桥接的完整指南 【免费下载链接】UnityCsReference Unity C# reference source code. 项目地址: https://gitcode.com/gh_mirrors/un/UnityCsReference
UnityCsReference 是 Unity 6(版本 6000… · 2026/11/3 2:28:24
RLHF 之父下场宣战:Diogo Almeida 用 Jev 宣告『AI 不只是会说话』 RLHF 之父下场宣战:Diogo Almeida 用 Jev 宣告『AI 不只是会说话』
一个只会输出「是/否」与 0-100 分、从不生成一个自然语言字符的模型,正在改写智能体的运行方式。2026 年第三季度,社区与媒体冠以「RLHF 之父」之称的 Diogo Almeida 发布… · 2026/11/3 2:28:24
本地私有化三连:Nimble、tev1 怎么选、怎么跑、怎么验收置信度 本地私有化三连:Nimble、tev1 怎么选、怎么跑、怎么验收置信度
2026 年 9 月下旬,一类"只做判断、不写作文"的决策模型(System 1 风格模型)密集落地:先是 Jev 以"快 193.6 倍、输入 0.042 美元/百万 t… · 2026/11/3 2:28:24
echarts地图开发经验分享 一、前言
最近工作中,客户要求使用地图展示省-市-县区三级业务关系,并且可实现地图三级钻取。过程中遇到了以下几个问题,在此经验分享,希望大家少走弯路。
1.业务地图与实际行政区域关系不完全一致。 2.不同权限的人进入到系统后… · 2026/11/3 2:28:24
3 步反向图片搜索 + 元数据提取:OSINT-Framework 图像情报分析完整指南 3 步反向图片搜索 元数据提取:OSINT-Framework 图像情报分析完整指南 【免费下载链接】OSINT-Framework OSINT Framework 项目地址: https://gitcode.com/gh_mirrors/os/OSINT-Framework
手里有一张来路不明的图,想弄清它最早出现在哪个网站、大… · 2026/11/3 2:28:24
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18