C语言调用C++代码实战:ABI兼容、接口设计与工程实践
1. 项目概述与核心挑战最近在重构一个老旧的嵌入式系统时我遇到了一个非常典型但又有点棘手的问题一个核心的、用纯C语言编写的驱动模块需要调用另一个新开发的、用C编写的算法库中的某些功能。这听起来像是“一个C文件调用另一个C文件”的简单扩展但实际上一脚踩进了C与C语言互操作的深水区。如果你也在为类似的问题头疼比如想让你的C程序调用一个用C封装的机器学习模型类或者让一个C语言的老项目集成新的C日志组件那么这篇从实战中总结出来的经验或许能帮你省下不少折腾的时间。这个问题的本质是跨越两种不同编程语言“世界观”的桥梁搭建。C语言是过程式的简单直接函数名在编译后基本保持不变而C是面向对象的支持类、重载、异常等复杂特性编译器会对函数名进行“名字修饰”Name Mangling导致链接时根本找不到符号。直接让C调用C的类成员函数就像让一个只懂方言的人去理解一门充满语法变体和隐式规则的外语几乎不可能直接沟通。因此我们的核心任务就是在C这一侧建造一个C语言能理解的“外交接口”让两者能够安全、高效地对话。2. 为什么必须通过C接口进行桥接在动手之前我们必须彻底理解为什么“用C接口包装C”是行业内的标准做法而不是去硬碰硬地让C直接链接C库。2.1 ABI的稳定性与复杂性差异ABIApplication Binary Interface可以理解为函数在二进制层面的“呼叫约定”。它规定了参数如何传递放在寄存器还是栈里、栈由谁清理、返回值放在哪里、数据结构在内存中如何布局等底层细节。C语言的ABI极其简单和稳定几乎成为了不同编译器、不同操作系统乃至不同编程语言之间交互的“通用语”。无论是GCC、Clang还是MSVC对于基本的C函数调用其ABI规则都高度一致。而C的ABI则复杂得多。为了实现函数重载、命名空间、类成员函数、虚函数、异常处理、RTTI运行时类型识别等高级特性编译器会对函数名进行复杂的修饰。例如一个简单的void MyClass::foo(int)函数在GCC编译后其符号可能变成_ZN7MyClass3fooEi。这个符号对于链接器来说是唯一的但对于我们人类或者其他语言来说几乎是不可读且不可预测的。更糟糕的是不同编译器甚至同一编译器的不同版本其名字修饰规则都可能不同。这意味着即使用C编译了一个动态库换一个编译器环境来链接很大概率会因符号找不到而失败。实操心得曾经在一个跨平台项目里用MSVC编译的C库拿到Linux下用GCC链接光是处理std::string等STL类型在不同编译器ABI下的不兼容问题就耗费了整整两天。最终方案就是老老实实回归到最基础的C接口。2.2 运行时环境的鸿沟C语言几乎没有运行时环境Runtime。它不负责对象的构造与析构不管理异常也没有垃圾回收。一个C函数就是一段纯粹的指令序列。C则拥有一个相对复杂的运行时环境。当一个C对象被创建时构造函数会被自动调用销毁时析构函数会被调用。如果函数中抛出了异常需要有相应的机制来捕获和传播。这些机制都是C语言所不具备的。如果让C代码直接去“new”一个C对象它完全不知道之后该如何调用析构函数来正确释放资源极易导致内存泄漏。2.3 工具链的广泛支持几乎所有的现代编程语言Python、Java、Go、Rust、C#等都提供了与C语言交互的标准机制。Python有ctypes/cffiJava有JNIGo有cgoRust有extern C。它们的设计初衷就是与C ABI对接。如果你想让你用C写的库被Python调用最稳妥、最通用的路径就是先把它包装成C接口然后Python通过ctypes调用这个C接口。试图让这些语言直接去理解C的类布局和虚表无异于自找麻烦。3. 核心方案设计C风格包装器Wrapper理解了“为什么必须这么做”之后我们来看“具体怎么做”。核心思想是在C项目中创建一层薄薄的C语言兼容层将面向对象的C类接口“翻译”成一系列过程式的、只使用基本数据类型的C函数。3.1 接口设计原则在设计这层包装器时需要遵循几个关键原则这能让你后续的调试和维护工作轻松很多使用不透明指针Opaque Pointer这是最关键的一招。C语言没有“类”的概念但它有指针。我们可以在C的头文件中声明一个不完整的结构体类型如typedef struct MyClassHandle MyClassHandle;而不暴露其内部成员。在C的实现文件中再将其具体定义为真实的C类指针。这样C代码拿到的是一个void*类似的“句柄”Handle它可以通过这个句柄来调用后续的函数但完全不知道句柄背后具体是什么实现了信息隐藏。资源生命周期管理必须提供明确的创建Create和销毁Destroy函数。C代码负责调用Create函数来获取句柄并在使用完毕后调用Destroy函数来释放资源。这模拟了C中new和delete的行为。扁平化函数接口将C类的成员函数转化为接受句柄作为第一个参数的C函数。例如MyClass::process(int data)转化为void myclass_process(MyClassHandle* handle, int data)。使用基本数据类型接口函数参数和返回值应尽量使用C语言的基本类型int,float,double,char*或简单的结构体。避免直接传递C标准库类型如std::string,std::vector。如果需要传递字符串使用const char*如果需要传递数组使用指针加长度的方式int* array, int length。错误处理C可以用异常但C不行。包装器内部必须捕获所有C异常并将其转换为C接口能理解的错误码如返回int类型0表示成功非零表示错误或者通过额外的输出参数来传递错误信息。3.2 头文件.h的双重角色我们需要创建两个头文件一个给C语言调用者使用纯C头文件一个给C包装器实现使用C兼容头文件。通常我们可以用条件编译#ifdef __cplusplus来将两者合并。示例my_cpp_lib.h(供C和C共同使用)// my_cpp_lib.h #ifndef MY_CPP_LIB_H #define MY_CPP_LIB_H #ifdef __cplusplus extern C { #endif // 前向声明一个不透明句柄类型C代码只需知道这个名字 typedef struct MyAlgorithmHandle MyAlgorithmHandle; // 创建算法实例返回句柄。失败返回NULL。 MyAlgorithmHandle* create_algorithm(const char* config_path); // 使用算法处理数据 // 参数1: 实例句柄 // 参数2: 输入数据数组 // 参数3: 输入数据长度 // 参数4: 输出数据数组由调用者分配假设长度已知 // 返回值: 0表示成功负数表示错误码 int algorithm_process(MyAlgorithmHandle* handle, const float* input_data, int input_len, float* output_data); // 获取算法状态信息填充到提供的字符串缓冲区 int algorithm_get_status(MyAlgorithmHandle* handle, char* status_buffer, int buffer_size); // 销毁算法实例释放资源 void destroy_algorithm(MyAlgorithmHandle** handle); // 使用二级指针便于置空 #ifdef __cplusplus } #endif #endif // MY_CPP_LIB_H这个头文件对C编译器来说看到的只是一些函数声明和一个不完整的结构体。extern C确保了在C编译器下这些函数名不会被修饰。4. 包装器实现详解与实操要点接下来我们实现包装器的C源文件。这个文件才是真正连接两个世界的桥梁。4.1 实现文件的基本结构示例my_cpp_lib_wrapper.cpp// my_cpp_lib_wrapper.cpp #include my_cpp_lib.h // 包含实际的C类头文件 #include MyAdvancedAlgorithm.hpp #include cstring // for strncpy #include exception // 将不透明句柄具体化为指向真实C类的指针 struct MyAlgorithmHandle { MyAdvancedAlgorithm* algo_obj; }; // 辅助函数将C异常转换为错误码 static int translate_exception() { try { throw; // 重新抛出当前异常 } catch (const std::invalid_argument e) { return -1; // 参数错误 } catch (const std::runtime_error e) { return -2; // 运行时错误 } catch (...) { return -99; // 未知错误 } } extern C { MyAlgorithmHandle* create_algorithm(const char* config_path) { try { // 使用真实的C类构造函数 MyAdvancedAlgorithm* algo new MyAdvancedAlgorithm(config_path); MyAlgorithmHandle* handle new MyAlgorithmHandle; handle-algo_obj algo; return handle; } catch (...) { // 创建失败返回空指针 return nullptr; } } int algorithm_process(MyAlgorithmHandle* handle, const float* input_data, int input_len, float* output_data) { if (!handle || !handle-algo_obj || !input_data || !output_data || input_len 0) { return -1; // 无效参数 } try { // 调用真实C对象的方法 // 这里假设process方法接受vector我们需要转换。 // 更优做法是让C接口也接受指针和长度避免额外拷贝。 std::vectorfloat input_vec(input_data, input_data input_len); std::vectorfloat output_vec handle-algo_obj-process(input_vec); // 检查输出大小是否匹配调用者预期这里简化处理 // 实际项目中可能需要额外的函数来查询输出长度 if (output_vec.size() static_castsize_t(input_len)) { // 假设输出不长于输入 return -3; // 输出缓冲区可能不足 } std::copy(output_vec.begin(), output_vec.end(), output_data); return 0; // 成功 } catch (...) { return translate_exception(); } } int algorithm_get_status(MyAlgorithmHandle* handle, char* status_buffer, int buffer_size) { if (!handle || !handle-algo_obj || !status_buffer || buffer_size 0) { return -1; } try { std::string status handle-algo_obj-getStatus(); // 安全地拷贝字符串防止缓冲区溢出 strncpy(status_buffer, status.c_str(), buffer_size - 1); status_buffer[buffer_size - 1] \0; // 确保终止符 return 0; } catch (...) { return translate_exception(); } } void destroy_algorithm(MyAlgorithmHandle** handle) { if (handle *handle) { if ((*handle)-algo_obj) { delete (*handle)-algo_obj; (*handle)-algo_obj nullptr; } delete *handle; *handle nullptr; // 避免悬空指针 } } } // extern C4.2 关键实现细节与避坑指南资源管理必须成对出现create_algorithm和destroy_algorithm必须严格配对。在destroy函数中要先删除内部的C对象再删除句柄结构体本身。使用二级指针MyAlgorithmHandle**是个好习惯它允许我们在销毁后将外部的指针变量置为NULL防止后续误用导致双重释放或野指针。参数校验不可或缺所有导出函数的第一步都应该是严格的参数校验。特别是对传入的指针和句柄进行非空判断。C语言调用者可能传递非法值健壮的包装器能避免程序崩溃而是返回明确的错误码。数据类型的转换与拷贝这是性能损耗的主要来源。如上例中将float*转换为std::vectorfloat涉及一次内存拷贝。对于性能敏感的场景有两种优化思路修改底层C类的接口使其也提供接受原始指针和长度的process重载版本。这样包装器可以直接传递指针无需拷贝。在接口设计时约定数据由谁分配。例如可以让algorithm_process函数返回一个指向内部数据的指针但要注意生命周期管理或者要求调用者先调用algorithm_get_output_buffer_size来获取大小再进行操作。异常安全包装器必须捕获所有从C代码中可能抛出的异常绝不能让其传播到C代码中因为C没有异常处理机制会导致程序立即终止。translate_exception函数将不同类型的C异常映射为不同的整数错误码为C调用者提供了清晰的错误信息。踩坑实录曾经没有在包装器内部捕获一个std::bad_alloc异常内存不足导致C程序在内存耗尽时直接abort()日志都没留下排查极其困难。后来统一加了catch(...)并记录日志世界清净了。5. C语言项目的调用方实现现在C语言项目可以像调用普通C库一样调用我们包装好的接口了。5.1 编译与链接首先你需要将C包装器代码和它依赖的C核心代码编译成一个库。Linux/macOS (GCC/Clang):# 编译C包装器和核心代码生成位置无关代码PIC用于动态库 g -c -fPIC -stdc11 my_cpp_lib_wrapper.cpp MyAdvancedAlgorithm.cpp -I. # 链接成动态库 g -shared -o libmycppalg.so my_cpp_lib_wrapper.o MyAdvancedAlgorithm.o # 静态库也可以 ar rcs libmycppalg.a my_cpp_lib_wrapper.o MyAdvancedAlgorithm.oWindows (MSVC):# 编译 cl /c /EHsc /MD my_cpp_lib_wrapper.cpp MyAdvancedAlgorithm.cpp # 链接成动态库DLL link /DLL /OUT:mycppalg.dll my_cpp_lib_wrapper.obj MyAdvancedAlgorithm.obj/EHsc参数启用C异常处理这对包装器至关重要。然后在你的C项目中包含头文件#include my_cpp_lib.h链接库在编译命令中指定-lmycppalgLinux或将mycppalg.libWindows加入链接器输入。确保运行时能找到动态库.so或.dll。5.2 C语言调用示例示例c_main.c// c_main.c #include stdio.h #include stdlib.h #include my_cpp_lib.h int main() { MyAlgorithmHandle* algo NULL; const char* config config.json; float input[] {1.0f, 2.0f, 3.0f, 4.0f}; float output[4] {0}; char status[256] {0}; // 1. 创建实例 algo create_algorithm(config); if (!algo) { fprintf(stderr, Failed to create algorithm instance.\n); return EXIT_FAILURE; } // 2. 获取状态 if (algorithm_get_status(algo, status, sizeof(status)) ! 0) { fprintf(stderr, Failed to get status.\n); destroy_algorithm(algo); return EXIT_FAILURE; } printf(Algorithm Status: %s\n, status); // 3. 处理数据 int ret algorithm_process(algo, input, 4, output); if (ret ! 0) { fprintf(stderr, Algorithm process failed with error code: %d\n, ret); destroy_algorithm(algo); return EXIT_FAILURE; } printf(Processing result: ); for (int i 0; i 4; i) { printf(%.2f , output[i]); } printf(\n); // 4. 销毁实例释放资源 destroy_algorithm(algo); // 良好的习惯销毁后将指针置NULL if (algo NULL) { printf(Algorithm handle destroyed successfully.\n); } return EXIT_SUCCESS; }编译这个C程序假设使用动态库gcc -o c_program c_main.c -L. -lmycppalg -stdc99 # 运行前确保动态库路径在LD_LIBRARY_PATHLinux或当前目录Windows6. 进阶话题与替代方案6.1 处理复杂的C类型如果必须传递复杂的结构体怎么办例如你的C类接口使用了std::mapstd::string, std::vectorint。强行在C中定义对应的结构非常困难且容易出错。解决方案在C接口中使用void*来代表这些复杂对象并提供专门的“序列化/反序列化”函数。void* create_complex_data_from_c(const char* json_str);// C传JSON字符串C解析后返回句柄int fill_complex_data(MyAlgorithmHandle* handle, void* complex_data_handle);// 将句柄传给算法void free_complex_data(void** handle);// 释放资源 本质上是把复杂数据类型的生命周期管理也封装成一套C风格的API。6.2 使用自动化工具SWIG/pybind11如果你的主要目标是将C库暴露给Python、Java等高级语言手动写C包装器虽然通用但比较繁琐。可以考虑自动化工具SWIG (Simplified Wrapper and Interface Generator)功能强大支持多种目标语言Python, Java, C#, Go, Lua等。它通过一个特殊的接口文件.i来描述要包装的C接口然后自动生成包装代码。对于大型项目或需要支持多语言时可以节省大量时间。但配置和学习曲线稍陡峭生成的代码可能比较冗长。pybind11专注于Python这是一个轻量级的头文件库用于在C中创建Python绑定。它的语法非常直观利用了大量的C11特性代码看起来就像在写C。如果你只需要对接Pythonpybind11是比手动写C包装器再通过ctypes调用更优雅、更高效的选择。选择建议如果调用方是C、或者需要最大程度的兼容性和可控性如嵌入式环境手动编写C包装器是最可靠、依赖性最小的方案。如果调用方是Python等脚本语言且项目结构允许使用pybind11能极大提升开发效率。6.3 回调函数Callback的处理有时C算法需要异步通知C程序某些事件。这可以通过函数指针实现回调。在C头文件中定义回调类型typedef void (*ProgressCallback)(int percent, void* user_data);在创建函数中增加回调参数MyAlgorithmHandle* create_algorithm_with_callback(const char* config, ProgressCallback cb, void* user_data);在C包装器内部将C函数指针和用户数据保存起来当C算法有进度更新时调用这个保存的C函数指针。注意从C回调到C时执行环境是安全的但传递的参数仍需是基本类型或双方约定好的简单结构体。7. 常见问题与排查技巧实录即使按照上述步骤操作在实际集成过程中仍可能遇到各种问题。下面是一些典型问题及其解决方法。问题现象可能原因排查步骤与解决方案编译C包装器时链接错误未定义的引用1. 忘记链接C核心实现文件.cpp。2. C核心代码依赖了其他第三方库如Boost、OpenCV但未在链接指令中指定。1. 检查编译命令确保所有必要的.cpp文件都已加入。2. 使用nm或dumpbin工具查看生成的库文件符号确认需要的函数是否已导出。3. 确保链接命令包含了所有必需的库如-lopencv_core。C程序编译链接通过但运行时崩溃段错误1.最常见C和C代码使用了不同的C运行时库CRT。在Windows上用/MD动态链接编译的库无法与/MT静态链接的程序混用。2. 资源管理错误重复释放、访问已释放的内存。3. 参数传递错误例如传递了无效的句柄NULL或野指针。1.统一运行时库确保C项目和C包装器项目使用相同的CRT链接选项在MSVC中都是/MD或都是/MTdfor Debug。2. 在包装器的create和destroy函数中加入日志跟踪句柄的生成和销毁。3. 在包装器所有函数的入口处严格校验输入参数对非法输入立即返回错误码。C程序调用函数后得到乱码或错误结果1. 函数调用约定不一致。虽然用了extern C但如果函数声明头文件和定义实现不匹配也会出错。2. 数据类型不匹配例如在64位系统上C代码中的long可能是8字节而C包装器参数声明为int4字节。3. 内存布局问题如果传递了结构体需确保C和C中结构体的对齐方式packing一致。1. 使用extern C并确保头文件被C和C包含时其声明完全一致。2.使用明确大小的类型在接口中尽量使用stdint.h中的类型如int32_t,uint64_t,size_t。3. 在结构体定义中使用编译指令强制对齐如GCC的__attribute__((packed))或MSVC的#pragma pack(push, 1)。动态库加载失败Linux:dlopen失败Windows: 找不到DLL1. 库文件不在系统的库搜索路径中。2. 库有未满足的依赖可以用ldd(Linux)或Dependency Walker(Windows)查看。3. C库依赖的Glibc版本高于运行环境。1. 将库所在目录添加到LD_LIBRARY_PATHLinux或放在可执行文件同级目录Windows。2. 解决缺失的依赖或将依赖库一并打包。3. 在较老的系统上部署时考虑在低版本的Glibc环境下编译你的库。一个实用的调试技巧在C包装器函数的入口和出口添加简单的日志输出可以快速定位问题发生在哪个环节。例如int algorithm_process(...) { fprintf(stderr, [C Wrapper] algorithm_process called. Handle: %p\n, handle); // ... 实际逻辑 fprintf(stderr, [C Wrapper] algorithm_process returning: %d\n, ret); return ret; }让C语言项目调用C代码核心在于建立一道由C函数构成的“防火墙”。这道墙隔离了C的简单世界和C的复杂世界。墙的一边C代码享受着稳定、简单的函数调用墙的另一边C代码可以继续使用其所有的强大特性。而这道墙本身——即C风格的包装器——需要你精心设计处理好资源生命周期、异常转换、数据搬运和错误处理。虽然手动编写需要一些工作量但它带来的兼容性、可控性和性能优势在需要长期维护和跨平台部署的系统集成项目中是绝对值得的。下次当你面对需要混合C与C的场景时不妨就从定义一个清晰的不透明句柄和一对Create/Destroy函数开始吧。

相关新闻

【半导体百科】扩散掺杂工艺:从原理到实战,一文吃透

【半导体百科】扩散掺杂工艺:从原理到实战,一文吃透

一、问题背景:一次炉管温度漂移引发的批量良率事故2024年某8英寸Fab在量产0.18um PMOS晶圆时,扩散推进(Source/Drain Extension,简称SDE)工序过后,CP测试显示批量片内薄层电阻(Rs)超…

2026/10/19 15:56:15 阅读更多 →
碰一碰能力进化史:从单设备传输到 ShareKit 全域坐标交互升级

碰一碰能力进化史:从单设备传输到 ShareKit 全域坐标交互升级

一、三代碰一碰能力完整演进脉络 第一代:基础NFC碰一碰(鸿蒙4~5,独立NFC Kit) 定位:纯NFC标签/点对点设备配对,仅做设备握手触发 底层:仅NFC近场通信,无高速传输通道,…

2026/10/18 2:29:43 阅读更多 →
视频帧级差异检测工具:支持同步比对、仪表盘识别与3D场景专用分析

视频帧级差异检测工具:支持同步比对、仪表盘识别与3D场景专用分析

本文还有配套的精品资源,点击获取 简介:一套开箱即用的Python视频比对工具,基于OpenCV实现像素级帧差异识别。能自动对齐两个视频的时间轴并逐帧比对,也支持无时间同步的原始帧差分;针对3D渲染画面、汽车仪表盘类UI…

2026/10/18 15:26:30 阅读更多 →

最新新闻

RAG全链路拆解:从文档解析到检索优化的实战指南

RAG全链路拆解:从文档解析到检索优化的实战指南

RAG这两年几乎是所有大模型应用绕不开的话题,尤其是在企业内部知识库问答、私有文档检索、客服助手这类场景里,它已经是标配方案了。但我在和一些开发者、产品经理聊的时候发现,大家对RAG的理解往往停在“检索生成”这四个字上,知…

2026/10/19 16:12:02 阅读更多 →
开源式代码审查:CLI+本地LLM+Git Diff的自动化实践

开源式代码审查:CLI+本地LLM+Git Diff的自动化实践

1. 这不是另一个“AI代码审查工具”,而是一套可落地的开源协作新范式“open-code-review”这五个字母组合,最近在工程师茶水间、技术 Slack 频道和 GitHub Trending 页面上出现频率陡增。它不指向某个具体 SaaS 服务,也不是某家大厂刚发布的闭…

2026/10/19 16:10:58 阅读更多 →
计算机网络期末备考:TCP可靠传输、子网划分与路由协议核心考点解析

计算机网络期末备考:TCP可靠传输、子网划分与路由协议核心考点解析

1. 考前避坑:先说清楚这门课到底考什么这几天后台收到不少私信,清一色都在问“深大计网慕课期末有没有答案”“某某章的题会不会原题考”。说实话,问答案的人多,但真正把课听完、把实验做完的人,反而没几个。我扒过好几…

2026/10/19 16:10:58 阅读更多 →
Java开发者接入大模型实战:JBoltAI基础能力层核心模块解析

Java开发者接入大模型实战:JBoltAI基础能力层核心模块解析

不要让 Java 开发者继续当 AI 时代的旁观者。过去这两年,大模型相关的技术几乎被 Python 生态垄断,做 AI 项目绕不开 Python、PyTorch、LangChain 这一套。但很多团队的核心资产和业务系统都是 Java 写的,尤其是金融、电商、政企这些对稳定性…

2026/10/19 16:10:58 阅读更多 →
文件上传漏洞攻防实战:绕过手法与防御加固指南

文件上传漏洞攻防实战:绕过手法与防御加固指南

文件上传,这四个字在Web安全测试里几乎每次深度审计都绕不开。我最近把一套开源的Web漏洞靶场(项目名就叫iwebsec)里的文件上传专题完整过了一遍,从最基础的前端限制到后面的内容检测、解析差异、条件竞争,一层层打下来…

2026/10/19 16:10:58 阅读更多 →
spaCy中文命名实体识别实战:从模型选型到自定义训练与部署

spaCy中文命名实体识别实战:从模型选型到自定义训练与部署

1. 为什么中文命名实体识别我选了 spaCy先交代一下背景。我前阵子接了个文本挖掘的活,要在一批中文资讯里把人名、机构名、地点、时间这些实体抽出来,喂给下游的关联分析。刚开始我没多想,直接拿现成的一套分词工具加规则去搞,结果…

2026/10/19 16:10:58 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →