C++结构体与结构体数组:从定义、内存布局到排序实践
如果你翻过C教材的目录多半会在“结构体”这一章停几秒钟然后想这不就是把几个变量捆在一起吗有什么好讲的。我工作之后才意识到结构体恰恰是C里最容易被低估的基础设施链表、树、图形控件的属性表、网络报文的解析几乎处处都是结构体的影子。struct 用极简的语法解决了数据分组的问题而结构体数组则进一步解决了“成批的分组数据”怎么组织、怎么遍历、怎么排序的问题。这篇文章不是复述教科书而是结合我写代码的真实体验把 struct 定义、实例化、内存布局和结构体数组的日常操作里那些容易忽略的细节拆开讲清楚适合刚学 C 的读者也适合想查漏补缺的人。1. 为什么需要结构体从三个并行数组讲起1.1 没有结构体之前数据是“散装”的假设要管理三个学生的成绩每个学生有姓名、学号和总分。不理解结构体时很多人会写三个数组char names[3][32]; int ids[3]; double scores[3];看似整齐但实际使用起来很别扭。比如要输出 0 号学生的信息你得同时访问names[0]、ids[0]、scores[0]。如果调换顺序改名字的时候还得同步改学号和成绩一旦漏改数据就错位了。这种写法最大的问题是程序里的人都知道这三组数据属于同一个人但编译器不知道。编译器眼里它们是三个互相独立的数组没有任何逻辑关系。我们写过实际项目的人都懂最怕的就是这种“散装管理”。数据量小的时候还能忍一旦数据超过几十条再要按总分排个序、按学号查个人就得在两个数组之间来回倒腾下标索引容易错越改越乱。解决方案不是写更多数组而是把属于同一个实体的变量打包成一种新的类型——这就是结构体出现的原因。1.2 结构体把数据变成一个逻辑整体结构体的英文是struct全称structure本身就带着“结构”的意思。它允许你把不同类型的数据打包成一个自定义类型比如struct Student { char name[32]; int id; double score; };这时候Student就成了一种新的数据类型。你可以像使用int、double一样用它去定义变量、定义数组、传参。数据之间的逻辑关系由类型定义锁定编译器也知道了“这三个字段是同一条记录里的字段”想拆都拆不开。我用一个生活类比三个散装数组就像把每本书的书名、作者、价格分别放在三个抽屉里结构体则像把每本书的相关信息装进同一个透明文件袋然后再把一堆文件袋放进文件柜。管理一批“内容相关的概要信息”文件袋方案显然更不容易出错。1.3 顺带说说 struct 和 class 的关系很多初学者会遇到一个疑问C 里明明有class为什么还要教struct其实在 C 中struct和class在功能上几乎完全相同都可以定义成员变量、成员函数、继承等。唯一的默认区别是访问权限struct默认是publicclass默认是private。struct A { int x; }; // x 默认公开 class B { int x; }; // x 默认私有也就是说struct更适合表示纯粹的数据集合强调“没有封装要求数据对外可见”class则适合表示有封装、有行为、有逻辑的对象。在现代 C 编码规范里很多人也遵循这个习惯需要简单数据载体时用struct需要完整对象时用class。这不影响编译结果却会影响阅读代码的人对意图的理解。2. struct 的定义与实例化三个容易被绊倒的细节2.1 定义位置与结尾分号定义一个结构体常见位置是全局区、函数外这样多个函数都能使用。例如#include iostream #include cstring struct Point { double x; double y; }; int main() { Point p; return 0; }这里最容易踩的坑就是结尾的分号。C 规定结构体定义结束后必须加;因为struct Point { ... };在语法上是一条完整的声明语句。漏掉分号编译器会在下一行报错而且错误信息往往很不直观经常指到很远的地方。我第一次编程序时就因为这个分号被折腾了好一阵。还有一个细节结构体可以放在函数内部定义但一般不推荐。函数内部定义的结构体作用域只限于该函数无法被其他函数使用这会让“打包数据”的意义大大降低。除非是局部的临时分组数据否则还是把结构体定义放在全局或命名空间内比较好。2.2 实例化的三种常见方式定义好结构体之后就可以用它来声明变量。C 里有几种常见的写法struct Point { double x; double y; }; // 方式一直接声明成员未初始化 Point a; // 方式二聚合初始化按定义顺序给值 Point b{3.5, 2.0}; // 方式三C11 以后可以用等号 花括号 Point c {1.0, 4.0};对于内置类型成员直接声明的局部变量如果没有赋初值成员值是不确定的用{}初始化则可以把值明确设定。在 C11 之后推荐始终使用花括号初始化因为花括号初始化不允许窄化转换。比如Point d{3.5, 2}中整数转double属于可接受转换但int到char的窄化就会被编译器拦下来。这能帮你提前发现很多类型问题。2.3 点号访问与箭头访问的使用场合结构体变量访问成员用.。如果通过指针访问结构体则需要用-Point p{1.0, 2.0}; Point* ptr p; std::cout p.x; // 正确 std::cout ptr-x; // 正确等价于 (*ptr).x std::cout *ptr.x; // 语法错误先算 ptr.x再解引用 x这个看似简单但实际代码里一旦指针在函数间传来传去就容易把.和-混用。一个快速的判断方法左边是普通变量或引用就用.左边是指针就用-。如果发现自己对着报错改来改去大概率是把二者的语义弄混了。3. 结构体数组批量管理数据的起点3.1 定义结构体数组的两种写法有了结构体类型定义数组就顺理成章了。可以把Student当成一种普通类型写出Student class1[40]; // 40 个学生 Student class2[] { {Alice, 1001, 90.5}, {Bob, 1002, 88.0} };第二种写法让编译器根据初始化列表自动推断元素个数。日常开发中如果明确知道最大容量可以定义固定大小数组如果空间随运行情况变化更建议使用std::vectorStudent后面再讲。注意结构体数组的每个元素都是一个完整的结构体变量它们之间是连续存放的。数组名可以视为指向第一个元素的指针这个语义是后面几乎所有操作的基础。3.2 初始化、部分初始化和清零结构体数组初始化有几种典型写法。最稳妥的做法是使用完整初始化列表Student students[3] { {Alice, 1001, 90.5}, {Bob, 1002, 88.0}, {Cindy, 1003, 92.5} };如果初始化列表提供的元素少于数组长度剩余元素会被“值初始化”内置类型成员归零指针成员置为空指针。这在 C 中叫“剩余元素零初始化”比如Student s[3] { {Alice, 1001, 90.5} }; // s[1]、s[2] 的所有成员都被置为零值这个特性很实用。当你只想显式初始化前几项又希望后面数据安全时可以直接用这种语法。但要留个心眼如果结构体里有std::string这类带非平凡构造器的成员剩余元素会被正确构造为空字符串不会出现半初始化状态。这是 C 对聚合类型的保障。有的老教材会教你用memset(s, 0, sizeof(s))清空结构体数组。在 C 风格结构体里这没问题但现代 C 里如果结构体包含了std::string或其他需要构造的成员memset会摧毁对象内部状态导致未定义行为。这已经比语法错误更严重了。我建议涉及 RAII 类型成员不要用memset直接用循环赋值或s.fill()类操作。3.3 遍历结构体数组下标、范围 for 与指针遍历数组是基本操作。三种方式各有各的使用场景// 下标遍历 for (int i 0; i n; i) { std::cout students[i].name std::endl; } // 范围 forC11 起推荐 for (const Student s : students) { std::cout s.name std::endl; } // 指针遍历 for (Student* p students; p ! students n; p) { std::cout p-name std::endl; }我强烈推荐范围 for尤其是在只需要读取的时候用const Student s可以避免复制整个结构体。结构体占用空间往往不小一次完整的元素复制可能涉及几十上百字节循环次数一多性能差距是能感受到的。而用const Student绑定数组元素本身既不复制又禁止修改是读取的最稳妥选择。当然如果要修改元素就写成Student s。4. 内存布局结构体数组的核心性能陷阱4.1 字节对齐结构体的实际大小不是成员大小之和先看一个典型例子struct Foo { char c; // 1 字节 int n; // 4 字节 };直觉上sizeof(Foo)应该是 5但在绝大多数主流平台上结果是 8。原因在于字节对齐alignmentCPU 访问对齐的内存地址时最高效许多硬件对未对齐的访问会报错或付出额外代价。编译器所以在成员之间插入填充字节padding保证每个成员落在合适的地址上。在上面的例子中char c占 1 字节随后 3 个字节被填充int n从偏移 4 开始。整个结构体大小是 8。偏移量内容占用0char c1 字节1-3padding3 字节4-7int n4 字节这个特性直接影响结构体数组的步长。Foo数组每个元素占 8 字节不是 5 字节。当你关心内存占用时必须知道sizeof(Foo)而不是简单的成员之和。4.2 通过合理排列成员来减少填充既然编译器要填充那我们可以通过调整成员顺序来减少浪费。把大宽度的成员放在前面往往能降低尾部填充量。struct A { char c1; int num; char c2; }; // 通常 sizeof(A) 是 12 struct B { int num; char c1; char c2; }; // 通常 sizeof(B) 是 8两种结构体包含的成员完全相同只是顺序不同。因为int需要 4 字节对齐B 把int放最前面两个char就挤在尾部只用了 2 字节再加上必要的结尾填充到对齐边界整个大小是 8。而 A 把int夹在两个char之间前后各产生 3 字节 padding变 12 字节。数组元素越多空间差异越明显。因此在设计结构体时养成“按成员宽度从大到小排列”的习惯能免费节省不少内存。注意这属于优化策略仅在确实需要控内存时采取不要为了对齐牺牲代码可读性。4.3 结构体数组的内存连续性与缓存友好结构体数组最迷人的地方在于它的元素在内存中是连续排列的。访问第 i 个元素时地址就是基地址加上i * sizeof(Student)。这种连续存储特别适合大量数据的批量扫描。举个例子如果你要遍历 1 万个学生的成绩结构体数组在内存中呈现为一整块连续区域CPU 加载缓存时会把相邻元素一起读入后面的访问命中缓存概率很高。如果改成指针数组每个元素是一个指针指向堆上的独立结构体那么数据结构本身连续但实际数据分散访问每个结构体都要跳转到不同的内存区域缓存命中率会显著下降。这也是为什么在性能敏感场景我不会轻易去写“结构体指针数组”除非单个结构体内部存在堆分配或需要多态。5. 结构体数组的进阶操作排序、查找与传参5.1 用 std::sort 给结构体数组排序现实中的结构体数组往往不是单纯存数据而是要按某个字段排序。比如按总分成绩从高到低排列。C 标准库里的qsort写起来很啰嗦C 的std::sort配合 lambda 表达式要舒服得多#include algorithm Student students[3] { {Alice, 1001, 90.5}, {Bob, 1002, 88.0}, {Cindy, 1003, 92.5} }; std::sort(std::begin(students), std::end(students), [](const Student a, const Student b) { return a.score b.score; // 按成绩降序 });注意 lambda 参数要用引用。如果不加引用每次比较都会复制两个完整结构体排序过程中元素位置的交换还会再复制消耗不小。用const Student可以提高效率也避免意外修改。如果你希望成绩相同的情况下学号小的排在前面可以写二级比较条件[](const Student a, const Student b) { if (a.score ! b.score) return a.score b.score; return a.id b.id; }这种写法在数据处理的场景里非常常见本质上是定义了一个“优先级规则”读者很容易看懂。5.2 二分查找需要有序和统一比较排完序之后按分数查找某个值可以用二分查找。C 标准库提供std::lower_bound它要求数组已经按同一比较规则排好序并且查找时使用的比较逻辑和排序时一致否则结果就不可靠。// 需要数组按 score 降序排列查找与排序使用相同的比较规则 auto it std::lower_bound(std::begin(students), std::end(students), 90.0, [](const Student s, double value) { return s.score value; // 按降序比较 }); if (it ! std::end(students)) { std::cout 找到成绩 90.0 的学生 it-name std::endl; }这里有个很容易掉进去的坑lower_bound的比较函数需要和排序的规则“同构”。排序时比较两个Student查找时却常常与一个裸值比较两种写法很容易不一致导致明明数组里有这个成绩却找不到。我建议把排序规则抽成一个命名函数或 lambda 变量排序和查找都使用同一个减少出错的可能。5.3 函数传参结构体数组会退化为指针把结构体数组传给函数时数组名会自动退化为指向首元素的指针。也就是说void printStudents(Student arr[], int n); // 实际上等效于 Student* arr在printStudents内部sizeof(arr)不再等于整个数组的字节数而是一个指针的大小通常是 8 字节。所以必须额外传一个长度参数。如果你希望保持“数组”语义现代 C 更推荐使用std::arrayStudent, N或者在更灵活的场景直接用std::vectorStudent。std::array作为值类型传递时不丢失长度信息还能使用范围 for 等现代语法。结构体数组的概念在标准库里得到了更好的表达但它并没有让“结构体数组”这个基础概念过时反而让相关的批量操作更安全。6. 实际工程中绕不开的几个决策6.1 结构体成员里的字符串用 char[] 还是 std::string老式 C 风格结构体常用定长字符数组struct Student { char name[32]; int id; };优点是内存连续、可预测缺点是不够灵活。万一用户输入的名字超过 31 个字符就要截断或发生缓冲区溢出。现代 C 代码我更推荐直接使用std::stringstruct Student { std::string name; int id; };这时候结构体大小会随着字符串长度动态变化并不是std::string对象本身在结构体里只占固定大小的sizeof(std::string)通常是 24 字节或 32 字节实际字符串内容在堆上。这样结构体数组依然是连续存储的只是每个std::string内部可能指向堆上的内存。这并不破坏数组的连续性只是多了一层间接访问。字符串长度差异较大时这种设计比定长数组节省空间字符串都很短时定长数组反而更省。6.2 结构体数组 vs std::vector结构体这是一个我会被反复问到的问题。简单说定长结构体数组适合元素个数固定、生命周期明确、性能要求高的场景比如解析固定数量的网络配置项std::vector 适合动态增长、频繁插入删除的场景比如读取一个不知道行数的数据文件。两者底层都是连续内存std::vector本身管理扩容如果定义成std::vectorStudent它内部就保存着一段连续的结构体数组。差别在于容量管理和自动化。如果性能要抠到极致也无非是“固定数组零扩展成本”对“vector 扩容时的搬移成本”的取舍。除非你知道明确上限否则默认用std::vector更安全。反过来如果数据量巨大且长度固定写一个std::arrayStudent, N或原生数组反而少一层动态分配心态也更稳。6.3 复制结构体数组时要留意深拷贝与浅拷贝结构体数组直接整体复制时比如Student arr2[10] arr1;实际上是把每个元素按字节复制。结构体中只有内置类型和定长数组时这种复制没有问题但一旦成员里有指针或者std::vector单纯按字节复制就会产生“两个数组的元素共享同一个堆数据”的结果修改其中一个会影响另一个。在需要独立副本的场景下必须逐元素做深拷贝或替结构体显式实现拷贝构造与拷贝赋值。这里我可以分享一个踩过的坑当年我在做一个模拟项目任务是把一组配置结构体复制到另一份再修改。结构体里有一个int*成员我直接整个数组赋值结果新数组的指针还指向旧数组的堆内存。改新数组的数据时旧数组的数据也变了。查了很久才发现是浅拷贝的问题。从那以后我对所有含指针或容器成员的结构体都会先问自己一个问题这个“复制”是想要共享还是想要独立。6.4 一个简单的综合示例最后把本文的要点整合成一段小代码展示定义结构体、创建结构体数组、初始化和按成绩排序的完整流程你可以直接跑起来看看#include iostream #include string #include algorithm struct Student { std::string name; int id; double score; }; int main() { Student students[] { {Alice, 1001, 90.5}, {Bob, 1002, 88.0}, {Cindy, 1003, 92.5} }; const int n sizeof(students) / sizeof(students[0]); std::sort(students, students n, [](const Student a, const Student b) { return a.score b.score; }); for (const Student s : students) { std::cout s.name s.id s.score std::endl; } return 0; }如果你认真跑过这段代码应该能体验到我前面说的几个点结构体数组可以像普通数组一样遍历范围 for 配合引用可以减少复制std::sort配合 lambda 可以清晰表达排序规则。而这些都是“结构体 数组”这个组合真正有价值的地方。我自己刚学 C 的时候总觉得结构体不过是一个“大一点的变量”。直到写的数据量越来越大开始关注内存、关注排序效率、关注复制代价之后才开始明白结构体决定了数据怎么被看待结构体数组决定了数据怎么被组织。这两者合在一起才是处理批量实体数据的起点。如果你现在还在为“什么时候用数组、什么时候用 vector、结构体参数怎么传”这些问题纠结不用急把这几个基础场景写多几遍手感自然就来了。

相关新闻

Spring Boot JPA 实战指南:从实体映射到查询优化的避坑经验

Spring Boot JPA 实战指南:从实体映射到查询优化的避坑经验

1. 为什么我要认真聊聊 Spring Boot 里的 JPA做后端开发这些年,我接触过的持久层方案不算少。从最早手写 JDBC 拼 SQL,到后来用 MyBatis 写 XML 映射文件,再到 Spring Boot 项目里大量使用 JPA,每一种方案都有它适合的场景。但要说…

2026/10/13 20:11:07 阅读更多 →
Sufer 8.0英文版等值线图实战:从散点插值到批量出图的完整避坑指南

Sufer 8.0英文版等值线图实战:从散点插值到批量出图的完整避坑指南

简介:Surfer 8.0英文版是Golden Software公司推出的专业地形图绘制与三维建模软件,面向地质、环境科学、工程及地球科学领域的从业者与学习者,用于将离散数据转化为连续格网表面并生成专业图件。压缩包共234个文件,约12.41MB&…

2026/10/13 20:10:07 阅读更多 →
校园外卖系统数据库课设实战:三端闭环与状态机设计

校园外卖系统数据库课设实战:三端闭环与状态机设计

简介:本资源是一套完整的微信小程序校园外卖系统课程设计实践项目,面向高校数据库原理与应用、Web前端开发及小程序实战类课程的学生与教师,解决课程设计中缺乏真实业务场景、前后端协同与数据库建模结合案例的问题。压缩包共123个文件&#…

2026/10/13 20:10:07 阅读更多 →

最新新闻

鸽哒IM源码:生产级私有化即时通讯系统解析

鸽哒IM源码:生产级私有化即时通讯系统解析

简介:这是一套完整的跨平台即时通讯(IM)系统源码,面向中高级Java/Android/iOS开发者及全栈工程师,用于学习和搭建企业级实时通信服务。资源涵盖Web(WebSocketHTML5)、Android(JavaFC…

2026/10/13 21:08:58 阅读更多 →
Python+React低代码平台:元数据驱动的业务系统快速构建方案

Python+React低代码平台:元数据驱动的业务系统快速构建方案

简介:这是一套面向全栈开发初学者与低代码实践者的前后端一体化低代码平台学习资源,聚焦React前端框架与Python后端框架的协同开发模式,帮助非专业开发者快速构建可运行的Web应用,降低传统编码门槛。资源共229个文件,涵…

2026/10/13 21:08:58 阅读更多 →
2020数学建模B题沙漠穿越:Matlab动态规划与剪枝实战解析

2020数学建模B题沙漠穿越:Matlab动态规划与剪枝实战解析

简介:面向备战2020年数学建模B题的参赛者,完整Matlab源代码覆盖四关求解流程,从第一关基础路径规划到第四关随机天气下的最优策略,涉及距离矩阵处理、单目标优化、动态规划、数学期望与随机分布建模等方法。第二关重点解决双地图与…

2026/10/13 21:08:58 阅读更多 →
CEF4Delphi 控件:Delphi 与 JavaScript 双向通信及避坑指南

CEF4Delphi 控件:Delphi 与 JavaScript 双向通信及避坑指南

简介:CEF4Delphi控件是一套面向Delphi开发者的Chromium谷歌内核浏览器集成方案,适合需要在桌面程序中嵌入现代网页渲染能力的开发者,覆盖从Delphi 7到Delphi 10.2的多个版本,作者已亲自测试调试安装正常。资源包共462个文件&#…

2026/10/13 21:08:58 阅读更多 →
数据库大作业图书销售系统:核心是约束设计而不是页面数量

数据库大作业图书销售系统:核心是约束设计而不是页面数量

简介:面向数据库课程大作业的图书销售管理系统数据库设计文档,完整覆盖从问题描述、需求分析、概念模型设计、逻辑模型设计到建立数据库、录入数据及数据库操作的全过程,并配有全局与局部ER图、表结构、外键关系说明,以及实施中遇…

2026/10/13 21:08:58 阅读更多 →
Java 8 Stream 实战指南:从传统循环到声明式流水线

Java 8 Stream 实战指南:从传统循环到声明式流水线

如果你写过几年 Java 业务代码,大概率见过这类场景:一个方法里塞了三四个 for 循环,循环里面套 if,if 里面再往 List 或 Map 里 put 数据,看完要花好几分钟才理清楚这段逻辑到底在干嘛。这种代码不能说错,但…

2026/10/13 21:07:57 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 18:11:17 阅读更多 →