MFC集成SQLite实战:编码转换、事务处理与踩坑记录
简介一份面向MFC初学者的SQLite3集成示例工程演示在Visual Studio 2010对话框应用中实现数据库添加、删除、修改、查询并特别展示标准查询与基于回调函数的异步查询适合需要轻量级嵌入式数据库的Windows桌面项目参考。压缩包共38个文件约7.01MB包内头文件与C源文件对应功能实现exe可执行程序可直接运行查看效果dll与lib为SQLite3引擎组件db文件提供现成测试数据sln工程文件便于整体加载。已有1338人学习下载。示例工程目录结构直观从DBTool封装到DemoDlg界面交互均有清晰划分通过实际代码可掌握sqlite3_open、sqlite3_exec、sqlite3_close等核心API的调用以及回调函数处理查询结果、错误检查、资源释放等实用技巧对在MFC项目中集成SQLite3、实现CRUD与异步任务非常有帮助。1. 为什么在 MFC 里用 SQLite从配置到第一条 CRUD 的完整路径MFC 工程里嵌入 sqlite3 的例子我前后拆过好几轮。第一次做桌面工具时数据量不大但需要在本地保存几百条记录注册表太散Access 又要带驱动最后选了 sqlite3——单文件、零配置、纯 C 接口问题只有一个MFC 的 CString 是 UnicodeSQLite 要的是 UTF-8中间这层转换绕不开。这篇笔记就从一个能跑的 MFC sqlite3 例子讲起把工程接入、参数绑定、事务批量写入这些常规活儿怎么干说清楚再把容易翻车的地方列成踩坑记录。新手按步骤能搭起来熟手可以跳过前半段直接看第 4 章的坑和第 5 章的封装技巧。2. 把 SQLite 接进 MFC 工程编译、链接与连接管理2.1 源码编译 vs 预编译库我最终选了哪条路SQLite 分发时就是一堆源码网上能找到 sqlite3.c、sqlite3.h也会有人直接提供编译好的 dll lib。MFC 项目第一次接的时候很多人图省事直接拿预编译库结果后续 Debug/Release、x86/x64、字符集一混各种“玄学”问题就出来了。其实根源在于预编译库的运行时库和主工程不一致导致堆上分配的指针在另一方释放轻则崩溃重则内存越界。我一般会把 sqlite3.c 直接加进 MFC 工程编译。理由很直接sqlite3.c 和 sqlite3.h 两个文件放进目录编译时跟随工程的所有编译选项字符集、运行库、优化开关完全和主程序统一以后基本不用再担心 ABI 不一致。代价是第一次编译会多花几十秒但这比事后排查指针越界值多了。如果项目体型敏感也可以用 sqlite3.dll lib 的方式但记得把 dll 放到 exe 同目录并在工程里配置好以下内容。如果想让 SQLite 带扩展能力可以在工程预处理器里加 SQLITE_ENABLE_COLUMN_METADATA、SQLITE_ENABLE_FTS5、SQLITE_ENABLE_JSON1。加这些宏后 sqlite3.c 会编译得更慢但一个工程只编译一次没必要为了省这几秒钟丢功能。我手动写 SQL 时经常用 column_name所以至少会开 COLUMN_METADATA。另外用预编译库时要特别注意平台版本32 位工程配 32 位 sqlite3.dll64 位工程配 64 位 sqlite3.dll配置错了链接能通过运行时报“应用程序无法正常启动”这种坑比代码问题难找得多。2.2 工程配置头文件、附加依赖和字符集设置把 sqlite3.c 和 sqlite3.h 放进工程后具体步骤可以这样走在解决方案资源管理器里把这两个文件加入工程如果用的是旧版 VS直接拖到 Source Files 里。打开项目属性确认“配置属性 - 常规 - 字符集”选“使用 Unicode 字符集”。MFC 默认就是这样但如果从老工程迁移过来可能还是“未设置”这会直接导致 _T 宏和 CString 都按 ANSI 处理后面接口就会全部出错。如果你用的是预编译 lib在“链接器 - 输入 - 附加依赖项”里写入 sqlite3.lib如果像我们一样源码编译这一步省略。确认“C/C - 语言 - 符合模式”不是“否”。有的老工程为了兼容设成了否否则 sqlite3.c 里部分类型定义和声明会报警告甚至编译失败。头文件在 stdafx.h 里统一包含这样所有 cpp 都能用 sqlite3 接口// stdafx.h 片段 #pragma once #include afxwin.h #include afxext.h #include afxdisp.h #ifndef _AFX_NO_AFXCMN_SUPPORT #include afxcmn.h #endif // 加入 sqlite3 头文件注意目录层级 #include sqlite3/sqlite3.h // 如果使用的是预编译 lib则取消下面注释 // #pragma comment(lib, sqlite3.lib)这段代码的作用是把 SQLite 的 C 接口暴露给整个工程。#pragma comment(lib, ...)是一种简单的链接指令写在头文件里能让每个 cpp 在链接时自动带上依赖省得每次到属性页里改。使用动态库时记得把 sqlite3.dll 放在 exe 同级目录否则启动后会弹“找不到动态链接库”。这个错误最常见于换电脑跑程序直接把工程拷走dll 却只留在 Debug 目录里。2.3 用 RAII 封装连接句柄避免 C 接口裸奔sqlite3_open 返回的sqlite3*是原始指针MFC 程序里到处是分支 return一旦中间某个 CString 分配失败虽然概率低或者你忘了在某条路径上调用 sqlite3_close连接就泄漏了。多开几次窗口内存就上去了。我习惯写一个最小的 RAII 包装类把生命周期交给 C 的栈对象管理。// SQLiteConn.h #pragma once #include sqlite3/sqlite3.h class CSQLiteConn { public: CSQLiteConn() : m_db(nullptr) {} ~CSQLiteConn() { Close(); } // 打开数据库路径为宽字符函数内部转成 UTF-8 bool Open(const CString strDbPath) { Close(); // 先做路径转换后面 4.2 会详细说 int nLen WideCharToMultiByte(CP_UTF8, 0, strDbPath, -1, nullptr, 0, nullptr, nullptr); if (nLen 0) return false; std::string strPath(nLen, \0); WideCharToMultiByte(CP_UTF8, 0, strDbPath, -1, strPath.data(), nLen, nullptr, nullptr); int rc sqlite3_open(strPath.c_str(), m_db); return rc SQLITE_OK; } void Close() { if (m_db) { sqlite3_close(m_db); m_db nullptr; } } sqlite3* Handle() { return m_db; } bool IsOpen() const { return m_db ! nullptr; } private: sqlite3* m_db; // 禁止拷贝 CSQLiteConn(const CSQLiteConn) delete; CSQLiteConn operator(const CSQLiteConn) delete; };这里把拷贝构造和赋值都禁掉了防止两个对象持有一个连接然后二次 close。Open 内部做的 UTF-8 转换是 MFC SQLite 的第一个关键点CString 是 UTF-16而 sqlite3_open 只认 UTF-8 的char*。WideCharToMultiByte 需要先把目标长度算出来再分配缓冲。注意这里用std::string代替char[]避免手动释放。RAII 的好处是哪怕调用 Open 后函数发生异常CSQLiteConn 的析构函数也会执行 Close不会把连接扔在系统里。sqlite3_close 也可能返回 SQLITE_BUSY表示还有未释放的 stmt。所以真正的工程里Close 里最好循环处理void Close() { if (m_db) { // 先结束所有未完成的事务和 stmt while (sqlite3_close(m_db) SQLITE_BUSY) { // 这里可以 sleep 一下或者强制 finalize 已知 stmt } m_db nullptr; } }不过如果代码里每一处 prepare 都配对 finalize基本走不到这个分支。我加上这段主要是提醒sqlite3_close 失败时千万别直接判空要检查返回值否则连接依然挂着文件被占用后续删除数据库文件会失败。3. 核心操作查询、插入、更新与事务的 MFC 化写法3.1 封装一个轻量 CDatabaseHelper有了连接类还不够直接用 sqlite3_stmt 写业务代码会有一堆 sqlite3_prepare、step、finalize 散布在对话框代码里项目一复杂就难维护。我一般会再包一层薄的辅助类把常用的打开、执行、查询统一入口错误信息用一个 CString 带出来。下面是这个类的头文件。// DatabaseHelper.h #pragma once #include sqlite3/sqlite3.h #include vector #include map #include afx.h // 一行数据列名 - 值 typedef std::mapCString, CString RowData; typedef std::vectorRowData TableData; class CDatabaseHelper { public: CDatabaseHelper(); ~CDatabaseHelper(); bool Open(const CString strDbPath); void Close(); // 执行 INSERT / UPDATE / DELETE / CREATE 等无返回行的 SQL bool ExecuteNonQuery(const CString strSQL); // 执行 SELECT结果填充到 outTable bool ExecuteQuery(const CString strSQL, TableData outTable); CString GetLastError() const { return m_strError; } private: sqlite3* m_pDB; CString m_strError; };这个类没有把多线程同步放进来意图是每个线程自己持有实例属于最省心的线程模型。以后想加线程安全在这个壳里统一改比在业务代码里改容易得多。简单说数据库访问全部走这个类业务不直接碰 sqlite3 宏。辅助类的构造函数里把 m_pDB 置空析构函数里调用 Close这样即使哪条业务代码忘记释放栈对象析构时也能兜底。3.2 参数化 INSERT 和 SELECT为什么不要用 CString 拼 SQL很多例子程序喜欢这样写CString strSQL; strSQL.Format(_T(INSERT INTO user(name, age) VALUES(%s, %d)), strName, nAge); sqlite3_exec(m_pDB, CT2A(strSQL), nullptr, nullptr, nullptr);这是最直白但也是最容易出事的写法。第一是注入风险名字里只要出现单引号SQL 就坏了第二是性能每条 SQL 都要重新解析第三是和 SQLite 的 UTF-8 结合后转义更麻烦。我建议所有带变量的 SQL 都走参数绑定。下面是对应 INSERT 的封装写法bool CDatabaseHelper::InsertUser(const CString strName, int nAge) { sqlite3_stmt* pStmt nullptr; // 注意SQL 原文中的 ? 是占位符不是拼进去的值 CStringA strSQL INSERT INTO user(name, age) VALUES(?, ?); int rc sqlite3_prepare_v2(m_pDB, strSQL, -1, pStmt, nullptr); if (rc ! SQLITE_OK) { m_strError CA2TEXchar, CP_UTF8(sqlite3_errmsg(m_pDB)); return false; } // 将 CString 转成 UTF-8 后绑定 std::string strNameUtf8; if (!strName.IsEmpty()) { int nLen WideCharToMultiByte(CP_UTF8, 0, strName, -1, nullptr, 0, nullptr, nullptr); strNameUtf8.resize(nLen - 1); // 去掉结尾空字符 WideCharToMultiByte(CP_UTF8, 0, strName, -1, strNameUtf8.data(), nLen, nullptr, nullptr); } sqlite3_bind_text(pStmt, 1, strNameUtf8.c_str(), -1, SQLITE_TRANSIENT); sqlite3_bind_int(pStmt, 2, nAge); bool bOk (sqlite3_step(pStmt) SQLITE_DONE); if (!bOk) { m_strError CA2TEXchar, CP_UTF8(sqlite3_errmsg(m_pDB)); } sqlite3_finalize(pStmt); return bOk; }这段代码里几个参数值得单独说。sqlite3_prepare_v2的第四个参数是pStmt的地址第五个参数传 nullptr 表示不关心 SQL 解析到哪里结束。sqlite3_bind_text第二个参数是占位符序号从 1 开始这跟很多人从 0 开始的习惯不同。最后一个参数 SQLITE_TRANSIENT 告诉 SQLite如果后面还要用这块内存就先自己拷贝一份不要依赖我们传进去的 char* 存活。这一点非常重要——如果传 SQLITE_STATIC而我用的是局部 std::stringstep 时字符串已经析构了就会读出垃圾数据。SELECT 的封装更常见返回一个“列名 - 值”的 vector能直接喂给控件。下面这个函数把所有查询行都拉进内存只适合结果集不大的场景桌面工具完全够用bool CDatabaseHelper::ExecuteQuery(const CString strSQL, TableData outTable) { outTable.clear(); CStringA strSqlA CT2A(strSQL, CP_UTF8); sqlite3_stmt* pStmt nullptr; int rc sqlite3_prepare_v2(m_pDB, strSqlA, -1, pStmt, nullptr); if (rc ! SQLITE_OK) { m_strError CA2TEXchar, CP_UTF8(sqlite3_errmsg(m_pDB)); return false; } int nCols sqlite3_column_count(pStmt); while ((rc sqlite3_step(pStmt)) SQLITE_ROW) { RowData row; for (int i 0; i nCols; i) { const char* szName sqlite3_column_name(pStmt, i); const unsigned char* szText sqlite3_column_text(pStmt, i); CStringA strNameA(szName ? szName : ); // 这里先转 CStringA后面有讲乱码问题 CStringA strValA(szText ? reinterpret_castconst char*(szText) : ); row[CString(strNameA)] CString(strValA); } outTable.push_back(row); } if (rc ! SQLITE_DONE) { m_strError CA2TEXchar, CP_UTF8(sqlite3_errmsg(m_pDB)); sqlite3_finalize(pStmt); return false; } sqlite3_finalize(pStmt); return true; }这里有个很容易被忽略的细节CT2A(strSQL, CP_UTF8)将宽字符 SQL 转成 UTF-8 的 CStringA但 CStringA 默认按 ANSI 拷贝你必须显式指定 CP_UTF8 才正确。同样的道理从 sqlite3_column_text 拿回来的是 UTF-8 字节流如果直接赋给 CString不带 A 的MFC 会按当前代码页解释中文马上乱。第 4.4 节我会再专门演示一个可复用的转换函数。3.3 事务批量写入把基准从 3 分钟压到 3 秒如果一次性要插入一万条记录最差的写法是循环里每条sqlite3_exec(INSERT ...)——SQLite 默认每条 SQL 都隐含独立事务每条的提交都要做一次磁盘同步实测一万条能卡好几分钟。开启显式事务后同样是循环调用 InsertUser时间能降到一两个数量级以内。代码特别简单sqlite3_exec(m_pDB, BEGIN, nullptr, nullptr, nullptr); bool bFailed false; for (int i 0; i 10000; i) { CString strName; strName.Format(_T(user_%d), i); if (!InsertUser(strName, 20 i % 10)) { bFailed true; break; } } if (bFailed) { sqlite3_exec(m_pDB, ROLLBACK, nullptr, nullptr, nullptr); } else { sqlite3_exec(m_pDB, COMMIT, nullptr, nullptr, nullptr); }这里 COMMIT 一旦执行数据才算真正“落库”。中间某条失败调用 ROLLBACK前面的全部回滚不会留下半截数据。我习惯把 BEGIN/COMMIT/ROLLBACK 也封装到辅助类里因为业务代码有时会忘记配对调用一个BeginTransaction()/Commit()/Rollback()比裸写 sqlite3_exec 更容易配对。注意如果你每步都检查返回值可以发现 BEGIN 失败多半是磁盘权限问题而 COMMIT 失败通常意味着连接中断这种情况只能回滚重试。事务虽然好但别把一条一万行的循环和用户交互控件刷新也放进来界面会卡到让人怀疑程序假死。4. 避坑与排查MFC SQLite 开发中的 5 个真实踩坑记录下面这些坑都是我在具体项目里一条条踩出来的每一条都经历过“现象摆在那、原因查半天、解决就几行”的过程。把它们写在这里等于给自己留了一份自查单。4.1 现象Debug 程序在 sqlite3_close 时崩溃调试时发现程序退出时在 sqlite3_close 里报堆溢出但 Release 又好好的。原因我当初偷懒用了预编译的 Release 版 sqlite3.dll lib而 MFC 工程是 Debug 构建两边使用的 C 运行时库不同。sqlite3_close 里释放的内存可能是由 Debug CRT 分配的跨模块释放直接触发堆校验失败。解决如果你是这种情况要么换 Debug 版 sqlite3 库要么干脆把 sqlite3.c 源码加进工程一起编译从根上消除 ABI 不一致。我个人一劳永逸的做法是源码编译顺便还能自定义宏。4.2 现象数据库路径带中文打不开直接把CString传给 sqlite3_open很多时候收到的错误是“unable to open database file”。原因sqlite3_open 接收的路径是 UTF-8 编码的char*CString 在 Unicode 工程下是 UTF-16 的宽字符char* 强制转换后路径变成畸形的 ANSI自然找不到文件。解决入库前统一做一次宽字符到 UTF-8 的转换。参考实现std::string CStringToUtf8(const CString strW) { if (strW.IsEmpty()) return std::string(); int nLen WideCharToMultiByte(CP_UTF8, 0, strW, -1, nullptr, 0, nullptr, nullptr); std::string result(nLen, \0); WideCharToMultiByte(CP_UTF8, 0, strW, -1, result.data(), nLen, nullptr, nullptr); return result; }注意WideCharToMultiByte在目标长度传 -1 时会把结尾空字符也算进去所以返回的 std::string 里末尾会多一个\0。传给 sqlite3_open 没问题但如果你再把这份字符串拼接到别的 SQL 里会带上这个暗伤。上面的代码申请长度时保留了这个结尾也可以自己减 1两种都行只要心里有数。我在项目里统一用这个函数避免每次调用都重复写三行转换。4.3 现象多线程同时操作同一个连接偶发崩溃界面线程用一个连接后台线程又拿同一个 sqlite3* 去查询有时运行几小时才崩一次完全随机。原因sqlite3 默认的线程模式是 serialized即同一个连接最多同时进入一个线程操作但 MFC 程序里如果你没有加任何锁两个线程同时调用 step / finalize 就会在你自己的代码里产生数据竞争。SQLite 的锁只在内部保证不会写坏数据库文件但回调、绑定逻辑比如我们封装的 std::string 转换不在保护范围。解决最稳妥的方案是每个线程建立独立连接。数据库文件可以被多个连接同时打开SQLite 自己会用锁解决写冲突。如果非要共享连接必须自己在外面加临界区或者开连接前调用sqlite3_config(SQLITE_CONFIG_MULTITHREAD)并在每次访问时加互斥锁。我还是那句MFC 程序里别共享连接按线程建一个最省心。4.4 现象查询结果里的中文全部变成乱码绑定参数没问题但列值显示成“我是”这类字节串。原因sqlite3_column_text 返回的是 UTF-8 编码而 MFC 的 CString(strValA) 默认用本地代码页GBK解读。解决拿到const char*后不要直接构造 CString应该先用 MultiByteToWideChar 从 CP_UTF8 转成宽字符。下面是项目里通用的转换工具CString Utf8ToCString(const char* utf8) { if (!utf8) return CString(); int nLen MultiByteToWideChar(CP_UTF8, 0, utf8, -1, nullptr, 0); CString strW; wchar_t* pBuf strW.GetBufferSetLength(nLen - 1); MultiByteToWideChar(CP_UTF8, 0, utf8, -1, pBuf, nLen); strW.ReleaseBuffer(); return strW; }把ExecuteQuery里的CString(strValA)替换成Utf8ToCString(szText)中文就正常了。这个问题是 MFC sqlite3 最容易忽略的尤其项目里没有统一封装时每个对话框里各写各的一半乱码一半正常排查起来特别耗时间。我通常会把这个工具函数放在一个公共模块里所有取列值的代码都走它。4.5 现象Release 版本链接 sqlite3.lib 失败报 LNK2001Debug 链接正常切到 Release 后一堆 unresolved external symbol。原因大概率是工程属性里“附加依赖项”只在 Debug 配置里填了 sqlite3.lib或者你用的是 32 位库但 Release 平台是 x64。解决打开“配置管理器”分别看 Debug/Release、x86/x64 的平台设置。如果使用预编译库确保四个组合都有对应库如果源码编译则没有这个问题。另外检查头文件里#pragma comment(lib, sqlite3.lib)是否被#ifdef _DEBUG之类的条件编译挡住了。我见过有人因为 Release 下定义了 NDEBUG 导致这个 pragma 被排除链接器当然找不到库。建议把 pragma 放在头文件的最不起眼的角落但不要包在任何条件编译块里。5. 进阶把 SQLite 封装成可复用的 DAO 层再加一个重试与缓存机制5.1 设计一个轻量 DAO 抽象到了这一步工程里可能已经有十几个对话框直接调用 CDatabaseHelper业务逻辑和 SQL 混在一起。我通常会再抽一层 DAO把表操作收敛到独立类。界面层不再需要知道“这张表有哪几列”只要调用LoadAll()得到对象数组即可。下面是一个简化的基类模式class CBaseDAO { public: CBaseDAO() : m_pDB(nullptr) {} virtual ~CBaseDAO() {} void SetDatabase(sqlite3* pDB) { m_pDB pDB; } protected: sqlite3* m_pDB; CString Utf8ToCString(const char* utf8); // 复用上面转换 };派生类比如 CUserDAO 只需要实现bool LoadAll(vectorCUser users)和bool Save(const CUser user)内部把 bind/text 的转换细节全部处理掉。这样以后如果从 SQLite 换成别的本地库改动面只在这一层不会动界面代码。注意我的目的是收敛不是过度抽象——MFC 里搞三层接口加一堆工厂只会让维护的人想砸键盘。5.2 给连接设置 WAL 模式与 busy_timeout读写并发多的时候SQLite 默认的 journal 模式会频繁互斥。我习惯在每次 Open 成功后就执行几条 PRAGMAsqlite3_exec(m_pDB, PRAGMA journal_modeWAL;, nullptr, nullptr, nullptr); sqlite3_exec(m_pDB, PRAGMA busy_timeout3000;, nullptr, nullptr, nullptr); sqlite3_exec(m_pDB, PRAGMA synchronousNORMAL;, nullptr, nullptr, nullptr);WAL 模式允许读和写并行busy_timeout 告诉 SQLite 当数据库被别的连接锁住时最多等 3000 毫秒而不是立刻返回 SQLITE_BUSY。synchronousNORMAL 在 WAL 下能减少 fsync桌面场景掉电丢失一点点数据的概率可以接受换来的是写入性能提升。这些设置要放在表的创建之前否则已经建立的连接不会自动切换模式。WAL 模式是库级别的设置一次后同一个库文件后续连接默认延续但新文件每次还是要设置所以建议写成一个 InitDatabase() 函数统一调用。5.3 一个值得养成的习惯发布前跑一遍压力用例最后分享一个让我少挨骂的习惯。接手一个 MFC sqlite3 的模拟项目X时当时功能表面正常但连续操作几分钟后开始出怪毛病。后来我写了个临时对话框循环一万次插入、一万次查询、五千次更新和回滚同时用任务管理器盯内存和句柄数。果然第一次跑就发现内存涨了揪出来一个 SQLite stmt 忘了 finalize 的漏洞。从那以后我每次接手类似工程都强制自己先跑一遍压力用例开两个连接同时读写看 busy_timeout 是否生效把数据库文件复制到带中文的路径下再跑确认转换函数没漏。这套验证脚本留在项目里比文档管用得多。如果你也打算在 MFC 里长期用 sqlite3建议把第 4 章那五条坑提前画进自查单能少走我当年一半的弯路。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

ComfyUI v0.11.0实操:新模型接入与性能调优指南

ComfyUI v0.11.0实操:新模型接入与性能调优指南

玩 ComfyUI 的朋友应该都有同感:这工具就像一套乐高积木,模型、采样器、VAE、LORA 全是独立零件,怎么拼全看自己,但零件换得也快。每隔几周就要追一次新版本,这次 v0.11.0 的更新名单确实有点长——Zimage Omni、Anima…

2026/10/18 10:30:44 阅读更多 →
AI写论文靠全流程赋能:从选题到答辩的完整方法指南

AI写论文靠全流程赋能:从选题到答辩的完整方法指南

每年到毕业季,就会有一大批人到处问“AI写论文到底哪个软件好用”。说实话,这个问题在2024年下半年以来变得越来越难回答,因为工具迭代太快了,前两个月还算好用的方案,可能过一阵子就被竞品甩开。我自己因为工作关系&a…

2026/10/18 10:30:44 阅读更多 →
Vue路由配置全指南:动态权限、懒加载与工程实践

Vue路由配置全指南:动态权限、懒加载与工程实践

路由配置这活儿,看似谁都能写上两行,但真放到工程里,路由表怎么组织、权限怎么挂载、懒加载怎么切分、刷新为什么白屏,每一处都是选择题。我最早只是把路由当“页面路径登记表”用,后来在几个中后台项目里反复重构&…

