C语言scanf函数深度解析:缓冲区机制与安全输入实践
1. 项目概述为什么scanf()值得你花时间深究在C语言的世界里scanf()函数就像一把万能钥匙负责从标准输入通常是键盘读取数据。无论是初学C语言的新手还是需要处理简单控制台输入的老手都绕不开它。但正是这个看似简单的函数却成了无数程序员的“新手墙”——输入一个数字程序正常输入一个字符可能就跳过了输入带空格的字符串直接给你截断。这些问题背后是scanf()对数字、字符、字符串截然不同的处理逻辑和缓冲区机制。我见过太多项目前期逻辑写得天花乱坠最后却因为输入处理不严谨导致程序在交互环节崩溃或行为异常。尤其是在嵌入式开发、算法竞赛快速原型搭建或是教学演示程序中一个健壮的输入处理能省去大量调试时间。今天我们就来彻底拆解scanf()不光是记住格式符%d、%c、%s更要弄明白它背后的“为什么”为什么读取字符时会“吃掉”回车为什么字符串读取遇到空格就停止为什么有时需要在格式字符串里加空格掌握了这些你就能预判并规避绝大多数输入相关的坑写出更稳定、更符合预期的代码。2. 核心原理scanf()的工作机制与缓冲区陷阱要驾驭scanf()绝不能把它当成一个黑盒。你必须理解它的工作流程核心就在于输入缓冲区。2.1 输入缓冲区与格式匹配流程当你从键盘键入内容并按下回车键后你输入的所有字符包括你最后按下的那个回车符在C中表示为\n并不会直接交给scanf()而是先被存入一个叫做标准输入缓冲区的临时区域。scanf()函数被调用时才会从这个缓冲区中按照你指定的格式字符串进行“读取”和“匹配”。这个过程可以分解为几个关键步骤跳过前导空白字符对于大多数格式说明符如%d%f%sscanf()会首先自动忽略并丢弃缓冲区开头所有的空白字符包括空格、制表符\t、换行符\n直到遇到第一个非空白字符为止。这是它能正确读取数字的关键。尝试匹配与转换根据格式说明符scanf()尝试从当前缓冲区位置开始匹配符合预期的字符序列。%d期望匹配一个可能带正负号的十进制整数序列。遇到非数字字符如字母、标点即停止并将之前匹配的数字字符转换为整型值。%f期望匹配一个浮点数。%c这是一个特例它不跳过任何空白字符直接读取缓冲区中的下一个字符无论它是字母、数字还是空格、回车。%s期望匹配一个非空白字符序列。它会读取字符直到遇到空白字符空格、\t、\n为止并在末尾自动添加字符串结束符\0。这意味着它无法直接读取包含空格的句子。匹配成功与消耗如果匹配成功相应的值被存入你提供的变量地址中同时这些被成功读取的字符会从输入缓冲区中移除。匹配失败如果一开始就无法匹配例如缓冲区第一个非空白字符对%d来说不是数字scanf()会立即返回并返回一个小于预期参数个数的值通常可用于判断输入错误且缓冲区中的字符保持不变等待下一次读取。这常常是导致程序陷入死循环或逻辑错误的原因。注意scanf()的返回值是一个非常重要的工具它返回的是成功匹配并赋值的输入项数量。务必养成检查其返回值的习惯例如if (scanf(“%d”, num) ! 1) { /* 处理输入错误 */ }。2.2 缓冲区残留问题详解理解了流程我们就能解释最常见的“灵异现象”。假设有以下代码int age; char grade; printf(“请输入年龄: “); scanf(“%d”, age); // 用户输入 18 然后回车 printf(“请输入等级(A/B/C): “); scanf(“%c”, grade); // 问题出现用户第一次输入18并回车后缓冲区里的内容是’1’‘8’‘\n’。第一个scanf(“%d”, age)会读取数字18遇到\n停止18被转换并存入age字符’1’‘8’从缓冲区移除。此时缓冲区里只剩下一个孤零零的’\n’。紧接着第二个scanf(“%c”, grade)上场。记住%c不跳过空白字符它直接读取缓冲区中的下一个字符也就是那个’\n’回车符然后将其存入grade变量。于是程序看起来就像“跳过”了第二次输入直接结束了。这就是经典的“缓冲区残留换行符”问题。对于%s因为它会跳过前导空白所以如果残留的是空白字符它通常能正常工作。但如果你在%s之前有其他输入且缓冲区留有非空白字符同样会出问题。3. 分类型精讲数字、字符、字符串的读取实战知道了原理我们来看具体怎么用以及如何应对各种情况。3.1 数字的读取%d, %f, %lf等读取数字是scanf()最稳定的功能之一因为它会自动跳过空白字符。基础用法int num; float price; double precision; scanf(“%d”, num); // 读取整数注意变量前的 符号取地址必不可少 scanf(“%f”, price); // 读取单精度浮点数 scanf(“%lf”, precision); // 读取双精度浮点数格式符是 %lf (long float)读取多个数字格式字符串中可以连续指定多个格式符它们会依次匹配。int x, y; scanf(“%d%d”, x, y); // 可以输入 “10 20” 或 “10\n20”这里第一个%d读取完10后缓冲区可能还有空格或回车第二个%d会主动跳过它们然后读取20。指定输入格式高级用法你可以用scanf()解析有固定格式的输入。int year, month, day; scanf(“%d-%d-%d”, year, month, day); // 输入 “2023-11-27”格式字符串中的非格式说明符如-必须与输入流中的字符严格匹配。如果输入是2023/11/27匹配就会失败。实操心得符号不能忘对于基本类型变量int,float,char等scanf()需要变量的内存地址来写入数据所以是必须的。这是新手最常犯的错误之一。浮点数精度float用%fdouble一定要用%lf。虽然在printf中double也可以用%f输出但在scanf中区分是严格的。处理非法输入如果要求输入数字但用户输入了字母scanf(“%d”, num)会匹配失败返回0并且非法字符会留在缓冲区影响后续所有读取。必须清空缓冲区。一个简单但不完美的方法是while (getchar() ! ‘\n’);这行代码会读取并丢弃缓冲区中直到换行符的所有字符。3.2 字符的读取%c 的“固执”与应对%c是最“单纯”也最容易出问题的格式符因为它见什么读什么。基础与陷阱char ch; scanf(“%c”, ch); // 读取用户输入的下一个字符可能是字母也可能是空格、回车、制表符。如何安全地读取一个“有效字符”比如字母关键在于清除%c之前可能存在的空白字符。有几种常用方法在%c前加一个空格格式字符串中的空格会匹配并消耗任意数量的空白字符。int age; char grade; scanf(“%d”, age); scanf(” %c”, grade); // 注意 %c 前面有一个空格这会跳过 age 输入后留下的回车符。这个空格告诉scanf“先跳过所有空白字符再读取下一个非空白字符给我。”这是最简洁有效的办法。使用getchar()手动清除int age; char grade; scanf(“%d”, age); getchar(); // 读取并丢弃掉缓冲区里的回车符 scanf(“%c”, grade); // 现在可以安全读取用户新输入的字符了这种方法更直观但要注意如果缓冲区里不止一个字符比如用户输入了18abc然后回车一个getchar()只能清掉一个。读取多个字符%c可以连续使用但同样要注意空白字符问题。char a, b; scanf(“%c%c”, a, b); // 如果输入 “X Y”a’X’ b’ ‘空格 // 更常见的用法是读取连续字符如 “AB”则 a’A’ b’B’常见问题为什么我的程序不让我输入字符就直接跳过了99%的原因是之前读取数字或字符串后缓冲区留下了\n被后续的%c直接读取了。解决方案就是上面提到的加空格或手动清空。我想读取一个包括空格在内的完整句子怎么办%c一次只读一个字符你需要用循环。但更常见的需求是读字符串这引出了下一个话题。3.3 字符串的读取%s 的局限与替代方案%s用于读取一个单词不带空格的字符串。基础用法与一个关键问题char name[20]; scanf(“%s”, name); // 输入 “John Doe” name 里只会得到 “John”注意name是一个字符数组数组名本身就代表了数组的首地址所以这里不需要符号。这是%s和%d、%c在语法上的一个重要区别。为什么%s不需要因为对于数组如char name[20]name这个标识符在大多数表达式中会被转换为指向其第一个元素的指针即name[0]。scanf()需要的就是这个地址以便将读取的字符串存入从该地址开始的内存中。所以直接写name即可。%s 的严重缺陷无法读取带空格的字符串遇到空格、\t、\n就停止。有缓冲区溢出风险scanf(“%s”, buf)不会检查buf数组的大小。如果用户输入超过数组长度的字符就会发生溢出覆盖相邻内存导致程序崩溃或安全漏洞著名的“缓冲区溢出攻击”就源于此类问题。安全读取字符串的替代方案使用带宽度限制的 %schar city[10]; scanf(“%9s”, city); // 最多读取9个字符为末尾的 ‘\0’ 留出空间这能防止溢出但依然无法读取空格。使用 fgets() 函数强烈推荐char sentence[100]; fgets(sentence, sizeof(sentence), stdin); // 从标准输入读取最多读入 sizeof(sentence)-1 个字符保留换行符fgets()会读取整行包括空格直到遇到换行符或达到指定大小。它会自动在末尾添加\0。注意它会把换行符\n也存储进来有时你需要手动去除它sentence[strcspn(sentence, “\n”)] ‘\0’; // 找到 ‘\n’ 并将其替换为 ‘\0’使用 scanf 的 scanset 功能%[ ]char line[100]; scanf(“%[^\n]”, line); // %[^\n] 表示读取所有字符直到遇到换行符为止 getchar(); // 读取并丢弃末尾的换行符防止影响下次输入这可以读取带空格的整行但和%s一样有溢出风险通常也需要结合宽度限制使用scanf(“%99[^\n]”, line)。实操心得对于简单的单词输入使用带宽度限制的%s是OK的。对于任何需要读取一行文本、可能包含空格的情况毫不犹豫地选择fgets()。它是更安全、功能更专一的选择。永远不要使用不设防的scanf(“%s”, buf)尤其是在生产代码或作业项目中这是一个严重的坏习惯。4. 混合输入与高级技巧实战实际编程中往往需要混合读取数字、字符和字符串。这是考验你对缓冲区理解深度的时刻。4.1 混合读取的经典模式与清障场景一先数字后字符串单词int id; char product[50]; printf(“Enter ID: “); scanf(“%d”, id); printf(“Enter Product Name: “); scanf(“%49s”, product); // %s 会自动跳过 id 后面的空白符所以这里通常没问题这个组合比较安全因为%s会跳过数字后的空白符。场景二先数字后字符串整行可能含空格int age; char bio[200]; printf(“Enter age: “); scanf(“%d”, age); while (getchar() ! ‘\n’); // 关键清掉 age 后面的回车符及其他可能字符 printf(“Enter bio: “); fgets(bio, sizeof(bio), stdin); // 使用 fgets 安全读取整行 bio[strcspn(bio, “\n”)] ‘\0’; // 去掉 fgets 带来的换行符如果不加while (getchar() ! ‘\n’);fgets()会立刻读到缓冲区里残留的\n从而直接返回一个空行。场景三先数字/字符串后单个字符int score; char level; printf(“Enter score: “); scanf(“%d”, score); printf(“Enter level (A/B/C): “); scanf(” %c”, level); // %c 前的空格至关重要或者char name[50]; char confirm; printf(“Enter name: “); scanf(“%49s”, name); printf(“Confirm? (y/n): “); scanf(” %c”, confirm); // 同样需要空格来跳过 name 后面的空白符4.2 错误处理与输入验证一个健壮的程序必须能处理用户的错误输入。检查 scanf 返回值int num; printf(“Please enter an integer: “); while (scanf(“%d”, num) ! 1) { // 如果输入的不是整数返回值不是1 printf(“Invalid input. Please enter an integer: “); // 清空错误输入 while (getchar() ! ‘\n’); // 丢弃缓冲区中所有错误字符直到换行 } printf(“You entered: %d\n”, num);这个循环会一直持续直到用户输入一个有效的整数。综合示例一个简单的学生信息录入#include stdio.h #include string.h int main() { int id, age; char name[100]; char grade; // 1. 读取ID printf(“Enter Student ID: “); while (scanf(“%d”, id) ! 1) { printf(“Invalid ID. Please enter a number: “); while (getchar() ! ‘\n’); } // 2. 清空缓冲区为读取整行姓名做准备 while (getchar() ! ‘\n’); // 3. 读取姓名可能包含空格 printf(“Enter Full Name: “); if (fgets(name, sizeof(name), stdin) ! NULL) { name[strcspn(name, “\n”)] ‘\0’; // 去除换行符 } // 4. 读取年龄 printf(“Enter Age: “); while (scanf(“%d”, age) ! 1 || age 0 || age 120) { // 加入范围验证 printf(“Invalid age. Please enter a number between 1 and 120: “); while (getchar() ! ‘\n’); } // 5. 读取等级跳过年龄输入后的回车 printf(“Enter Grade (A/B/C/D/F): “); scanf(” %c”, grade); // 注意空格 // 可选验证 grade 是否在合法范围内 while (getchar() ! ‘\n’); // 清掉后续可能的多余字符如用户输入了 ‘A’ // 输出信息 printf(“\n— Student Info —\n”); printf(“ID: %d\n”, id); printf(“Name: %s\n”, name); printf(“Age: %d\n”, age); printf(“Grade: %c\n”, grade); return 0; }5. 常见问题排查与终极建议即使理解了原理实际编码时还是会遇到各种奇怪的问题。这里记录一些典型的“坑”和解决方法。5.1 问题速查表问题现象可能原因解决方案程序在scanf(“%c”, …)处似乎被跳过没有等待输入输入缓冲区中残留了之前的换行符\n。1. 在%c前加空格scanf(” %c”, ch)。2. 在读取%c前使用getchar()清空缓冲区。scanf(“%s”, …)只读取了第一个单词%s遇到空格、制表符、换行符就会停止读取。使用fgets()读取整行或使用scanf(“%[^\n]”, …)。输入数字后程序行为异常或进入死循环用户输入了非数字字符如字母导致scanf(“%d”, …)匹配失败非法字符留在缓冲区。1. 检查scanf()返回值。2. 在错误处理分支中用while (getchar() ! ‘\n’);清空缓冲区。使用fgets()读字符串但第一次调用就得到了空字符串fgets()之前有scanf()等操作缓冲区留有\n被fgets()立即读取。在调用fgets()之前用while (getchar() ! ‘\n’);清空缓冲区。程序崩溃段错误使用scanf(“%s”, buf)且用户输入超长导致缓冲区溢出。始终为%s指定宽度scanf(“%19s”, buf)buf大小为20。或改用fgets()。scanf(“%d-%d”, a, b)输入10-20正常输入10 20失败格式字符串中的非格式字符-必须与输入流严格匹配。确保用户输入格式与格式字符串一致或改用更灵活的读取方式如先读整行再用sscanf解析。5.2 终极建议与心得经过这么多年的摸爬滚打对于C语言的输入处理我个人的体会是明确需求选择工具如果只是读一个简单的数字或单词用scanf()并做好错误检查。如果要读一行文字尤其是可能包含空格的无条件选择fgets()。它安全、可控虽然多一步处理换行符但省去了无数调试的烦恼。混合输入时头脑里要有一张缓冲区状态图。每次scanf()或getchar()后都想一下缓冲区里还剩下什么。养成检查返回值的习惯scanf()和fgets()都有返回值。忽略它们就等于关闭了错误反馈的大门。if (fgets(buf, size, stdin) NULL) { /* 处理错误或EOF */ }这样的代码能让你程序更健壮。清空缓冲区是万能钥匙吗while (getchar() ! ‘\n’);在很多教学示例里是万能的但它假设缓冲区里一定有一个\n。如果用户输入后没有回车比如文件重定向或者缓冲区里内容很多它可能清不干净或行为异常。更稳健的做法是基于scanf()的返回值进行条件性清理。考虑使用sscanf()进行二次解析一个非常强大的模式是先用fgets()安全地将一整行输入读到一个大缓冲区中然后再用sscanf()从这个缓冲区字符串里解析出你需要的数据。这样既安全避免了缓冲区溢出又灵活可以尝试多种解析格式失败了也不会影响原始输入流。char line[256]; int a, b; fgets(line, sizeof(line), stdin); if (sscanf(line, “%d %d”, a, b) 2) { // 解析成功 } else { // 解析失败但输入流没有被破坏可以重新提示用户 }最后输入处理是程序与用户交互的桥梁虽然繁琐但至关重要。花点时间把scanf()和缓冲区的机制吃透以后遇到任何输入问题你都能从容应对。在更复杂的项目中你可能会使用专门的输入库或框架但底层这些基本概念是相通的。从scanf()这个“小”函数入手理解数据流和控制流的交互是成长为一名扎实的C程序员的重要一步。

相关新闻

Origin软件中主成分分析(PCA)实战:从数据降维到科研绘图全解析

Origin软件中主成分分析(PCA)实战:从数据降维到科研绘图全解析

1. 项目概述:从数据海洋到信息孤岛,主成分分析的价值何在?做数据分析,尤其是处理化学、材料、生物这类实验科学的数据时,我们常常会陷入一种幸福的烦恼:仪器越来越先进,能测的指标(变…

2026/10/16 16:47:12 阅读更多 →
Figma新版Auto Layout深度解析:从Flexbox布局原理到高效组件构建实践

Figma新版Auto Layout深度解析:从Flexbox布局原理到高效组件构建实践

如果你最近打开 Figma,发现界面里多了一些新按钮,或者尝试调整组件间距时,感觉和以前不太一样了——别担心,这不是你的错觉。Figma 近期对Auto Layout功能进行了一次堪称“史诗级”的更新,同时,可嵌套文件夹…

2026/10/16 3:42:30 阅读更多 →
从内存故障到数据安全:深入解析ECC技术原理与应用

从内存故障到数据安全:深入解析ECC技术原理与应用

1. 从一次内存故障引发的思考:为什么需要ECC?几年前,我负责维护一个数据分析集群。某个深夜,系统监控突然告警,显示一台关键计算节点的计算结果出现了无法解释的偏差。经过长达数小时的排查,从应用层代码一…

2026/10/11 16:41:23 阅读更多 →

最新新闻

云原生功能测试实践:容器与微服务特性适配关键点

云原生功能测试实践:容器与微服务特性适配关键点

1. 项目背景与测试定位:容器化之后,功能测试到底测什么做云原生应用测试,最怕的就是一上来就扎进细节里。我接手这份“云原生应用软件功能测试报告:容器与微服务特性适配”的时候,第一反应不是急着列用例,而…

2026/10/17 4:41:13 阅读更多 →
Skill与Tool的本质区别:从执行单元到可演进认知单元

Skill与Tool的本质区别:从执行单元到可演进认知单元

1. 这不是概念辨析,而是工程现场的生存法则“Skill 为什么不同于 Tool?”——这个问题在Agent开发群里被问了37次,每次都有人贴出一段抽象定义:Tool是函数,Skill是能力封装。但真正跑通一个生产级Agent项目的工程师&am…

2026/10/17 4:41:13 阅读更多 →
开源项目周刊W38:代码评审、ADHD输出、智能体底座与文本去AI味

开源项目周刊W38:代码评审、ADHD输出、智能体底座与文本去AI味

1. 本周刊定位与内容拆解思路Github周刊这个系列我做了快两年,每周固定花三四个小时刷Trending、翻Star增速榜、蹲几个高质量作者的动态。2026年W38这一期信息密度格外高,四个项目分别踩中了代码协作、无障碍交互、智能体基础设施和内容真实性四个完全不…

2026/10/17 4:41:13 阅读更多 →
大模型参数量的本质:任务复杂度与架构约束的硬性映射

大模型参数量的本质:任务复杂度与架构约束的硬性映射

1. 这不是“堆料”,而是模型能力的物理边界“大模型的参数量为什么那么大?”——这个问题最近在技术社区、产品会议甚至高校课堂里反复出现,背后其实藏着一个被严重低估的事实:参数量不是工程师拍脑袋定的数字,而是当前…

2026/10/17 4:41:13 阅读更多 →
PySpark合成数据生成指南:从随机数到业务级假数据

PySpark合成数据生成指南:从随机数到业务级假数据

做数据开发这几年,我发现自己最常干的工作,一半是写业务逻辑,一半是“造数”。给下游团队演示报表要一批数据,给算法模型跑通流程要一批数据,给新集群做性能压测还要一批数据。真实数据牵扯内部权限和隐私管控&#xf…

2026/10/17 4:41:13 阅读更多 →
PyG自定义Data类:__inc__与__cat_dim__详解

PyG自定义Data类:__inc__与__cat_dim__详解

1. 从一次数据加载报错说起第一次在 PyTorch Geometric 里自定义Data类做小批量训练时,我遇到过一个很典型的报错:RuntimeError: The size of tensor a (5) must match the size of tensor b (4) at non-singleton dimension 0。当时排查了半天&#xff…

2026/10/17 4:40:13 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →