VC环境下Shapefile矢量编辑:从字节解析到内存映射实战
简介这是一份面向GIS初学者与专业开发者的Shapefile矢量编辑工具源码包基于Visual C编写用于对点、线、多边形等地理要素进行创建、修改与删除并支持属性数据编辑、坐标系统转换及缓冲区、叠加等基础空间分析适合学习矢量数据编辑原理或二次开发。压缩包共167个文件以53个C头文件与49个cpp源文件为核心辅以24个ico图标、22个bmp位图及bin、rc等资源文件整体约1.43MB工程结构完整可直接编译研究。目前已有335人学习下载。资源涵盖移动、修改、十字光标等编辑工具模块界面与工具栏素材齐备读者可借此掌握Shapefile文件结构、VC图形界面开发与GIS编辑功能实现思路是理解矢量编辑底层逻辑的实用参考。1. 从一份 VC 工程说起shp 编辑器到底在编辑什么很多人第一次拿到Shp__Editor__x.rar这种命名的压缩包会下意识以为里面是个绿色小工具双击就能改 shp。实际拆开看它往往是一份用 VCVisual C写的 MFC 或 Win32 工程核心逻辑围绕 shp 文件的读写、图层渲染和要素编辑展开。shp 本身不是单个文件而是.shp、.shx、.dbf三件套前者存几何中者存索引后者存属性。所谓“矢量编辑”本质是在内存里维护一份几何对象列表改完之后按 ESRI Shapefile 规范重新落盘。这个方向适合两类人一类是 GIS 桌面端开发者想在自己的 VC 程序里嵌入轻量级 shp 编辑能力不依赖 ArcGIS 那套重运行时另一类是数据处理工程师手头有大量 shp 需要批量改字段、裁几何、转格式想搞明白底层到底发生了什么。热搜里常出现“dwg转shp”“shp转txt”“shp转3dtiles”这些需求背后都绕不开同一个问题你得先能正确读写 shp才谈得上转换和编辑。下面按“先立住原理再动手复现”的节奏把这条路走一遍。2. 拆解 shp 三件套VC 工程里到底要读写哪些字节2.1 主文件、索引文件、属性表的分工shp 主文件由 100 字节文件头和若干变长记录组成。文件头里最关键的是第 24 字节开始的 4 字节文件长度以 16 位字为单位以及第 32 字节开始的 4 字节版本号固定 1000。每个记录前有 8 字节记录头前 4 字节是记录号从 1 开始大端后 4 字节是内容长度也是 16 位字大端。记录内容第一个 4 字节是形状类型后面才是坐标数据。shx 文件结构更简单100 字节文件头加若干 8 字节索引项每项记录对应记录在 shp 中的偏移和长度单位同样是 16 位字。dbf 则是 dBASE III 格式头 32 字节描述记录数和头长度之后是字段描述区再之后是记录区。VC 工程里如果只读 shp 不读 dbf属性就丢了只读 dbf 不读 shx随机访问会退化成顺序扫描。提示判断一个 shp 是否完整先看同名.shx和.dbf是否存在再看.shp文件头第 24 字节的文件长度是否和实际文件大小吻合。不吻合基本可以判定文件被截断或拼接错了。2.2 用 VC 读文件头的最小代码骨架下面这段代码演示在 VC 里用标准 C 文件接口读取 shp 文件头不依赖任何 GIS 库。注意所有多字节整数在 shp 里都是大端存储x86 平台需要手动翻转。#include stdio.h #include stdint.h typedef struct { int32_t fileCode; // 固定 9994大端 int32_t unused[5]; int32_t fileLength; // 文件总长度单位 16 位字大端 int32_t version; // 固定 1000小端 int32_t shapeType; // 形状类型小端 double xMin, yMin, xMax, yMax; // 包围盒小端 } ShpHeader; static int32_t swap32(int32_t v) { return ((v 0xFF) 24) | ((v 0xFF00) 8) | ((v 8) 0xFF00) | ((v 24) 0xFF); } int read_shp_header(const char *path, ShpHeader *out) { FILE *fp fopen(path, rb); if (!fp) return -1; uint8_t buf[100]; if (fread(buf, 1, 100, fp) ! 100) { fclose(fp); return -2; } // 前 24 字节按大端解析 out-fileCode swap32(*(int32_t*)(buf 0)); out-fileLength swap32(*(int32_t*)(buf 24)); // 从第 28 字节起按小端解析 out-version *(int32_t*)(buf 28); out-shapeType *(int32_t*)(buf 32); out-xMin *(double*)(buf 36); out-yMin *(double*)(buf 44); out-xMax *(double*)(buf 52); out-yMax *(double*)(buf 60); fclose(fp); return 0; }逻辑说明swap32只处理大端到小端的转换shp 文件头里只有前 28 字节是大端从第 28 字节的 version 开始全部是小端所以不能整块翻转。参数说明fileCode必须是 9994否则不是合法 shpfileLength乘以 2 才是字节数shapeType常见值 1 是点、3 是折线、5 是多边形、8 是多点。读完之后用fileLength * 2和ftell得到的实际大小对比能快速判断文件是否完整。2.3 记录级读取与形状类型分支读完文件头只是第一步真正编辑要素要逐记录解析。每条记录的结构是8 字节记录头记录号 内容长度均大端然后是内容。内容第一个 4 字节是形状类型小端之后按类型不同解析。点类型后面直接跟两个 double 的 x、y折线类型后面跟包围盒4 个 double、部件数int32、点数int32、部件索引数组、点数组。多边形和折线结构一致只是闭合方式不同。在 VC 工程里常见做法是定义一个ShpRecord结构体把几何和属性分开存。属性从 dbf 按记录号对应读取。这里有个血泪经验dbf 的记录顺序和 shp 的记录顺序理论上一致但有些工具导出的文件会错位所以编辑前最好先做一次记录号对齐校验否则改完属性发现对不上几何返工成本很高。typedef struct { int32_t recordNo; int32_t shapeType; int32_t numParts; int32_t numPoints; int32_t *parts; double *x; double *y; } ShpRecord; int read_record(FILE *fp, ShpRecord *rec) { uint8_t rh[8]; if (fread(rh, 1, 8, fp) ! 8) return -1; rec-recordNo swap32(*(int32_t*)rh); int32_t contentLen swap32(*(int32_t*)(rh 4)); uint8_t *content (uint8_t*)malloc(contentLen * 2); fread(content, 1, contentLen * 2, fp); rec-shapeType *(int32_t*)content; if (rec-shapeType 1) { rec-numParts 1; rec-numPoints 1; rec-x (double*)malloc(8); rec-y (double*)malloc(8); rec-x[0] *(double*)(content 4); rec-y[0] *(double*)(content 12); } else if (rec-shapeType 3 || rec-shapeType 5) { rec-numParts *(int32_t*)(content 36); rec-numPoints *(int32_t*)(content 40); int off 44; rec-parts (int32_t*)malloc(rec-numParts * 4); for (int i 0; i rec-numParts; i) rec-parts[i] *(int32_t*)(content off i * 4); off rec-numParts * 4; rec-x (double*)malloc(rec-numPoints * 8); rec-y (double*)malloc(rec-numPoints * 8); for (int i 0; i rec-numPoints; i) { rec-x[i] *(double*)(content off i * 16); rec-y[i] *(double*)(content off i * 16 8); } } free(content); return 0; }逻辑说明记录头 8 字节里记录号从 1 开始内容长度单位是 16 位字所以实际字节数是contentLen * 2。形状类型 1 的点记录内容只有 20 字节4 字节类型 16 字节坐标折线和多边形的内容长度随部件数和点数变化。参数说明parts数组存的是每个部件在点数组里的起始索引比如两个部件就是[0, 10]表示第一个部件用点 0 到 9第二个从 10 开始。编辑多边形时如果只改点不改 parts会出现部件错乱这是最常见的翻车点之一。3. 在 VC 里搭一个能跑的最小编辑闭环3.1 工程配置与依赖选择用 VC6 还是 VS 高版本取决于你手头工程的历史包袱。如果标题里的vc指的是 VC6那要注意它默认的 C 编译器对stdint.h支持不好得自己 typedef。常见做法是新建一个 Win32 Console 工程把 shp 读写封装成独立的.c和.hUI 层用 MFC 的CView派生类做绘图。不推荐一上来就上 GDAL虽然 GDAL 能省很多事但编译体积大而且一旦依赖它你就很难看清 shp 的字节级细节出了问题只能当黑匣子。如果只是做批量编辑不需要界面那更简单一个控制台程序参数是输入 shp 路径和操作类型输出新 shp。这样调试方便也容易集成到自动化流程里。热搜里“shp转txt”这类需求本质上就是读 shp 然后按格式写文本用控制台程序最合适。3.2 写回 shp 时的长度回填编辑完内存里的记录后写回 shp 有个关键动作文件头的fileLength和每条记录的contentLen必须在写完之后回填。因为写之前你不知道最终文件多大。常见做法是先写一个占位的 100 字节头然后逐记录写同时累加偏移最后fseek回文件头第 24 字节写入正确的长度。shx 也要同步生成否则很多 GIS 软件打不开。int write_shp(const char *path, ShpRecord *recs, int count, int shapeType) { FILE *fp fopen(path, wb); uint8_t header[100] {0}; // 先写占位头 fwrite(header, 1, 100, fp); long offset 50; // 单位 16 位字100 字节 50 字 for (int i 0; i count; i) { ShpRecord *r recs[i]; int32_t contentLen 0; if (r-shapeType 1) contentLen 10; // 4 16 20 字节 10 字 else contentLen 22 r-numParts * 2 r-numPoints * 8; // 写记录头 int32_t recNo swap32(i 1); int32_t clen swap32(contentLen); fwrite(recNo, 4, 1, fp); fwrite(clen, 4, 1, fp); // 写内容 fwrite(r-shapeType, 4, 1, fp); if (r-shapeType 1) { fwrite(r-x[0], 8, 1, fp); fwrite(r-y[0], 8, 1, fp); } else { double box[4] {0,0,0,0}; fwrite(box, 8, 4, fp); fwrite(r-numParts, 4, 1, fp); fwrite(r-numPoints, 4, 1, fp); fwrite(r-parts, 4, r-numParts, fp); for (int j 0; j r-numPoints; j) { fwrite(r-x[j], 8, 1, fp); fwrite(r-y[j], 8, 1, fp); } } offset 4 contentLen; // 记录头 4 字 内容 } // 回填文件头 int32_t fileCode swap32(9994); int32_t fileLen swap32((int32_t)offset); int32_t version 1000; fseek(fp, 0, SEEK_SET); fwrite(fileCode, 4, 1, fp); fseek(fp, 24, SEEK_SET); fwrite(fileLen, 4, 1, fp); fseek(fp, 28, SEEK_SET); fwrite(version, 4, 1, fp); fwrite(shapeType, 4, 1, fp); fclose(fp); return 0; }逻辑说明offset初始为 50 是因为文件头 100 字节等于 50 个 16 位字。每条记录写入后offset增加4 contentLen其中 4 是记录头 8 字节折合的 4 个字。参数说明contentLen的计算必须和实际写入字节数严格一致点类型是 20 字节即 10 字折线/多边形是 4 字节类型 32 字节包围盒 4 字节部件数 4 字节点数 部件索引 点坐标折合字数就是22 numParts * 2 numPoints * 8。写完之后一定要用 GIS 软件打开验证或者用前面读文件头的代码检查fileLength是否等于实际文件大小除以 2。3.3 属性表 dbf 的同步修改几何改完属性表也得跟着改。dbf 的头 32 字节里第 4 到第 7 字节是记录数小端第 8 到第 9 字节是头长度小端第 10 到第 11 字节是记录长度小端。字段描述区每个字段 32 字节字段名在头 11 字节类型在第 11 字节长度在第 16 字节。改属性时如果字段长度不变直接定位到记录区覆盖写如果要加字段或改长度就得重建整个 dbf因为记录长度变了所有记录的偏移都会变。常见做法是读原 dbf 到内存解析出字段列表和所有记录修改后按新结构重新写一个 dbf。注意 dbf 的记录区每条记录第一个字节是删除标记空格表示有效星号表示已删除。写的时候别忘了这个字节否则 GIS 软件可能读不出记录。热搜里“shp格式矢量数据导出为wkt”这类操作属性通常也要一起导出所以 dbf 的解析不能跳过。4. 避坑与排查shp 编辑里最容易翻车的 5 个点4.1 现象改完 shp 在 ArcGIS 里打开报“无效的几何”原因多边形环的方向不对。ESRI Shapefile 规范要求外环顺时针内环逆时针。很多自己写的编辑器只存点不检查环绕方向写回去之后 ArcGIS 校验失败。解决在写多边形记录前用鞋带公式算有向面积面积为正说明是逆时针需要反转点序。内环则相反。这个坑在“渔网分割shp”这类批量生成多边形的场景里尤其常见。4.2 现象shx 文件缺失或长度不对导致随机访问崩溃原因只写了 shp 没写 shx或者 shx 的偏移量单位搞错了。shx 里每个索引项的偏移和长度单位都是 16 位字不是字节。解决写 shp 的同时同步写 shx每写一条记录就往 shx 里追加 8 字节偏移量用当前 shp 的 16 位字偏移。写完用fileLength校验 shx 文件头里的文件长度是否等于50 记录数 * 4。4.3 现象dbf 中文属性乱码原因dbf 的字符编码没有统一标准有的用 GBK有的用 UTF-8还有的用 Latin-1。VC 工程里如果按char直接读写遇到 UTF-8 的多字节字符会截断。解决先读 dbf 头第 29 字节的语言驱动标识0x4D 表示 GBK0x57 表示 UTF-8 但很多工具不写这个字节。稳妥做法是提供编码选项让用户指定或者用 ICU 做探测。血泪经验不要假设 dbf 一定是 GBK跨平台数据交换时乱码概率极高。4.4 现象编辑后文件体积暴涨原因每次编辑都重建整个文件没有复用原有记录空间。或者点坐标用double存但实际精度只需要float写的时候没做压缩。解决如果只是改属性不要重写 shp如果改几何但点数不变可以原地覆盖记录内容只更新包围盒。另外检查是否误把 shx 的索引项写成了 16 字节那会让文件翻倍。4.5 现象VC6 编译报stdint.h找不到原因VC6 的 CRT 太老没有 C99 标准头。解决自己写一个stdint.h替代typedef__int32为int32_t__int64为int64_t。或者直接用long和__int64但要注意 32 位和 64 位下的长度差异。这个坑在维护老工程时几乎必遇提前准备好兼容头能省很多时间。5. 进阶用内存映射加速大 shp 的批量编辑当 shp 文件超过几百 MB用fread逐记录读会明显变慢。我一般会改用内存映射把整个 shp 映射到进程地址空间然后直接在映射区上解析和修改。Windows 下用CreateFileMapping和MapViewOfFileLinux 下用mmap。这样读文件头、定位记录、改坐标都是内存操作速度提升很明显。具体做法先用CreateFile打开 shp拿到文件句柄然后CreateFileMapping创建一个只读或读写的映射对象再MapViewOfFile得到基地址指针。之后所有解析都基于这个指针加偏移。改完之后如果是读写映射FlushViewOfFile刷回磁盘。注意映射大小要按系统分配粒度对齐通常 64KB。shx 和 dbf 也可以同样处理但 dbf 的记录长度可变映射后修改要小心越界。验证方法拿一个 500MB 左右的 shp分别用fread和内存映射跑一遍“读取所有点并计算包围盒”对比耗时。通常映射版本能快 2 到 3 倍。但内存映射不是后悔药如果文件在编辑过程中被其他进程截断映射区会触发异常所以生产环境里要加文件锁或版本校验。一个具体技巧在映射区上改点坐标时不要逐点memcpy而是把点数组当成double*直接索引因为 shp 里的点就是连续的double对。这样改一个点就是((double*)base)[offset] newVal比任何库函数都快。但前提是你已经正确解析了记录偏移否则会写穿到别的记录里这种翻车现场调试起来非常痛苦。我自己的习惯是任何写操作之前先做一次只读遍历把所有记录的偏移和长度记到一个数组里确认无误后再开写。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

PdfiumLib 实战:.NET 高效 PDF 转图片与中文渲染避坑指南

PdfiumLib 实战:.NET 高效 PDF 转图片与中文渲染避坑指南

简介:本资源面向需要在 Delphi 10.4.2 VCL 环境下实现 PDF 转图片的开发者,基于开源 PdfiumLib 封装 Google Chromium 项目的 Pdfium 渲染能力,解决 PDF 文档批量转换为 JPEG、PNG 等图像格式的需求,适用于预览、打印、分享与在线…

2026/10/10 13:21:20 阅读更多 →
TRISIS工控恶意代码深度剖析:SIS攻击链与防护实战

TRISIS工控恶意代码深度剖析:SIS攻击链与防护实战

1. 从一次工控安全事件说起:TRISIS到底触动了哪根神经工控安全圈子里,TRISIS(也有人叫它TRITON、HATMAN)是一个绕不开的名字。它不像那些横扫办公网络的勒索软件那样闹得满城风雨,但在搞工业控制系统安全的人眼里&…

2026/10/10 13:21:20 阅读更多 →
不写一行代码:用 BrowserSkill 让 AI 替你完成一次真实网页任务的全流程

不写一行代码:用 BrowserSkill 让 AI 替你完成一次真实网页任务的全流程

不写一行代码:用 BrowserSkill 让 AI 替你完成一次真实网页任务的全流程 【免费下载链接】BrowserSkill Let AI agents use your real, logged-in browser without interrupting your work. CLI extension for browser automation across any shell-capable AI age…

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

最新新闻

软件测试面试题全解析:从基础理论到AI与物联网实战

软件测试面试题全解析:从基础理论到AI与物联网实战

软件测试面试题这个话题,每年都能收到一堆私信。有人刷了一周八股文还是挂在一面,有人只准备了两天却拿到了不错的offer。核心区别不在于背了多少题,而在于有没有把题目背后的考察点摸透。我整理了这份软件测试面试常见问题清单,附…

2026/10/10 14:08:50 阅读更多 →
C++ unordered_map与unordered_set详解:哈希表原理、接口用法与性能优化

C++ unordered_map与unordered_set详解:哈希表原理、接口用法与性能优化

用过 C 的都知道,当你还在用map、set做查找和去重的时候,数据量一旦上来,心里多少会有点不踏实。这时候就该unordered_map和unordered_set登场了。这两个容器在 C11 里正式进入标准库,核心卖点就一句话:基于哈希表实现…

2026/10/10 14:08:50 阅读更多 →
Linux新手第一周:环境搭建、常用命令与学习路线全记录

Linux新手第一周:环境搭建、常用命令与学习路线全记录

几个月前,社团面试的场景还在眼前,转眼第一周周报已经躺在群文件里。说实话,接手【西邮 Linux 兴趣小组】的第一周,我最大的感受不是“教了多少东西”,而是“被一群刚接触 Linux 的新人追着问问题,自己回头…

2026/10/10 14:08:50 阅读更多 →
踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱

踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱

踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱 【免费下载链接】all-MiniLM-L6-v2 项目地址: https://ai.gitcode.com/hf_mirrors/sentence-transformers/all-MiniLM-L6-v2 把 all-MiniLM-L6-v2 接进 RAG 管线,几…

2026/10/10 14:08:50 阅读更多 →
2026年AI Agent发展趋势与挑战:从理论到实践的跨越,TaoToken统一Key打通OpenClaw落地链路

2026年AI Agent发展趋势与挑战:从理论到实践的跨越,TaoToken统一Key打通OpenClaw落地链路

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

2026/10/10 14:08:50 阅读更多 →
Harness 工程安全基线:为 AI Agent 编写 SECURITY.md 安全策略文件

Harness 工程安全基线:为 AI Agent 编写 SECURITY.md 安全策略文件

【免费下载链接】learn-harness-engineering Harness engineering beginner tutorial, from 0 to 1 项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineering 点击查看 免费下载 SECURITY.md 是面向 Agent 的仓库(agent-first reposito…

2026/10/10 14:07:49 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/10 11:14: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/10 5:23: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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →