CODESYS中ANY类型:从类型安全到动态灵活的工业自动化编程实践
1. 项目概述从“类型安全”到“动态灵活”的跨越在工业自动化编程领域尤其是使用像CODESYS这样的主流IEC 61131-3平台时我们长期被“类型安全”这一铁律所塑造。每一个变量都必须预先声明其精确的数据类型——BOOL,INT,REAL,ARRAY[1..10] OF INT或是精心设计的STRUCT。这种强类型系统是程序稳定运行的基石它能在编译阶段就揪出大量潜在错误比如试图把一个字符串赋值给一个整数变量。然而随着项目复杂度的提升尤其是面临需要处理未知格式的数据、构建高度通用的函数库如日志记录、数据序列化或与异构系统如不同厂商的PLC、数据库、Web服务交互时这种严格的类型约束有时会显得束手束脚。你可能会想能不能有一个变量它可以“容纳”任何类型的数据能不能写一个函数它的输入参数在编写时不需要确定具体类型这就是ANY数据类型出现的背景。它不是对类型安全的背叛而是一种在严格框架下开辟的、可控的“动态性”后门。简单来说ANY类型是一个指针它指向一块内存以及描述这块内存中数据“形状”即类型信息的元数据。当你将一个变量声明为ANY时你并不是说它没有类型而是说它的类型在编译时是未知的将在运行时根据实际赋予它的值来确定。这听起来有点像某些高级语言中的Variant或Object但在以实时性和确定性著称的PLC世界里ANY的实现更加底层和高效其行为也受到IEC标准的严格定义。对于从传统PLC编程转向复杂系统集成的工程师来说理解并善用ANY意味着你能设计出前所未有的灵活且强大的功能块。例如一个通用的调试信息打印函数可以接收任何数据类型的变量并格式化成字符串一个数据归档函数可以无需修改代码就记录来自不同工艺段、结构迥异的生产数据包。这不仅仅是语法糖它是一种思维模式的升级让你从“为特定类型写代码”转向“为抽象接口写代码”。2. ANY类型的核心原理与内存模型剖析要安全有效地使用ANY绝不能停留在“它能存放任何东西”的模糊认知上。我们必须深入其内部实现理解它如何在CODESYS的运行时环境中工作。这能从根本上解释许多看似怪异的行为并避免致命错误。2.1 ANY的本质带类型描述的指针在CODESYS中一个ANY类型的变量并不是一个“万能容器”它不直接存储数据本身。实际上它是一个固定大小的数据结构通常包含两个核心部分类型信息指针指向一个描述数据类型的结构体。这个结构体定义了数据的种类基本类型、数组、结构体等、元素类型、数组维度和大小等。数据指针指向实际数据所在的内存地址。当你执行anyVar : intVar;这样的赋值时发生的是浅拷贝。anyVar的类型信息部分被设置为INT的描述数据指针部分则指向intVar的内存地址。这里有一个至关重要的细节ANY并不复制数据本身。这意味着如果你通过ANY指针修改了数据原始变量的值也会改变。这与INT到INT的赋值值拷贝有本质区别。2.2 类型描述符与运行时类型识别CODESYS在背后为每一种出现在程序中的数据类型包括你自定义的STRUCT和FUNCTION_BLOCK都生成一个唯一的类型描述符。当你使用ANY时系统就是通过比对ANY变量所携带的类型描述符来执行运行时检查。例如在调用一个使用ANY输入的函数块时函数块内部代码可以并且应该检查传入的ANY参数是否与期望的类型匹配。IEC 61131-3第三版标准定义了TYPE_OF()和IS_INSTANCE_OF()等运算符用于在ST结构化文本语言中直接进行这种类型查询。虽然CODESYS对这些标准运算符的支持情况需查阅具体版本手册但通过系统库或底层功能实现类型判断是ANY应用的基础。2.3 内存布局示例与风险假设我们有一个结构体和一个ANY变量TYPE ST_Recipe : STRUCT id : INT; name : STRING(255); temperature : REAL; END_STRUCT END_TYPE VAR myRecipe : ST_Recipe : (id : 1, name : ‘Default‘, temperature : 25.5); anyBuffer : ANY; END_VAR执行anyBuffer : ADR(myRecipe);与anyBuffer : myRecipe;是截然不同的。anyBuffer : ADR(myRecipe);anyBuffer的类型信息被设置为POINTER TO ST_Recipe数据指针指向myRecipe变量的地址。你需要通过指针解引用来访问数据这通常需要配合ANY_TO_POINTER转换和明确的类型转换非常底层且危险。anyBuffer : myRecipe;这是更常见和推荐的方式。CODESYS会自动将anyBuffer的类型信息设置为ST_Recipe数据指针指向myRecipe的数据区。你可以使用ANY相关的函数来安全地访问其字段。注意直接操作ANY内部指针是极度危险的行为除非你非常清楚自己在做什么。错误的指针操作可能导致PLC运行时访问非法内存引发“Memory Access Violation”错误这是最高优先级的故障会直接导致PLC进入停止状态。在绝大多数应用场景中我们应该使用CODESYS提供的标准库函数如SysMem库中的函数或安全模式来操作ANY数据。3. ANY类型的实战应用场景与代码实现理解了原理我们来看ANY如何解决实际问题。下面通过几个典型场景展示从声明、赋值到使用的完整流程。3.1 场景一构建通用数据记录与调试功能块这是ANY最经典的应用。我们需要一个FB_GenericLogger它能将任意变量的名称、值和时间戳记录到文件或发送到网络。FUNCTION_BLOCK FB_GenericLogger VAR_INPUT dataName : STRING; dataValue : ANY; // 关键输入参数类型为ANY END_VAR VAR internalBuffer : ARRAY[1..1024] OF BYTE; // 用于临时存储格式化的数据 typeInfo : POINTER TO __SYSTEM.TYPEDESCRIPTION; // 类型信息指针概念性 END_VAR实现步骤获取类型信息在功能块内部首先需要获取传入dataValue的类型描述符。CODESYS可能通过__GET_TYPEID()或类似内部函数提供此功能。我们需要查阅对应版本的运行时库文档。类型判断与格式化使用CASE语句或IF...ELSIF链根据类型信息将数据转换为字符串。例如CASE typeInfo^.typeClass OF __SYSTEM.TYPE_CLASS_INT: stringValue : INT_TO_STRING(ANY_TO_INT(dataValue)); __SYSTEM.TYPE_CLASS_REAL: stringValue : REAL_TO_STRING(ANY_TO_REAL(dataValue)); __SYSTEM.TYPE_CLASS_STRING: stringValue : ANY_TO_STRING(dataValue); // ... 处理其他基本类型和数组 ELSE stringValue : ‘Unsupported Type‘; END_CASE实操心得对于复杂的自定义STRUCT通用的格式化非常困难。一个实用的技巧是要求所有需要通过此记录器记录的结构体都实现一个特定的方法如ToString()然后在ANY处理中通过查询接口或调用预定义的函数指针来调用该方法。这需要更高级的设计模式。拼接与输出将dataName、stringValue和当前时间戳拼接成一行日志写入文件缓冲区。调用示例VAR logger : FB_GenericLogger; cycleCount : INT : 0; motorSpeed : REAL : 1500.0; alarmMessage : STRING : ‘Over temperature‘; END_VAR logger(dataName : ‘CycleCount‘, dataValue : cycleCount); logger(dataName : ‘MotorSpeed‘, dataValue : motorSpeed); logger(dataName : ‘Alarm‘, dataValue : alarmMessage);一个函数块处理了INT、REAL、STRING三种完全不同的数据类型这就是ANY带来的灵活性。3.2 场景二实现可变参数列表的函数虽然IEC 61131-3标准本身不支持像C语言printf那样的可变参数但我们可以利用ANY类型和数组来模拟。创建一个函数块用于将多个任意类型的变量打包成一个字节数组用于网络传输或持久化存储。FUNCTION_BLOCK FB_VariablePacker VAR_INPUT // 使用数组输入每个元素是一个ANY可以接受多个参数 variables : ARRAY[*] OF ANY; END_VAR VAR_OUTPUT buffer : ARRAY[0..1023] OF BYTE; bufferLength : UINT; END_VAR VAR writePos : UINT : 0; i : UINT; END_VAR实现思路遍历ANY数组循环处理variables数组中的每一个元素。写入类型头在buffer中首先写入该变量的类型标识符一个预定义的枚举值和数据的字节长度。拷贝数据根据ANY变量携带的类型信息和数据指针使用MEMCPY函数将实际数据字节拷贝到buffer的相应位置。移动写指针更新writePos指向buffer中的下一个空闲位置。解包时需要另一个功能块FB_VariableUnpacker按照相同的格式类型头数据从buffer中读取并根据类型标识符将数据还原到对应类型的变量中。注意事项这种手动序列化/反序列化非常繁琐且容易出错尤其是处理包含指针的类型如STRING或嵌套结构时。在工业场景中更常见的做法是使用标准的序列化格式如JSON或Google的Protocol Buffers并寻找或开发对应的CODESYS库。ANY在这里的作用是为这些库的通用接口提供可能性。3.3 场景三与高级语言或外部系统的接口当CODESYS需要通过OPC UA、MQTT或自定义TCP/IP协议与MES、SCADA或云平台通信时经常需要处理动态的、模式不固定的数据点。例如一个云平台下发的配方可能包含新的、PLC程序编译时未知的字段。在这种情况下可以在PLC内部定义一个通用的“键值对”结构数组TYPE ST_KeyValuePair : STRUCT key : STRING(50); valueType : INT; // 自定义枚举1INT, 2REAL, 3STRING等 valueData : ARRAY[1..100] OF BYTE; // 存储值的原始字节 // 或者使用ANY但需注意生命周期管理 // valueAny : ANY; END_STRUCT END_TYPE VAR dynamicData : ARRAY[1..50] OF ST_KeyValuePair; END_VAR外部系统将数据解析后根据valueType将值写入valueData字节数组。PLC程序在读取时再根据valueType将字节数组解释为对应的类型。这里ANY可以作为valueData的一种高级替代让内部处理更直接但必须确保ANY指针在整个生命周期内有效即指向的数据未被覆盖或释放。4. 深入ANY的高级特性与边界探索掌握了基础应用后我们来看看ANY的一些高级特性和需要警惕的边界情况。4.1 ANY与引用类型、值类型的交互这是理解ANY行为的关键。在CODESYS中基本类型BOOL,INT,REAL,BYTE等是值类型。anyVar : intVar;之后anyVar指向intVar的存储位置。修改anyVar指向的值就是修改intVar。字符串STRING是特殊的引用类型。一个STRING变量本身是一个固定大小的字符缓冲区描述符。anyVar : stringVar;后anyVar指向这个描述符。对ANY进行字符串操作会影响原stringVar。数组和结构体通常是值类型除非显式使用REFERENCE TO。对于大型数组或结构体赋值给ANY不会产生数据拷贝性能很高但同样意味着修改是“原位”的。示例引用陷阱VAR a : INT : 10; b : INT : 20; anyA, anyB : ANY; END_VAR anyA : a; anyB : anyA; // 将anyB也指向a anyB : b; // 现在anyB指向b了但anyA仍然指向a // 此时通过anyA修改值会影响a通过anyB修改值会影响b两者独立。ANY的赋值是改变其指向而不是拷贝其指向的数据。4.2 使用系统库函数安全操作ANY为了避免直接操作指针的风险应优先使用CODESYS系统库。例如SysMem库可能提供以下函数SysMemGetAnySize获取ANY变量指向的数据所占用的字节数。SysMemCopyAnyToBuffer将ANY指向的数据安全地拷贝到指定的字节数组缓冲区。SysMemCopyBufferToAny将字节数组缓冲区的数据安全地拷贝到ANY指向的位置。这些函数内部会进行边界检查和类型验证比手动进行指针运算安全得多。4.3 ANY的局限性性能开销虽然不拷贝数据但每次使用ANY都涉及额外的间接寻址和运行时类型检查其开销比直接操作强类型变量大。在对实时性要求极高的循环如1ms任务中需谨慎评估。调试困难在线监视时一个ANY类型的变量通常只会显示其内存地址或简单的类型描述无法直接看到其指向的具体值给调试带来不便。丧失编译时检查这是最大的代价。所有类型相关的错误都推迟到了运行时。一个本应在编译期发现的类型不匹配错误现在可能导致运行时功能块内部出错甚至PLC停机。平台兼容性ANY类型的具体内存布局、类型描述符结构以及相关支持函数可能在不同版本的CODESYS运行时或不同厂商的PLC基于CODESYS内核之间存在细微差异。编写高度依赖ANY内部机制的便携式库是困难的。5. 常见问题、调试技巧与最佳实践实录在实际项目中应用ANY我踩过不少坑也总结出一些让代码更健壮的方法。5.1 问题排查速查表问题现象可能原因排查步骤与解决方案PLC运行时访问冲突Memory Access Violation1.ANY指针已失效指向的变量已离开作用域。2. 通过ANY误写了只读内存区。3. 手动指针计算错误访问了非法地址。1.确保生命周期绝对不要将局部变量在函数或功能块内声明的临时变量的地址赋给全局或持久化的ANY变量。局部变量在调用结束后其内存可能被重用。2.使用系统库放弃手动MEMCPY或指针运算改用SysMem等安全函数。3.在线调试使用CODESYS的在线视图检查ANY变量的类型描述信息是否合理。功能块内部处理ANY数据时得到错误值1. 类型判断逻辑有误用处理INT的方式处理了REAL数据。2.ANY指向的数据在外部被意外修改。3. 字节序Endianness问题尤其在跨平台通信时。1.增强类型检查在功能块入口用更详尽的条件判断ANY的实际类型对不支持的类型立即返回错误。2.添加日志在处理前将ANY的类型信息和数据快照通过安全拷贝记录到日志便于回溯。3.明确约定在跨系统接口中明确约定数据格式和字节序。使用ANY的功能块性能低下在高速循环中频繁进行复杂的ANY类型判断和数据处理。1.性能分析使用CODESYS Profiler工具定位热点。2.缓存优化对于循环内不变的ANY参数可在功能块首次调用时识别其类型并缓存处理路径避免每次调用都进行完整判断。3.考虑替代方案对于性能瓶颈处是否能用多个针对特定类型的、重载的功能块来代替单个通用功能块在线监视时ANY变量显示为乱码或奇怪地址监视器无法解析ANY指向的未知或复杂类型。1.自定义监视视图为你的自定义结构体编写__WATCH视图这样当ANY指向该结构体时监视器可以正确显示其字段。2.辅助变量在调试时可以创建临时强类型变量通过安全转换将ANY的值赋给它然后监视这个临时变量。5.2 最佳实践与心得最小化使用范围不要因为“酷”而滥用ANY。将其严格限制在确实需要动态类型的模块如通用日志、通信适配层、配置加载器。业务逻辑核心部分应保持强类型。设计防御性接口任何接受ANY输入的功能块必须在最开始进行严格的类型验证。如果类型不符合预期应立即设置错误输出并返回避免后续操作导致不可预知的行为。提供类型安全的包装器为你强大的通用功能块基于ANY创建一系列针对常用类型的包装函数块。例如为FB_GenericLogger提供LogInt,LogReal,LogString等包装内部只是调用通用块。这样项目中的大多数代码依然享受编译时类型检查只有底层通用块处理ANY。详细文档与注释任何使用ANY的代码都必须附上详尽的注释说明其期望的数据格式、内存所有权谁负责分配和释放以及潜在的限制。这对团队协作和后期维护至关重要。充分的单元测试由于编译时检查减弱对使用ANY的代码进行全面的、覆盖各种数据类型和边缘情况的单元测试是必不可少的。CODESYS的测试管理器Test Manager是完成这项工作的好帮手。ANY类型是CODESYS赋予高级开发者的一把利器它打破了强类型的部分壁垒为实现高度灵活和可复用的代码组件提供了可能。然而正如所有强大的工具一样它需要使用者对其原理有深刻理解并遵循严格的使用规范。将它用在合适的场景并辅以周密的防御性编程和测试你就能在保持PLC程序固有可靠性的同时极大地提升其适应复杂性和变化的能力。从我个人的经验来看成功应用ANY的关键不在于写出最“聪明”的代码而在于构建最“健壮”和“清晰”的抽象。