2026/10/18 10:30:44 阅读更多 →

最新新闻

Claude Code + GitHub + WSL + ECS 四位一体 AI 开发架构:把 settings 改到 TaoToken

Claude Code + GitHub + WSL + ECS 四位一体 AI 开发架构:把 settings 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 17:41:43 阅读更多 →
察元 AI 文档助手 v4.0 全生命周期:从安装到 Claude 自动审查,把 MCP endpoint 改到 TaoToken

察元 AI 文档助手 v4.0 全生命周期:从安装到 Claude 自动审查,把 MCP endpoint 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 17:41:43 阅读更多 →
尼康ViewNX 2官方中文版安装配置与常见问题排查指南

尼康ViewNX 2官方中文版安装配置与常见问题排查指南

简介:ViewNX 2是尼康官方推出的中文免费版照片浏览与编辑软件,专为尼康数码相机用户设计,能够完成照片的导入、浏览、RAW格式白平衡与曝光调整,以及尺寸修改、裁切、矫正等常用润饰操作,同时支持查看符合Exif标准的JPE…

2026/10/18 17:41:43 阅读更多 →
FlowQuest操作手册高效使用指南:从入门到精通的仿真建模技巧

FlowQuest操作手册高效使用指南:从入门到精通的仿真建模技巧

简介:这份资源是FlowQuest系列水文测量软件的操作基础手册,面向从事河流流量监测的水文工作者、ADCP操作人员及水利项目技术人员,重点解决FQ300/600/1000等型号声学多普勒流速剖面仪在江苏省中小河流项目中的实际使用问题。手册系统梳理了从软…

2026/10/18 17:41:43 阅读更多 →
PDF单文件盖章工具:视觉盖章、内容流叠加与参数配置实战

PDF单文件盖章工具:视觉盖章、内容流叠加与参数配置实战

简介:这是一款面向电子签署与文件盖章场景的PDF单文件处理工具,旨在解决用户在PDF文档上快速、精准加盖电子印章的痛点,适用于商务合同、法律文书、行政审批等需要频繁用印的岗位。压缩包采用7z格式,共包含13个文件,主…

2026/10/18 17:41:43 阅读更多 →
AI辅助开发:如何同步提升程序员的创造力与效率

AI辅助开发:如何同步提升程序员的创造力与效率

接手一个需求,产品那边只说了一句“要做一个能根据用户行为自动打标签的模块”,需求文档就两页纸,核心流程写了三行,剩下的全靠猜。放在以前,这种需求我至少要卡三天——一天梳理逻辑,两天写代码&#xff0…

2026/10/18 17:40:42 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →