简介面向C语言初学者的文件读写学习文档围绕文件打开、写入、读取与关闭这一完整流程展开适合刚接触stdio.h、希望理解fopen、fprintf、fgets、fclose等标准库函数使用方式的学习者也可作为课堂示例或课下练习参考。压缩包内共1个doc文件大小约14KB内容轻量便于快速通读和边看边练。已有104人学习下载。文档以可直接运行的示例代码演示以“w”模式写入文本、以“r”模式逐行读取并包含文件指针定义、打开失败返回NULL的判断、缓冲区读取以及关闭文件释放资源等关键环节能够帮助读者建立“打开—操作—关闭”的文件处理思路。对需要夯实C语言文件读写基础、理解常见函数参数与错误处理写法的初学者较有参考价值。1. 写入成功却读不到内容C 语言文件读写的第一层错觉学 C 语言文件读写的人几乎都撞过同一堵墙程序里fprintf一路畅通“写入完成”也打印出来了可打开文件一看最后几行不见了甚至整个文件空着。同样的代码换成读操作fgets又莫名其妙少读最后一行。看上去像玄学其实是同一件事的两面——你手里的FILE *从来不是磁盘上的那个文件而是一块内存缓冲区的句柄。写进去的字节先落在缓冲里什么时候真正落盘由缓冲策略决定读出来的字节也是成批预取的停在哪个位置由游标决定。搞清这块缓冲区怎么分配、什么时候刷新、游标怎么移动那些“丢数据”“少一行”的现象会自己解释清楚。2. FILE 结构体的真相文件指针指向的是内存不是磁盘2.1 为什么标准库不肯让你看见 FILE 的内部C 标准只规定FILE是一个能标识文件的对象具体长什么样完全交给实现决定。glibc 里它是struct _IO_FILE几十个字段堆在一起到了 musl 又被精简到十来个字段。这种“只给指针、不给定义”的手法叫不透明指针opaque pointer动机有三个一是 ABI 稳定字段增删不影响已经编译好的二进制二是各平台实现差异极大把结构体写死在头文件里等于放弃可移植性三是防止有人绕过fgets直接改读写指针把用户态缓冲和内核里的文件偏移改成互相打架的状态。常见的实现思路是标准库把FILE对象放在堆上fopen里分配、fclose里释放。所以你手里的指针一次都没指向过磁盘它指向的是一块内存里的控制块控制块里再存一个整数_fileno那才是内核认得的文件描述符。注意FILE *、int fd、磁盘上的 inode是三样东西。混淆它们是后面几乎所有困惑的源头。2.2 缓冲区、读写游标与状态标志glibc 的_IO_FILE精简后真正决定行为的只有几组字段。下面是一个示意版结构字段名做了简化机制与真实实现一致/* 结构示意非真实头文件定义 */ struct _IO_FILE { int _fileno; /* 系统调用层的文件描述符 */ char *_buf_base; /* 用户态缓冲区起始 */ char *_buf_end; /* 用户态缓冲区结束 */ char *_read_ptr; /* 下一个要交给调用者的字节 */ char *_read_end; /* 缓冲区里已从内核取回的最后一个字节之后 */ char *_write_base; /* 待刷出数据的第一字节 */ char *_write_ptr; /* 待刷出数据的下一空位 */ int _flags; /* 可读/可写/EOF/出错/是否行缓冲 */ int _blksize; /* 底层块大小决定默认缓冲大小 */ };读的时候_read_ptr到_read_end之间是“已经预取、还没交给你”的字节_read_ptr一旦追上_read_end下一次fgets才会触发系统调用。写的时候正好反过来_write_base到_write_ptr是攒着还没发出去的数据_write_ptr追到_buf_end就强制刷一次。同一块内存被读、写两个游标复用这也是fopen要求你在读写之间必须插入fflush、fseek、fsetpos或rewind之一的原因——不切换状态两个游标会互相污染。_flags里还挂着_IO_EOF_SEEN和_IO_ERR_SEEN两个标记它们的区别正是后面feof一直误判的关键EOF 标记只在一次读操作“撞到文件末尾之后”才被置上而不是在读到最后一个字节时置上。2.3 从 fopen 到 open一次调用穿过两层世界fopen做的事比看上去多。大致可以拆成四步解析模式字符串成O_标志、发起open系统调用拿到 fd、在堆上分配FILE、挂上缓冲区。伪代码如下FILE *fopen(const char *path, const char *mode) { int flags parse_mode(mode); /* w → O_WRONLY|O_CREAT|O_TRUNC */ int fd open(path, flags, 0666); /* 从用户态进入内核真正碰文件 */ if (fd 0) return NULL; /* 失败原因留在 errno不在返回值里 */ FILE *f malloc(sizeof(FILE)); /* 堆上分配控制块 */ if (!f) { close(fd); return NULL; } f-_fileno fd; f-_flags readable(flags) ? _IO_RD : 0; f-_flags | writable(flags) ? _IO_WR : 0; /* 缓冲区一般延迟到第一次读写时才分配 用 fstat 取 st_blksize取不到就用 BUFSIZ */ return f; }这段逻辑解释了两件事。第一fopen返回NULL只表示“内核不让你打开”具体是路径不存在、权限不够还是路径指向了一个目录得看errno所以紧跟一句perror(fopen)比打印“无法打开文件”有用得多。第二创建模式传的0666只是“期望权限”最终会被进程 umask 削减想要固定的权限位就得在open之后补chmod。顺带说一句fopen成功的瞬间磁盘上可能什么都没变——Open 阶段只建立了 fd 和控制块缓冲区往往还空着。这就是“打开成功不等于写入成功”的机制来源。3. 打开模式一个字母如何决定文件的命运3.1 “w” 的破坏力O_TRUNC 在 open 那一刻就执行w是最常用的模式也是最容易翻车的模式。它对应的标志是O_WRONLY | O_CREAT | O_TRUNC其中O_TRUNC由内核在open内部原子执行文件长度瞬间被截成 0。这跟“后面有没有真的写入”完全无关只跟有没有成功fopen有关。所以这种写法没有后悔药FILE *fp fopen(demo.txt, w); /* 此刻 demo.txt 已被清空 */ if (fp NULL) { /* 打开失败文件反而安全 */ } /* ...程序在这里因为别的原因 return 1; 文件已经没了 */想保留原内容做覆盖式修改正确做法是r它不创建、不截断从文件头开始写写多少覆盖多少剩下的旧内容还在。如果新内容比旧内容短尾部会残留旧字节——这不是 bug是r的定义。真要截断在打开之后调用ftruncate(fileno(fp), 新长度)显式做。a的行为常被误解。它对应O_APPEND追加动作由内核在每次write时保证“先定位到文件末尾再写”所以多进程同时追加也不会互相覆盖。但这只对写有效a模式下的初始读位置在文件头读之前不rewind一下你会以为自己读的是空文件。3.2 文本模式与二进制模式两个字节的悄悄改写r和rb在 POSIX 系统上没有区别换行符就是\n一个字节。但在 Windows 的 C 运行库里文本模式会做一层翻译读的时候把\r\n折叠成\n写的时候把每个\n展开成\r\n。这层翻译是流式的发生在缓冲区填充和刷出的环节而不是在文件里真改了字节。麻烦出在两个地方。第一ftell返回的不是真实字节偏移。写入了k个\n文件里实际是k个额外的\rftell在文本模式下给的是“翻译后”的计数。第二fseek在文本模式下只保证支持ftell的返回值和 0 这几个位置其它偏移是未定义行为——你按字节算出来的位置落到文件里可能正好卡在\r和\n之间。血泪经验是只要文件里存的不是纯文本或者你需要按字节精确定位就用rb/wb。图片、结构体数组、序列化后的数据一律二进制模式别指望文本模式“反正也读得出来”。3.3 模式组合映射表把六种基本模式的语义摊开对比比死记要牢模式等价的 O_ 标志文件不存在文件已存在初始位置rO_RDONLY返回 NULL正常打开文件头wO_WRONLY|O_CREAT|O_TRUNC创建截断为 0文件头aO_WRONLY|O_CREAT|O_APPEND创建保留内容文件尾rO_RDWR返回 NULL正常打开文件头wO_RDWR|O_CREAT|O_TRUNC创建截断为 0文件头aO_RDWR|O_CREAT|O_APPEND创建保留内容读在头部写在尾部表里最值得盯的是r与w那一列两者都是可读可写差别只在“文件已存在时要不要清空”。很多人以为自己需要w其实要的是r结果每次启动程序都把配置清空一次排查半天才发现错在一个字母。4. 缓冲区与刷新写入和读取共用同一条链路4.1 fprintf 的三段式格式化、拷贝、必要时落盘fprintf不是“写文件”它是“格式化后塞进缓冲区”。内部大致分三段先把可变参数按格式串渲染到一块临时区再把渲染结果拷进FILE的写缓冲区并把_write_ptr前移最后判断缓冲区是否已满、流是否行缓冲、有没有遇到换行满足条件才发起write系统调用把整块数据交给内核。#include stdio.h #include errno.h #include string.h int main(void) { FILE *fp fopen(demo.txt, w); if (fp NULL) { /* 失败原因在 errno 里 */ fprintf(stderr, open: %s\n, strerror(errno)); return 1; } /* 返回值是实际写入的字符数负值代表失败 */ int n fprintf(fp, line %d: %s\n, 1, hello); if (n 0) { perror(fprintf); fclose(fp); /* 出错也要关别泄漏 fd */ return 1; } /* 此刻数据大概率还躺在用户态缓冲区没进内核 */ if (fflush(fp) ! 0) /* 强制把整块缓冲推给内核 */ perror(fflush); fclose(fp); /* 内部再 flush 一次然后 close(fd) */ return 0; }参数说明fprintf的第一个参数是流指针后面的%d、%s与printf完全同源区别只在于输出目的地。返回值必须看写满磁盘ENOSPC这类错误只会在返回值或errno上体现程序不会崩数据也不会到。中间那次fflush是为了把“数据已交给内核”这件事变成确定的而不是等fclose顺带完成——两者效果一样但显式刷一次能让你在出问题时更早定位到是哪一段出的错。4.2 全缓冲、行缓冲、无缓冲判据是设备类型标准只给了原则具体阈值由实现定。常见的实现思路是stderr默认无缓冲保证错误信息立刻可见stdout接终端时用行缓冲接管道或文件时切成全缓冲其它流一律全缓冲。glibc 在分配缓冲区时会fstat一次拿st_blksize终端通常是 1024 字节普通文件常见 4096 字节BUFSIZ那个宏glibc 里是 8192只是“建议值”不代表实际分配大小。这三档的差别直接决定了“什么时候数据会丢”。行缓冲意味着每写一个\n就刷一次所以终端上你能看到逐行输出全缓冲意味着要攒满一块才发程序中途被杀最后不满一块的内容就留在内存里消失了。要自己定用setvbuf必须在任何 IO 之前调用setvbuf(fp, NULL, _IOFBF, 1 16); /* 全缓冲64KB缓冲区由库自己 malloc */ setvbuf(fp, NULL, _IOLBF, 0); /* 行缓冲遇 \n 刷出 */ setvbuf(fp, NULL, _IONBF, 0); /* 无缓冲每次写都直接落到 write */第三个参数是模式第四个是缓冲区大小第二个参数传NULL表示“缓冲区你来分配”传自己的数组则要保证它在fclose之前一直有效。写日志这种“宁可慢一点也不能丢”的场景_IOLBF或者干脆_IONBF更稳批量导数据反过来大缓冲能少掉几个数量级的系统调用。4.3 fgets 的读取路径与 EOF 的真面目fgets(buf, n, fp)的语义是从流里取字符直到遇到\n、取满n-1个、或者遇到文件结束。取到的内容存进buf末尾补\0。注意它是“最多 n-1 个”不是“恰好一行”——超长行会被切成几段每段都当成一次成功返回。当用户态缓冲区被消费空底层会触发一次read(fd, _buf_base, _blksize)把一大块数据预取进来。这就是为什么读大文件时fgets很快真正的系统调用次数是“文件大小除以块大小”而不是“行数”。char line[256]; while (fgets(line, sizeof(line), fp) ! NULL) { size_t len strlen(line); /* 换行符被原样保留很多字符串比较的 bug 由此而来 */ if (len 0 line[len - 1] \n) line[--len] \0; printf([%s]\n, line); } /* 循环退出后要区分正常读完还是读出错 */ if (ferror(fp)) perror(read);参数sizeof(line)包含了结尾的\0所以单次最多装 255 个字符line传数组名即退化为指针等价于line[0]。循环结束后那三行判断非常关键fgets返回NULL有两种可能文件正常结束或读出错ferror才是分辨它们的依据不看它就会把磁盘故障当成“读完了”静默处理。4.4 关闭与刷新fclose 到底做了什么fclose的动作顺序是固定的先把写缓冲区里的残留刷出去内部就是一次fflush再close(fd)最后free掉FILE对象。任何一步失败都会返回EOF但 fd 该关还是关了重复fclose是未定义行为。exit()会在进程退出前遍历所有打开的流逐个刷新所以正常return或exit不会丢数据。_exit()和abort()直接走内核退出路径用户态缓冲区里的东西一律丢弃。这就是“程序加了一行调试输出就正常了”的机制解释多出来的那次调用恰好触发了刷新把原本会丢的尾巴救了回来。别信这种偶然该fclose就fclose。5. 避坑排查五条踩出来的血泪经验5.1 现象文件末尾少几行重跑一次有时又正常原因几乎总是刷新时机不确定。数据攒在用户态缓冲区里程序走上某条提前return的分支或者被信号杀掉缓冲区就跟着进程一起消失了。还有一种变体子进程fork之后直接_exit父进程缓冲区里的内容被子进程复制了一份两边各刷一次尾部出现重复行。解决办法是把“关流”当成资源释放的固定动作所有出口路径统一走fclose子进程在fork之后立刻fflush(NULL)或者干脆只调_exit不要碰父进程留下的流。写关键日志时开_IOLBF用确定性换性能。5.2 现象fgets 循环读出来比预期少一行最后一行凭空消失原因是拿feof当循环条件。feof的语义是“上一次读操作是否撞到了文件末尾”它总比真正的结束晚一步读到最后一个字节时它还是假要再读一次失败才置位。写成while (!feof(fp)) { fgets(...); ... }最后一轮fgets返回NULLbuffer里还是上一轮的旧内容于是要么多处理一遍脏数据要么被后面的判空逻辑吞掉。解决是永远以fgets的返回值为准feof只在循环之后用来解释“为什么结束”。同理ferror也要在循环后检查一次把“正常读完”和“读挂了”分开。5.3 现象读到的第一行是空的或者读出来的内容和写进去的错位常见于“先scanf再fgets”的组合。scanf(%d)只消费数字行尾的\n留在输入缓冲区里紧接着的fgets立刻撞上这个换行返回一个只含\n的字符串。改用fgets读整行再sscanf解析能一次绕开。另一种错位来自读写混用没切换状态。同一流上先写后读中间必须插fflush(fp)先读后写中间必须插fseek(fp, 0, SEEK_CUR)之类的定位操作。省掉这一步两个游标指向的位置对不上读出来的自然不是刚写进去的。5.4 现象长行被截成两半或者缓冲区边界处出现乱码fgets的第二个参数是缓冲区总长度含\0不是“可读字符数”。传sizeof(buf)是安全的传一个手算的数字很容易少算结尾那一个字节把\0写到缓冲区外面去。更隐蔽的是超长行。一行 1000 个字符缓冲区 256fgets会分四次返回每次都是一段“看起来像完整行”的内容。要正确处理得判断这次的字符串末尾有没有\n没有就说明还没读完继续拼接到一个动态增长的缓冲区里直到看见\n或fgets返回NULL。这一步不做后面按行解析的逻辑全是错的。5.5 现象二进制文件读到一半数据错位或者图片打开是花的九成是模式字符串漏了b。在 Windows 上文本模式会在读的时候把\r\n折叠成\n写的时候反过来展开二进制数据里恰好出现0x0D 0x0A的地方就被悄悄改掉了一个字节长度跟着变后面的结构全乱。ftell在这种模式下给出的偏移也不是真实字节位置。解决就是三个字用rb、wb。顺带一提结构体直接fwrite落盘本身也不可靠编译器会在成员之间插填充字节跨编译器、跨架构读回来字段就错位了正经做法是逐字段序列化成固定字节序。6. 设计取舍为什么 fgets 逼你管缓冲区而不是替你分配读到这里一个合理的疑问会浮上来既然fgets有超长行截断、换行符残留这些毛病标准库为什么不做成“自动分配一整行”答案是历史包袱和可控性之间的取舍。fgets诞生在一个栈空间以 KB 计、没有标准内存分配保证的年代定长缓冲区 调用者持有内存是最不容易出事的契约。同一时期还有个gets它不检查长度因为无法知道缓冲区多大最终被从标准里删掉——fgets多出来的那个参数n就是这场事故留下的安全阀。把三种读法摆在一起看取舍很清楚方式边界安全超长行处理空白处理典型问题gets无直接溢出保留已从标准移除fgets有需传对 n切成多段保留\n需自己拼接、去换行fscanf(%s)有上限截断遇空白停读不了含空格的整行getlinePOSIX有自动扩容保留\n需free非标准 Cgetline是后来补上的答案传NULL和0它自己malloc并随行长度扩容返回读到的字节数用-1表示结束。代价是你得接管这块内存的生命周期多一次free。在 Linux 上写解析器我一般直接上getline写需要跨平台的代码还是老老实实fgets加手写拼接。缓冲大小也是一笔账。默认块大小跟着st_blksize走是为了让每次read恰好覆盖一个文件系统块避免内核里再多一次拆分调大了能减少系统调用次数但会增加首次读的延迟和内存占用调到极小则退化成逐字节系统调用性能断崖式下跌。setvbuf给的就是这个旋钮但绝大多数程序用默认值就是对的除非你 profiling 出来 IO 是瓶颈。错误处理同样是一种“把责任交回去”的设计。fopen返回NULL而不抛异常fprintf用负返回值而不中断feof和ferror分开两个标记——这一整套约定逼着调用者在每个环节显式检查。啰嗦但换来了两件事任何一行都可能失败而程序不会莫名其妙跳走以及在嵌入式这类连异常机制都没有的环境里同一份代码能原样跑。想验证上面这些说法不用读源码两个办法就够。一是把strace -e traceopen,read,write,close挂在程序上看系统调用次数和时机——你会发现fopen之后并没有立刻write一段连续输出最后合并成一两次writefclose时补上最后一次。二是用setvbuf把缓冲改成_IONBF再跑一遍write的次数会暴涨到和fprintf调用次数同量级两相对照缓冲区到底在哪一层工作就一目了然了。我自己的习惯是只要动手写文件先把fopen的返回值检查和fclose的出口路径写死再往里填业务逻辑。早年被“写入完成”这四个字骗过一次几百行统计结果最后只落了半截翻遍逻辑没找到错最后发现是某个分支return之前忘了关流。从那以后我每写一个fopen都强制在同一次编辑里把对应的fclose敲完中间的逻辑才往里塞。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Scala从安装到高阶函数:JDK1.8、IDEA插件与版本避坑 简介:这份 docx 文档面向准备进入大数据开发、尤其是想上手 Spark 的初学者,围绕运行在 JVM 上、兼具面向对象与函数式特性的 Scala 语言展开,帮助读者跨过环境搭建与语法入门两道门槛。内容先说明 Spark 1.6 依赖 Scala 2.10、Spark 2.0 之后… · 2026/11/3 3:14:32
二维锥优化技术在配电网重构中的实践与突破 1. 项目概述:二维锥优化在配电网重构中的应用价值配电网重构是电力系统运行中的关键环节,既要保证日常运行的经济性,又要在故障发生时快速恢复供电。传统方法往往将这两个需求分开处理,而二维锥优化技术的引入彻底改变了这一局面。… · 2026/11/3 3:14:32
高性能序列化库选型与优化实战指南 1. 为什么我们需要高性能序列化库在分布式系统开发中,数据需要在不同节点间频繁传输。我曾参与过一个电商平台的微服务改造项目,当系统从单体架构拆分为20多个微服务后,服务间的通信开销突然成为性能瓶颈。通过火焰图分析,我们发现… · 2026/11/3 3:13:32
LLM硬件加速器:KV Cache与MoE架构的专用硬件解法 1. 这不是“又一个AI芯片”:LLM硬件加速器的本质是重构计算范式你可能已经看过太多标题里带“AI芯片”“大模型加速”“国产替代”的新闻稿——它们往往堆砌参数、罗列算力数字、强调“支持千亿参数”,最后落点在“已量产”“达成战略合作”。但真正做过… · 2026/11/3 4:26:48
OpenCode+Harness智能体工程方法论:构建可编排、可审计的生产级智能体 1. 这不是又一个“AI玩具”,而是一套可落地的智能体工程方法论OpenCode 智能体教程——光看标题,很多人第一反应是“又一个新出的AI平台宣传页”。但如果你真花30分钟拆开它背后的Harness核心架构,就会发现这根本不是教你怎么点几下鼠标调用大… · 2026/11/3 4:26:48
人形机器人技术瓶颈与商业化路径分析 1. 人形机器人产业现状与商业困境人形机器人这个领域最近两年突然火了起来,但真正了解行业的人都知道,现阶段做这个生意简直就是在"烧钱"。我接触过几家头部企业,他们每年的研发投入都是以亿为单位计算的,但商业化落地却… · 2026/11/3 4:26:48
边缘容器服务:云边协同架构的技术突破与实践 1. 边缘容器服务的技术本质与行业痛点云原生技术发展到今天,已经形成了从开发到运维的完整技术栈,但传统中心化云计算架构在物联网、工业互联网等场景下暴露出明显短板。我曾参与过某智能制造项目的云边协同改造,当车间设备产生的实时数据需要… · 2026/11/3 4:26:48
TFLite内存规划器解析:张量生命周期与Arena复用机制 做端侧推理的人,早晚会在内存上栽一回。模型文件 20MB,你觉得稳了,结果一跑起来进程直接飙到 200MB 以上,换个小内存的设备就 OOM。多数人第一反应是换更小的模型、降分辨率,其实还有一个比模型体积更隐蔽的“内存黑洞… · 2026/11/3 4:25:48
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18