相关新闻

率零和嘎嘎降AI哪个划算?1万字论文实测:价格/达标率/用时全对比

率零和嘎嘎降AI哪个划算?1万字论文实测:价格/达标率/用时全对比

率零和嘎嘎降AI哪个划算?1万字论文实测:价格/达标率/用时全对比 这两款工具经常被一起比较,因为都是国内降AI工具里知名度比较高的。实际用下来差异不小。这篇文章用同一篇1万字论文分别测了两款,把结果整理出来。 基本参数对比…

2026/10/14 16:07:45 阅读更多 →
每日热门skill-35万次下载!这款Skill让AI Agent装上“自省基因“,从此告别重复犯错

每日热门skill-35万次下载!这款Skill让AI Agent装上“自省基因“,从此告别重复犯错

今日主角:self-improving(ivangdavila) 标签:#AI Agent #自我进化 #记忆管理 #零依赖 一、当AI Agent开始"认错" 想象一个场景。 周一,你让AI Agent帮你把三个季度的销售表合并,跑出环比增长图…

2026/10/14 0:00:36 阅读更多 →
D2DX暗黑破坏神2高清补丁:三分钟让经典游戏焕发第二春

D2DX暗黑破坏神2高清补丁:三分钟让经典游戏焕发第二春

D2DX暗黑破坏神2高清补丁:三分钟让经典游戏焕发第二春 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 还在忍受暗…

2026/10/13 21:53:30 阅读更多 →

最新新闻

JDK1.8源码jar包制作与IDE关联调试实战指南

JDK1.8源码jar包制作与IDE关联调试实战指南

简介:JDK 1.8源码包完整收录了Java 8标准库的全部源代码,适合希望深入理解核心类库与并发模型的Java开发人员,也适合高校学生对照源码学习设计模式。借助这份包,可以在开发工具中直接打开并查看HashMap、ConcurrentHashMap、Strea…

2026/10/14 16:10:21 阅读更多 →
Android ViewPager2与BottomNavigationView双向联动实战避坑指南

Android ViewPager2与BottomNavigationView双向联动实战避坑指南

1. 这不是“套娃”,而是Android导航架构里最常被误用的联动模式你有没有遇到过这样的需求:一个页面顶部是TabLayout控制的横向滑动内容区,底部又有一排固定菜单按钮,点击底部菜单时,不仅切换底部选中状态,还…

2026/10/14 16:10:21 阅读更多 →
STK中文学习资料实战指南:从解压到跑通第一个场景

STK中文学习资料实战指南:从解压到跑通第一个场景

简介:这份STK中文学习资料包面向航天任务规划、遥感通信与导航领域的初学者及进阶用户,系统解决从软件入门到模块化应用的学习需求。包内共30个文件,以14份PDF教材为核心,辅以PPT讲义、GIF与BMP图示、DOC文档及XML、HTM等配置示例…

2026/10/14 16:10:21 阅读更多 →
MQTT在工业现场踩坑实录:4类场景慎用

MQTT在工业现场踩坑实录:4类场景慎用

把MQTT搬进工业现场这三年,我踩过的坑比调试时掉的头发还多。三年前给某电池厂做产线数据采集选型时,市面上主流的方案几乎都绕不开MQTT,当时我也被它的"轻量、异步、解耦"这几个词吸引,一股脑把几十台设备的工艺数据全…

2026/10/14 16:10:21 阅读更多 →
MQTT在工业现场的4个“不适合”场景与选型建议

MQTT在工业现场的4个“不适合”场景与选型建议

MQTT在工业现场用了3年,我总结的4个“不适合”场景1. 先说结论:MQTT在工业现场为什么会这么火,又为什么会被我拉进黑名单三年前上一家单位做产线数采改造,我作为通信方案负责人,一头扎进MQTT的怀里。那时候理由很充分&…

2026/10/14 16:10:21 阅读更多 →
The Nature of Code 极坐标与三角函数实战:用 sin/cos 在 p5.js 中绘制圆周、螺旋与任意形状

The Nature of Code 极坐标与三角函数实战:用 sin/cos 在 p5.js 中绘制圆周、螺旋与任意形状

教程文档示例工程 【免费下载链接】website-archive Archive of the Coding Train website (first version) 项目地址: https://gitcode.com/gh_mirrors/we/website-archive 点击查看 免费下载 本文以 website-archive 仓库中归档的《Trigonometry and Polar Coord…

2026/10/14 16:09:19 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →