news 2026/9/27 1:20:37

STM32工具链四件套详解:Keil MDK、CubeMX、Programmer和ST-Link

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32工具链四件套详解:Keil MDK、CubeMX、Programmer和ST-Link

这个系列写到第四篇,后台收到一条让我印象很深的留言:“你让我装了四个软件,我到现在都不知道它们是干嘛的。”我一看就乐了,因为这几乎是我当年刚接触STM32时的真实心理活动。当时我也是老老实实装了Keil MDK、STM32CubeMX、STM32CubeProgrammer和ST-Link驱动,跟着教程一路Next,最后盯着桌面的一排图标发呆——它们到底有什么区别?为什么不能一个软件全搞定?

这篇不写业务代码,专门把这四个软件掰开揉碎讲清楚。从它们各自扮演的角色,到整个开发流程里谁先谁后,再到实际点灯时每个按钮对应谁,一并说透。搞明白这套工具链的分工,你后面再学嵌入式C++、再碰外设驱动,脑子里就会有一条清晰的地图,而不是每一步都靠“照着做”。这篇同样适合刚把环境装完、对着软件界面发懵的初学者,以及那些装了半年还在“机械点按钮”的朋友。

1. 你电脑里躺着的四个软件,到底谁是谁

1.1 先给四个软件发一张“身份证”

很多人装完四个软件后最懵的一件事,是不知道哪个软件该在什么时候打开。我先用一句话分别定义它们,你再回头看安装教程,就会有一种“原来如此”的感觉。

  • Keil MDK:集成开发环境(IDE),它是你写代码、编译、下载、调试的主战场。简单说,代码是在这里敲的,编译是在这里跑的,烧录按钮也是在这里点的。它相当于整个开发流程的“总操作台”。
  • STM32CubeMX:图形化配置工具。你用鼠标点击、勾选的方式配置芯片引脚、时钟、外设功能,它帮你生成一套初始化C代码。换句话说,它是“自动写初始化代码的生成器”。
  • STM32CubeProgrammer:独立的烧录工具。它负责把编译好的文件写进STM32芯片的Flash里,同时支持读回校验、选项字节配置、芯片擦除、读保护设置等专业操作。
  • ST-Link驱动:严格说它不是一个“软件”,而是USB驱动。它让电脑能够识别ST-Link调试器,没有它,上面三个软件和设备之间的通信全部瘫痪。

这四个东西不是功能重复,而是各管一段。很多人以为“装了一个IDE就能搞定一切”,那是被Arduino这类一体化环境惯坏了。STM32的生态更接近一条分工明确的流水线,你作为开发者,要做的不是把流水线合并成一个人,而是学会在正确的时间用正确的工具。

1.2 用一个“后厨团队”理解它们的协作关系

我把这套环境比作一个餐厅的后厨团队,你体会一下这几个软件的分工逻辑。

  • STM32CubeMX是配菜师傅:客人点菜(你选芯片、选外设)之后,他负责把食材预处理成标准半成品(生成初始化代码)。你不用自己洗菜切菜,他会告诉你哪些料已经备好了。
  • Keil MDK是掌勺大厨:半成品送到他手上,他负责加调料(写业务逻辑)、开火(编译)、出锅(生成可烧录文件)。你大部分工作时间都在这个灶台前站着。
  • STM32CubeProgrammer是打包出餐员:掌勺大厨把菜做好之后,由他把菜品送到客人桌上(把hex/bin烧进芯片)。他还会检查菜量对不对(校验)、能不能二次加热(读回来对比)。
  • ST-Link驱动是传菜通道:后厨和前厅之间如果没有这条通道,菜根本端不出去。它负责让电脑和ST-Link硬件之间能正常对话。

这四个环节缺一不可,但很多人栽跟头的地方在于:装了驱动却以为它是主角,天天打开CubeProgrammer却忘了那是出餐口,或者永远只点Keil的下载按钮,一旦遇到量产烧录就不知道怎么办。接下来我把每个角色单独拎出来讲。

2. Keil MDK:那个你打开最多的“三合一”工位

2.1 别以为它只是个编辑器,它背后干了一大堆活

Keil MDK的界面看起来像一个编辑器,左边工程树、中间代码区、下面输出窗口,很多人第一次打开就把注意力放在写代码上。但真正的重点是:当你点击Build按钮时,它悄悄完成了一整条编译流水线。

STM32上跑的代码,最终要变成芯片能识别的机器指令。Keil在这条链路里同时充当了编译器、汇编器、链接器的角色:

  1. 预处理与编译:把main.c、main.cpp等源文件编译成汇编代码,再生成目标文件(.o)。
  2. 链接:把你的目标文件、启动文件、标准库、HAL库代码合并成一个可执行镜像。
  3. 格式转换:生成.axf/.elf调试格式,同时转出.hex或.bin用于烧录。

你写的每一行C++代码,最后都会被转换成芯片Flash上的一段二进制。Keil的输出窗口显示的那些信息,并不是废话,它是在告诉你这次“烹饪”有没有出问题。比如你经常看到这样的输出:

Build started: Project: led_demo *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' compiling main.c... linking... Program Size: Code=1200 RO-data=260 RW-data=8 ZI-data=512 FromELF: creating hex file... "led_demo.axf" - 0 Error(s), 0 Warning(s).

看到0 Error(s), 0 Warning(s)的那一刻,才意味着你手里有了一份可以往下走的固件。如果这行字变成了1 Error(s),说明某个环节断了,后厨炒不了菜,出餐口更是连门都开不了。

2.2 为什么芯片包必须单独装,版本还要严格配对

我见过太多新手第一次用Keil遇到的第一个坎:代码写好了,编译报错,提示找不到某个头文件,比如fatal error: 'stm32f1xx_hal.h' file not found。问题不是Keil坏了,而是你根本没装对应的芯片包(Device Family Pack,DFP)。

芯片包是什么?它是芯片厂商针对具体芯片系列提供的“方言词典+工具说明书”,里面包含三个关键内容:芯片型号定义、寄存器地址映射、Flash烧录算法。没有这个包,Keil根本不知道你要编译的芯片长什么样,更不知道烧录时该往哪个地址写。

所以安装Keil之后,第一件事是打开Pack Installer,搜索你用的芯片型号,比如STM32F103C8T6,然后点击Install。注意两点:

  • 只装了主程序没装包,等于安装了输入法却没装中文语言包,你打出来的全是乱码。
  • 同一个芯片型号在Pack列表里可能有多个版本,尽量选新的,但也要看你的HAL库版本是否兼容。如果工程里用的是老版本HAL库,Pack升得太新也可能出现接口不匹配的问题。

这块我的建议是:初学者直接用当前最新Pack,HAL库也从CubeMX默认配置走,至少保证工具链内部的一致性,等踩坑踩多了再学怎么降版本。

2.3 “魔术棒”里最不该乱动的三个配置页

Keil工程工具栏那个长得像魔术棒的图标(Options for Target),是所有问题的源头。对新手来说,这里只看三个页面,其他设置默认就好。

Device页:确认你选的芯片型号和手上的板子一致。这一步错了,后面烧录大概率直接失败。F103C8T6和F103RCT6虽然都是F1系列,但Flash大小、外设资源差很多,选错型号轻则容量不对,重则引脚功能全对不上。

Target页:最需要注意的是Xtal晶振频率和ARM Compiler版本。晶振频率设置会影响系统时钟的换算逻辑,而编译器版本更是直接关系到C++是否能正常工作。Keil V5默认用ARMCC(V5编译器),V6用ARMCLANG。如果你的工程是C++写的,我强烈建议选V6,它对C++11/14/17的支持比V5好太多。这个细节后面写C++工程时会专门展开。

Debug页:必须把调试器选成ST-Link Debugger,然后点旁边的Settings,在Debug选项卡里确认连接方式是SW(不是JTAG),速度可以先用默认值。如果这里配置正确,你会看到一栏IDCODE,比如0x1BA01477,看到这个数字说明调试器已经和芯片握手成功。很多人烧录时提示No target connected,八成就是这一页没配对。

Keil是四个软件里你最常待的地方,但也是配置项最多、最容易出错的地方。新手不用追求把所有选项都弄懂,先把Device、Target、Debug这三页配明白,就能覆盖90%的日常开发场景。

3. STM32CubeMX:那个“画引脚”的工具,省掉的是你的头发

3.1 没有它之前,初始化代码要怎么写?

如果你接触过老一代STM32开发教程,一定见过这种写法:操作寄存器、对着数据手册一个一个地址查。以点亮LED为例,你要手动把GPIO端口的时钟使能打开,再配置GPIO的控制寄存器、数据寄存器,每一步都要算地址、移位、按位或。代码大概是这种感觉:

RCC->APB2ENR |= 1 << 2; // 使能GPIOA时钟 GPIOA->CRL &= ~(0xF << (5 * 4)); // 清空PA5配置位 GPIOA->CRL |= (0x2 << (5 * 4)); // PA5设为通用推挽输出 GPIOA->BRR = (1 << 5); // PA5输出低电平

这段代码对一个老手来说没什么,对初学者来说却像天书——你还没搞懂GPIO是什么,就被一堆寄存器位移吓退了。而CubeMX把这些底层操作全部变成了图形化配置,你只需要在芯片引脚图上点一下PA5,选择GPIO_Output,它就会自动生成完整且正确的初始化代码。这就是它的核心价值:把“配置硬件”和“写业务逻辑”这两件事彻底分开。

我经常和新人说:不排斥看寄存器、看数据手册,那是一个嵌入式工程师的长期素养。但入门阶段,没必要把时间全耗在查寄存器地址上,用CubeMX先把流程跑通,建立“配置→生成→写业务→烧录→观察现象”的正反馈,比死磕底层细节重要得多。

3.2 那几个页面到底在调什么?

CubeMX界面通常分三块:芯片引脚图、外设配置列表、时钟树。很多人第一次打开时不知道点哪里,其实核心就三个页面。

Pinout & Configuration(引脚配置):在这页里,你对着芯片图形点击某个引脚,就能给它分配功能。比如PA5选择GPIO_Output,PA9、PA10选择USART1的TX/RX。系统会自动检查引脚冲突——如果两个外设抢同一个引脚,Popup窗口会打出红色警告。这种冲突检查在纯手写代码的时代完全靠人工核对,经常要画回头账。

Clock Configuration(时钟配置):这一页就是著名的“时钟树”图形化界面。你输入外部晶振频率(比如8MHz),设置PLL倍频系数、分频系数,图形界面会实时算出HCLK、PCLK1、PCLK2这些总线时钟。如果某个数值超过芯片允许的最大值,它会立刻标红提示。我第一次看这页时其实没多大感觉,直到后来有一次把PCLK1义务设到了72MHz,导致片上外设工作完全异常,才老老实实回来研究时钟树。

Project Manager(工程管理):这里设置工程名、存储路径,关键是在Toolchain/IDE下拉框里选MDK-ARM,代表生成Keil工程。在Code Generator区域,建议勾选Generate peripheral initialization as a pair of .c/.h files per peripheral,这样每个外设单独一个文件,代码结构更清晰,C++工程里也更好封装。

3.3 生成的代码到底生成了什么?哪些能碰哪些不能碰?

CubeMX点GENERATE CODE之后,会自动生成一个完整的Keil工程。你打开后会看到:

  • 根目录下有Core/Src/main.c、Core/Src/stm32f1xx_hal_msp.c,里面包含时钟初始化、外设初始化、引脚复用配置。
  • Drivers/目录下是HAL驱动库,这是一大堆官方封装好的驱动源码。
  • LED项目的main函数里,已经帮你调好了HAL_GPIO_WritePin这一类外设操作函数。

你写业务代码的地方,集中在main函数的while(1)循环里,以及代码中专门标注的USER CODE BEGIN/USER CODE END注释段落之间。这一点必须牢牢记在脑子里:

千万不要去改CubeMX自动生成的初始化代码区域,要改业务逻辑只改USER CODE段。否则下次重新生成工程时,你手改的部分会被直接覆盖。

这个坑我踩过。初学时为了调一个引脚电平,直接改了初始化函数,结果在CubeMX里多勾选了一个外设重新生成代码,改的东西全没了,当时对着屏幕愣了五分钟。后来才明白,USER CODE区域就是为了让你在“自动生成”和“手动修改”之间留一个安全缓冲带。

4. CubeProgrammer与ST-Link驱动:烧录调试的“最后一公里”

4.1 为什么有了Keil,还要单独一个烧录软件?

很多人第一次受到灵魂拷问是在这里:Keil里不是有个Download按钮吗?我点一下也能烧录,为什么还要装CubeProgrammer?

Keil的下载功能本质是把烧录算法和调试器驱动打包进了IDE,你点Download时,它直接把.hex通过ST-Link写进Flash。这在日常开发中确实够用,教科书式的“改代码-编译-下载-看现象”流程完全跑得通。但生产环境或进阶场景里,CubeProgrammer有它不可替代的价值:

  • 它支持离线烧录:不需要打开工程,不用管源码,直接加载一个hex文件就能烧。量产时很多操作员根本不需要知道代码怎么写。
  • 它支持读回校验:烧录完成后能把芯片里的内容读回来对比,确保烧录过程没出错。
  • 它支持选项字节配置:比如设置读保护、配置独立看门狗、调整Flash等待周期。
  • 它支持全片擦除:芯片被锁死或者程序跑飞无法连接时,可以用它做整片擦除,把设备救回来。

所以你可以这么理解:Keil负责日常开发,CubeProgrammer负责“最后的交付和救火”。两者不是谁替代谁的关系,而是不同阶段各自的专业工具。

4.2 两种烧录方式对比,什么时候用哪个?

我用一张表把这俩的区别列出来,你对照自己的场景选就行。

对比项Keil内置下载STM32CubeProgrammer
使用场景开发阶段频繁修改、调试量产烧录、固件交付、救砖
是否需要打开工程需要不需要,直接加载固件文件
是否支持读回校验有限支持完整支持,可逐字节比对
是否支持选项字节配置部分支持完整支持
是否支持擦除整个芯片不支持支持
是否依赖编译器环境依赖Keil独立运行
能否设置读保护不能能

实际操作中,我在CubeProgrammer里烧录的流程通常是这样的:打开软件,选择右侧的ST-Link图标,点Connect把电脑和板子连起来,然后左侧加载刚才Keil生成的hex文件,点Download。如果烧录成功,状态栏会出现绿色的Download verified successfully,看到这句话就可以安心复位看现象了。

4.3 ST-Link驱动:最容易被忽略、却最致命的环节

ST-Link驱动是这四个“软件”里最没有存在感的,它装完之后甚至不会在桌面上创建一个图标。但如果你把它删了,Keil和CubeProgrammer会同时罢工,设备管理器里也会出现一个黄色感叹号的未知设备。

很多人插上开发板之后发现电脑没反应,第一反应是板子坏了,其实一半以上的情况是驱动问题。排查思路很简单:打开Windows的设备管理器,看有没有一个叫ST-Link Debug的设备。如果没有,或者看到带感叹号的设备,说明驱动没装好或者USB线有问题。说到USB线,这里特别提醒一句:很多设备附带的Micro-USB线只能充电、不能传数据,这种线插上去只会看到板载LED亮,但电脑完全无法识别。换一根带数据功能的线,问题会立刻消失。

另外有些教程会提到ST-Link Utility这个工具,它是CubeProgrammer的前身,功能和界面都比较老。如果你装过,保留着也不影响开发,但现在ST官方已经不再推荐使用了,新项目请直接用CubeProgrammer。这也是一个容易让人困惑的“第四点五”个软件,知道它已经被替代了就好,不用再花时间学它。

5. 真实流程走一遍:从CubeMX建工程到板子点灯

5.1 六步走通第一次完整开发链路

前面把四个软件分开讲完了,现在把它们按顺序串起来跑一遍。拿最常见的“点灯”举例,也就是让STM32F103C8T6控制一个LED亮起来,走完这六步你就能完整感受到整条链路是如何协作的。

  1. 用CubeMX新建工程:打开STM32CubeMX,选择Board或Directly select MCU,搜索STM32F103C8T6,双击创建工程。
  2. 配置引脚和时钟:在Pinout & Configuration里点击PA5,选择GPIO_Output,给它取个用户标签(比如LED)。然后在Clock Configuration里设置外部晶振8MHz,让HCLK自动到72MHz。最后在Project Manager里设置工程名,IDE选MDK-ARM,版本选V5或V6。
  3. 生成并打开工程:点击GENERATE CODE,CubeMX会在你指定的目录下生成Keil工程。找到.uvprojx文件双击打开,代码已经写好了一部分,你只需要在while(1)循环里加一句控制引脚电平的语句(如果用C++写法,后续我会再把封装思路展开)。这一步操作的核心意义是验证CubeMX生成的工程结构是否完整。
  4. 在Keil里编译:点击Build按钮,等待输出窗口出现0 Error(s), 0 Warning(s)。第一次编译可能有点慢,因为要编译整个HAL库,耐心等就好。
  5. 配置调试器并下载:打开魔术棒,Debug页选择ST-Link Debugger,然后点Download按钮。如果Keil弹出提示说ST-Link固件需要更新,直接选Yes让它更新,这是正常现象。下载完成后按一下板子上的复位键。
  6. 用CubeProgrammer验证一次:打开CubeProgrammer,连接ST-Link,读回一次Flash内容或者直接把同一个hex再次烧进去,体会一下这个独立工具的工作方式。

这一步下来,你就能直观感受到整套工具链“谁先谁后、谁负责哪一段”的协作逻辑。做完之后再回看开头那张流水线比喻图,会觉得特别贴切。

5.2 新手最容易翻车的5个操作

这条路我走过不止一遍,下面这些坑是新手扎堆踩的位置,提前打预防针:

  1. USB线选错:搞了一晚上连不上芯片,最后换一根数据线秒解决,这种案例我见过不下十次。只要涉及“电脑不识别设备”,先怀疑线,别怀疑板子和软件。
  2. 芯片型号选错:CubeMX里选了别的不匹配型号,Keil里也同步选错,代码能编译、烧录却失败。最诡异的是有时候能烧进去但程序根本跑不起来,因为外设地址都变了。
  3. ST-Link固件未更新:老版调试器在Keil下载时提示固件太旧,操作界面看起来像报错,其实是让你更新的,直接确认就好。
  4. Debug设置没选ST-Link:在魔术棒里默认可能配的是ULINK或者其他调试器,点Download提示No target connected,改回ST-Link就正常。
  5. 改错了自动生成代码区:在CubeMX生成的main函数初始化段手动改了东西,一旦重新生成就全部还原。我给你的建议是:业务逻辑只写在USER CODE段落里,或干脆封装到自己的C/C++文件中。

总结成一句话:烧录出了问题,先查硬件连接和线材,再查调试器配置,最后才是代码问题。不要一上来就怀疑芯片坏了,大概率是接线或配置的锅。

6. 常见问题速查与避坑心得

6.1 装上软件之后,你大概率会遇到的报错

很多人装完软件那天晚上不会消停,各种弹窗接踵而至。我整理一份高频报错速查表,直接对着找答案。

错误现象根本原因解决方法
No target connectedKeil调试器没配对或芯片没上电检查Debug页是否选了ST-Link,检查板子供电和ST-Link连接
Flash Download failed - "Cortex-M3"芯片型号不对或烧录算法缺失在魔术棒Debug的Flash Download里确认编程算法是否匹配芯片
No ST-LINK detected驱动没装好或USB线是充电线查设备管理器确认ST-Link设备是否识别,换数据线
fatal error: 'stm32f1xx_hal.h' file not found芯片包没装或工程路径不对打开Pack Installer安装对应芯片包,确保工程路径没有中文
Error: Flash Download failed - target not in secure state芯片开了读保护用CubeProgrammer做整片擦除并关闭读保护
Keil编译特别慢首次编译整个HAL库多编几次就好了,也可以用Go To Definition触发预编译缓存

这些报错信息表面上吓人,其实背后逻辑都很直白。排查时保持一个原则:先看硬件,再查配置,最后怀疑代码。按这个顺序逐级排查,能省下大量瞎折腾的时间。

6.2 关于C++和这套环境的配合,提前说两句

既然这个系列定格在“STM32的嵌入式C++编程”,工具链还有一个隐藏的兼容性问题要提前交代:编译器版本。Keil V5的ARMCC编译器对C++支持得比较保守,很多现代C++写法会报错或行为怪异;而ARMCLANG(V6编译器)对C++11/14/17支持更好,是嵌入式C++的推荐选择。

这里有几个操作层面的建议,等你开始写真正的C++工程时会用到:

  • 在CubeMX的Project Manager里,IDE生成选项选MDK-ARM V6,这样直接生成V6编译器对应的工程配置。
  • 调用HAL库函数时,由于HAL库本身是C语言写的,C++文件里要包一层extern "C",否则链接时会因为符号修饰规则不一致而报错。
  • C++的new/delete、模板、类继承在嵌入式环境里同样可用,但要注意内存分配和栈大小的问题,不要像写PC程序那样懒散。

这些内容我在后面的系列里会展开,尤其是“COOP写的类和HAL库怎么优雅地粘在一起”这种实操话题,比单纯堆语法有意思得多。

6.3 我的个人配置习惯,供你参考

最后分享几个落地的小习惯,不是说这是标准答案,而是我踩过坑之后积累的一套策略,至少能让你在起跑阶段更顺。

第一,工程路径不要带中文,不要放在桌面,也不要放在网盘同步目录里。Keil默认会生成一堆临时文件,路径里有中文时偶尔会触发奇怪的编码问题,网盘同步更是一会儿锁文件一会儿冲突,我吃过一次亏之后就老实了。

第二,给CubeMX生成的代码起一个清晰的用户标签。比如点PA5的时候命名成LED,生成的代码里就会出现HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, ...),读代码的人不用翻原理图就能看懂这个引脚是干嘛的。养成这个习惯只需要十秒钟,但对你后期排查问题的帮助是巨大的。

第三,Keil的字体和自动保存记得调一调。View菜单里可以改编辑器字体,Edit的Configuration里可以设置自动保存间隔。嵌入式开发经常一调试就忘了时间,一次意外崩溃如果没保存代码,那感觉比CPU跑飞还难受。

整套工具链搞明白之后,你会发现它其实没有想象中那么复杂。核心就是“配置生成、代码编译、烧录调试”三段式,四个软件只是把这三件事拆成了更专业的工位。先记住流程,再慢慢深入细节,后面学C++、学通信协议、学实时系统的时候,你手里的地图就是清晰的。下一篇,我们就正式在Keil里创建一个C++工程,把这套环境的真正威力用起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 1:20:30

楼梯目标检测数据集:1043张YOLO+VOC双格式实战解析

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

作者头像 李华
网站建设 2026/9/27 1:20:13

电脑端双开企业微信:批处理与多用户方案详解

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

作者头像 李华
网站建设 2026/9/27 1:18:06

eMMC存储架构、分区管理与u-boot实操全解析

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

作者头像 李华
网站建设 2026/9/27 1:17:28

新版MDK缺失AC5编译器?手把手教你安装与工程迁移

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

作者头像 李华
网站建设 2026/9/27 1:17:13

C语言真题卷(2)高频考点与易错点全解析

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

作者头像 李华