news 2026/9/27 1:41:43

基于MATLAB的CDMA通信系统仿真:从PN序列到误码率验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于MATLAB的CDMA通信系统仿真:从PN序列到误码率验证

简介:一份基于MATLAB的CDMA通信系统仿真与实现的毕业论文文档,面向通信工程、电子信息类专业学生及科研人员,用于理解码分多址的核心原理并掌握MATLAB仿真分析方法。文档聚焦扩频与多址两项关键技术,阐述了CDMA如何利用扩频提升抗窄带干扰能力、隐藏信号并允许多用户共用频带,同时梳理了两路信号经扩频、混合、加噪、调制解调、分离还原的完整仿真流程,结合波形图与频谱图评估误码率,并针对问题给出改进调试方案,仿真结果验证了系统设计的正确性。资源为单个doc文档,大小约1.99MB,包含中英文摘要、目录及正文章节,结构完整。目前已有127人学习下载,适合需要完成CDMA相关课程设计、毕业设计或入门移动通信仿真的读者,也可作为教师和研究者辅助教学的参考资料。

1. 为什么每个通信方向的学生都要亲手把 CDMA 链路跑通

答辩前夜,误码率曲线还是比理论值高一个数量级,我盯着星座图看了两个钟头,最后发现是接收端少做了一拍码片对齐。这类基于 MATLAB 的 CDMA 通信系统仿真与实现,绝大多数翻车现场都不是理论不会,而是扩频、同步、解扩里某个细节没落地。说白了,这个题目要干的事很具体:用 MATLAB 把一套直扩 CDMA 链路从头搭到尾,包括 PN 序列生成、扩频调制、信道加噪、接收端同步与解扩,最后用误码率曲线证明链路是对的。它适合三类人:正在写毕业论文需要仿真章节支撑的学生、课堂上要交通信系统大作业的人、以及想借着扩频通信把 MATLAB 信号处理底子补扎实的工程师。别小看这个“老题目”,跑通它之后,OFDM 的帧同步、LoRa 的扩频解调、测控链路里的捕获,很多坑都是同一批。

2. 动手前先花半小时倒腾环境:工具箱、路径与最小可复现闭环

凡是仿真报告写得越急的,越容易跳过环境检查。常见做法是先建项目目录,再确认通信工具箱在不在,最后用一段 20 行的 BPSK 最小闭环验证整条调试链路通了没有。这三步做完,后面所有脚本都在同一个环境里跑,省掉“在室友电脑上能出图,换到自己机器上就报错”的麻烦。

2.1 先确认通信工具箱在不在

毕设脚本里最常用的几个对象是 comm.PNSequence、comm.RayleighChannel,以及波形成形用的 rcosdesign。它们都挂在 Communications Toolbox 下面,如果安装 MATLAB 时只勾了基础模块,后面一跑就会报“未定义函数或变量”。

% 检查 Communications Toolbox 是否可用 % 如果返回 built-in 或某个路径,说明工具箱已安装 which comm.PNSequence % 或者直接看整个工具箱列表,找 Communications Toolbox 行 ver

which返回空的时候不要急着重装整个 MATLAB,先看ver列表里有没有 Communications Toolbox。没有的话去账户后台补装这个工具箱就行,装好重启再用ver确认。另外提醒一句:新版安装包体积越来越大,启动也慢,做这种基带仿真老版本完全够用,不用为了追新去折腾下载安装。如果不想在本地装大客户端,MATLAB Online 也能跑,但它每次登录后路径都得重新配置,跑蒙特卡洛循环时体验不如本地流畅。

还有个小坑是中文注释乱码。如果你拿到别人分享的脚本,打开后发现注释全是乱码,先检查文件编码,再检查 MATLAB 的字体预设。代码里尽量少用特殊字符,注释写中文没问题,但保存时统一用一种编码,别一会儿 UTF-8 一会儿 GBK。

2.2 给仿真建一个干净的“实验室目录”

很多人的毕设仿真就是桌面上一堆 script1.m、script2.m,跑完连自己都分不清哪个是最新版。我一般会建三个目录:scripts 放主脚本,utils 放自写函数,results 放数据和图。这样重跑仿真时,旧图不会被覆盖,论文需要哪个版本也能直接回溯。

% 三个文件夹:scripts 放主脚本,utils 放自写函数,results 放数据与图 mkdir scripts utils results addpath(genpath(pwd)); savepath;

addpath(genpath(pwd))把当前目录下所有子文件夹都加进搜索路径,savepath把这个路径存下来,下次启动 MATLAB 不用重新加。注意如果你用 MATLAB Online,这个路径配置是按会话管理的,关掉就没了。目录结构看起来是小事,但等到你要对比不同扩频码长、不同滚降系数的误码率曲线时,目录整洁能省一半时间。

2.3 先跑通最小闭环:一段 BPSK 过 AWGN 信道

正式写 CDMA 链路之前,先跑一个不含扩频的最小闭环。它的作用是验证三件事:随机数种子可控、awgn 函数用法正确、误码率统计逻辑没问题。这三件事里任何一件出错,后面 CDMA 仿真跑出来都是错的,而且你还很难定位。

% 最小 BPSK 链路:验证环境与调试流程 rng(42); % 固定随机种子,保证可复现 Nbits = 1e4; % 比特数 data = randi([0 1], Nbits, 1);% 随机信息位 sym = 2*data - 1; % BPSK 映射 EbN0_dB = 6; % 以 Eb/N0 为指标 snr_dB = EbN0_dB + 10*log10(1); % 这里无扩频、无过采样,SNR=Eb/N0 rx = awgn(sym, snr_dB, 'measured'); % 加高斯白噪声 ber = mean((rx > 0) ~= data); % 硬判决后统计误码率 fprintf('Eb/N0=%.1f dB 实测误码率: %.5f\n', EbN0_dB, ber);

rng(42)固定随机源,保证同样的脚本跑两次得到同样的误码率,这是后面调试的后悔药。awgn的第三个参数填'measured',意思是在加噪声前先实测信号功率,按实测值计算噪声方差,避免因为信号功率缩放导致信噪比不对。snr_dB这行先写成和 Eb/N0 相等,等第 4 章引入扩频增益之后要改掉,这里先埋个伏笔。

这一段跑通后,你的 MATLAB 环境就具备做 CDMA 仿真的一切条件了。为什么不建议直接用 Simulink 拖几个模块搭发射机和接收机?因为第一版代码应该保持信号处理过程透明,每个矩阵维度变化都得看得见。Simulink 适合后面做模块化验证,不适合一开始就用来理解原理。

3. 从 PN 序列到 CDMA 发射链路:m 序列、Gold 码与扩频成形

CDMA 发射链路的核心是把窄带数据符号“抹”到宽带上,这个动作靠 PN 序列完成。展开讲就是三件事:生成伪随机序列、用序列与符号做扩频、把码片脉冲成形到合适带宽。这一章按发射机的顺序把三件事全部落地。

3.1 用移位寄存器自写 m 序列和 Gold 码生成器

m 序列是最基本的 PN 序列,周期是 2^n - 1,其中 n 是移位寄存器级数。虽然工具箱里直接有 comm.PNSequence,但我建议自己写一遍,因为移位寄存器的实现只有十行,而且写完你就理解“为什么 Gold 码是两个 m 序列异或出来的”。

function seq = mseq(stages, taps) % stages: 寄存器级数,seq 长度 = 2^stages - 1 % taps: 反馈抽头,例如 [5 2] 对应 x^5 + x^2 + 1 N = 2^stages - 1; reg = ones(1, stages); % 初始状态不能全 0 seq = zeros(N, 1); for k = 1:N seq(k) = reg(end); fb = mod(sum(reg(taps)), 2); reg = [fb reg(1:end-1)]; end end

寄存器初始状态全 1 是常见约定,全 0 状态会让序列陷在里面出不来。抽头taps决定特征多项式,不同抽头产生不同序列,但周期一样。跑一遍mseq(5, [5 2])得到 31 个码片,这就是码长 N=31 的 m 序列。

Gold 码用一对优选对的 m 序列生成,通过对其中一个序列做循环移位再异或,能派生出多条码。这个特性很关键,它回答了一个毕设答辩必问题:CDMA 怎么区分不同用户?答案是靠不同的扩频码,码之间互相关性要足够小。

% 用两个优选对 m 序列生成一组 Gold 码 m = 5; % 级数,N=31 g1 = mseq(m, [5 2]); g2 = mseq(m, [5 4 3 2]); gold = mod(g1 + circshift(g2, 3), 2); % 相对移位 3 得到一条 Gold 码

circshift是循环移位,移位量不同得到不同 Gold 码。m=5 时一共能产生 33 条 Gold 码,给 32 个用户用还多一条。注意这里g1和g2的长度要一致,都是 31。实际仿真里我一般用 N=31 去验证扩频增益,用 N=63 去展示多用户容量,再往上推到 N=127 仿真时长会明显变长,论文里没太大必要。

3.2 扩频调制:码片速率、过采样与脉冲成形

扩频在数学上很简单:一个数据符号复制成 N 份,每一份乘一个码片。MATLAB 里用kron一步就能做完。

N = 31; % 扩频码长 gold = mod(g1 + circshift(g2, 3), 2); pn = 2*gold - 1; % 把 0/1 序列映射成 ±1 序列 data = randi([0 1], 500, 1); sym = 2*data - 1; % 每个符号扩展成 N 个码片 chip_seq = kron(sym, pn); % 过采样与脉冲成形:每码片 4 个采样,滚降系数 0.5 sps = 4; beta = 0.5; shaping = rcosdesign(beta, 6, sps); tx = upfirdn(chip_seq, shaping, sps);

kron(sym, pn)把每个符号替换成整段 PN 序列,得到一个长度为 500×31 的码片序列,这个动作就是直扩。rcosdesign(beta, 6, sps)生成一个根升余弦滤波器,beta=0.5是滚降系数,6是滤波器截断的符号数,sps=4表示每个码片采 4 个点。upfirdn(chip_seq, shaping, sps)先插值再滤波。

参数选择上,sps不是越大越好,它决定频谱分辨率和后续同步的粒度。4 是常用值,8 会让数据量翻倍但同步精度提升不明显。滚降系数 beta 越大,频谱越宽,但峰均比越好控制,0.5 是容易出图也容易答辩的值。

这里有个容易被追问的点:用根升余弦成形后,接收端要用同样的根升余弦做匹配滤波,整个链路才没有码间串扰。第一版可以用矩形匹配简化,但论文里要写明“接收端采用匹配滤波后误码率能更贴近理论曲线”,这个我在第 4 章会做简化处理。

3.3 把信号送进信道:AWGN 与多径的第一版实现

发射机输出的是码片速率的基带信号,加噪声时要特别注意:awgn 函数需要的是信噪比,不是 Eb/N0。因为一个比特被扩成了 N 个码片,每个码片又采了 sps 个点,能量折算关系完全不同。

EbN0_dB = 8; % 关键换算:扩频增益 N 会让每个比特的能量分摊到 N*sps 个采样点上 snr_dB = EbN0_dB + 10*log10(1/N) + 10*log10(sps); rx_awgn = awgn(tx, snr_dB, 'measured'); % 两径信道:主径 + 延迟 3 个码片、幅度 0.5 的次径 imp = zeros(round(sps*3)+1, 1); imp(1) = 1; imp(end) = 0.5; rx_multipath = filter(imp, 1, rx_awgn);

snr_dB这行是后面各种翻车的重灾区。10*log10(1/N)把扩频增益折算进去,10*log10(sps)把过采样增益折算进去。如果漏掉这两项,误码率曲线会整体平移,看起来像那么回事但数值全错。多径信道用一个简单的 FIR 滤波器模拟,imp(1)是主径幅度 1,imp(end)是延迟了 3 个码片的次径,幅度 0.5。实际信道里多径延迟是连续值,这里取整到采样点是工程简化,够用。

4. 接收端解扩与误码率验证:同步、换算与理论曲线对齐

发射链路跑通只是第一步,接收端才是 CDMA 仿真的分水岭。一个最常见的错误是:直接把接收信号从第 1 个采样点开始解扩,结果误码率永远接近 0.5,因为扩频码没对齐。这一章解决同步、误码率验证和多用户干扰三个问题。

4.1 前导序列与滑动相关捕获

解扩的前提是“知道每个符号从哪里开始”。所以发射端要在一帧数据前面加一段已知的 PN 序列做前导,接收端用滑动相关的办法找峰值位置。

% 用一段已知的前导序列做滑动相关 preamble = pn(1:64); % 发送端已知的捕获序列 L = numel(rx); M = numel(preamble); corr = zeros(L, 1); for idx = 1:L-M+1 corr(idx) = abs(sum(conj(preamble) .* rx(idx:idx+M-1))); end [~, peak] = max(corr); fprintf('捕获峰值位置: %d\n', peak);

这段循环写法不是性能最优,但最直观,适合调试时打印每一步结果。corr(idx)计算的是接收信号从 idx 开始的一段与前导的互相关模值,当 idx 正好对齐时,相关峰出现。m 序列自相关旁瓣是 -1/N,所以峰值一定高于噪声基底,用max就能捕获。

提示:实际工程里不会对整帧做全搜索,而是先粗搜再细搜,把搜索窗限制在一个符号周期内。但毕设仿真做全搜索完全够用,而且展示效果更直观。

捕获到峰值后,数据解扩就从peak偏移处开始取数。注意如果你的接收端做了多径信道,imp会带来一条延迟 3 个码片的多径分量,滑动相关的峰值旁边会出现一个次峰。这在第 5 章会详细讲怎么处理。

4.2 误码率统计:Eb/N0 与 SNR 换算,以及理论曲线对齐

误码率仿真要做蒙特卡洛循环:固定发射信号,每个 Eb/N0 点独立加一次噪声,统计误码后和理论曲线画在一起。理论值本身不是玄学,BPSK 在 AWGN 下的误码率公式非常硬。

EbN0_vec = 0:2:12; ber_sim = zeros(size(EbN0_vec)); for k = 1:numel(EbN0_vec) % 先算码片域 SNR,再做信道仿真 snr_dB = EbN0_vec(k) + 10*log10(1/N) + 10*log10(sps); rx_k = awgn(tx, snr_dB, 'measured'); % 按捕获位置截取整帧,长度与发送端一致 rx_k = rx_k(peak:peak + N*numel(sym)*sps - 1); % 解扩:每 N 个码片累加,判决符号 despread = zeros(numel(sym), 1); for s = 1:numel(sym) seg = rx_k((s-1)*N*sps + (1:N*sps)); despread(s) = sum(seg .* kron(pn, ones(sps, 1))); end ber_sim(k) = mean(despread > 0 ~= data); end % 理论 BPSK 误码率 ber_theory = qfunc(sqrt(2*10.^(EbN0_vec/10))); semilogy(EbN0_vec, ber_sim, 'o-', EbN0_vec, ber_theory, 'x-'); legend('仿真','理论'); grid on;

解扩的数学含义是把每个码片乘以本地 PN 码再累加。注意kron(pn, ones(sps,1)):因为发射端做过upfirdn,每个码片对应 sps 个采样,所以本地序列也要按 sps 重复再与接收信号逐点相乘。despread(s) > 0是 BPSK 判决。

这里最容易被追问的一个点是:为什么用了升余弦成形,解扩却用矩形匹配?严格做法是先做匹配滤波再解扩。毕设里为了简化,通常把匹配滤波隐含在解扩累加里,只要滚降系数不过大,性能差别不会肉眼可见。这句话建议写进论文的仿真假设里。

4.3 加入第二个用户:观察多址干扰

单用户跑通只能证明你会搭链路,双用户才是 CDMA 仿真的精髓。两个用户用不同的 Gold 码,发送信号叠加后进同一个信道,接收端分别用各自的码解扩。

% 第二条 Gold 码:改变移位量即可 gold2 = mod(g1 + circshift(g2, 10), 2); % 相对移位 10 pn2 = 2*gold2 - 1; data2 = randi([0 1], 500, 1); sym2 = 2*data2 - 1; chip2 = kron(sym2, pn2); tx2 = upfirdn(chip2, shaping, sps); snr_dB = 8 + 10*log10(1/N) + 10*log10(sps); rx_total = awgn(tx1 + tx2, snr_dB, 'measured'); % 分别用 pn1 和 pn2 解扩,统计各自误码率

这里tx1和tx2长度相同、功率相同,所以直接相加。实际问题来了:用 pn1 解扩后,pn2 那边残留的干扰不会因为信噪比提高而消失,这就是多址干扰 MAI。仿真结果一般能看到用户 1 的误码率比单用户时略差,用户 2 也一样。如果两个用户功率不一样,比如 tx2 比 tx1 大 10 倍,弱用户的误码率会直接崩掉,这个效应叫“远近效应”,是 CDMA 工程里必须处理的现实问题,第 5 章专门讲这个。

5. 避坑手记:CDMA 仿真最容易翻车的 4 个常见问题

这一章写的是我自己和周围人反复踩过的坑。每一条都按“现象 → 原因 → 解决”来写,你可以直接对照自己的仿真结果排查。血泪经验是:这些坑每一条都至少浪费过我一整天。

5.1 现象一:解扩后误码率接近 0.5,星座图散成一团

现象:低信噪比时误码率还勉强能看,高信噪比反而跳到 0.5 附近;星座图上四个象限全都有点,看不出聚集。

原因:接收端没有做码同步,直接从头开始按 N 个码片解扩。扩频码没对齐时,解扩操作等同于把噪声扩频,信号本身被当成干扰抹掉了。这类问题看着像玄学,其实把捕获峰值打印出来就明白了——峰值位置根本不在 1。

解决:发射端加一段已知 PN 前导,接收端先做 4.1 的滑动相关找峰值,再从峰值处截取数据解扩。注意如果峰值位置打印出来是 0,去看看数组索引是不是从 1 开始的问题。改完这段,误码率通常立刻就有物理意义。

5.2 现象二:误码率曲线形状和理论值一致,但整体偏移 3~5 dB

现象:仿真曲线和理论 BPSK 曲线平行,但一直偏在左边或右边,怎么都不重合。

原因:Eb/N0 和 SNR 混用。awgn 函数需要的是“信号功率与噪声功率之比”,而不是“每比特能量与噪声功率谱密度之比”。CDMA 系统里一个比特被扩成 N 个码片,每个码片又采 sps 个点,这两个增益如果不折算进去,曲线就会整体平移。偏移量恰好等于 10log10(N) 或 10log10(sps) 的整倍数。

解决:统一用snr_dB = EbN0_dB + 10*log10(1/N) + 10*log10(sps),并把换算公式写在脚本注释里。每次换扩频码长 N 或过采样率 sps,这行必须跟着改。建议把 EbN0_vec 和 snr_dB 都存成数组,画图前打印出来核对一遍,两分钟能省两小时。

5.3 现象三:脚本一运行就报“未定义函数或变量 comm.PNSequence”

现象:代码是从别人那儿拷的,或者自己换了一台电脑,运行到 PN 序列生成那一行直接报错。

原因:两种情况最常见。一是精简安装没装 Communications Toolbox,ver里看不到;二是老版本 MATLAB 里部分通信相关函数名和用法与新版本不一样,工具箱存在但函数被移到了别的包路径下。

解决:先跑which comm.PNSequence看返回空还是路径。返回空就补装工具箱;如果不是很依赖工具箱,干脆用第 3 章那个十行的mseq函数自己生成序列,任何版本都能跑。实际毕设里自写 m 序列不仅没坏处,答辩时解释起来还更清楚,因为你能直接画移位寄存器结构图。

5.4 现象四:多用户场景下弱用户误码率卡在一个固定值不上不下

现象:两个用户功率相差 10 dB 以上,弱用户的误码率曲线随着 Eb/N0 增大先下降,然后卡住不再变,出现“地板”。

原因:这就是 CDMA 远近效应。强用户的信号通过 Gold 码之间不为零的互相关泄漏到弱用户解扩结果里,而且这个泄漏功率不随噪声降低而减少,所以弱用户信噪比再高也没用。

解决:常见做法是在接收端做串行干扰消除 SIC。先解强用户,判出它的符号后重新扩频成形,再从总接收信号里减掉,最后解弱用户。

% 串行干扰消除(SIC)骨架:先解强用户再减掉 s1_est = sign(despread_by_pn1); % 强用户符号判决,despread_by_pn1 来自 4.2 recon1 = upfirdn(kron(s1_est, pn1), shaping, sps); % 重构强用户基带信号 rx_for_weak = rx_total - recon1; % 从总信号中减去,得到弱用户的净信号

重构时必须用同一个成形滤波器和同一个捕获偏移,不然减法会把强用户残余当成新的多径干扰。这一段代码建议在论文“系统改进”章节里放进去,再配一张 SIC 前后的误码率对比图,整体工作的完整度立刻上一个台阶。

6. 进阶验证:参数扫描、Simulink 互证与答辩用的漂亮图

前面的链路是单点验证,论文里更有说服力的是参数扫描:固定其他条件,改变扩频码长 N,观察误码率曲线的变化趋势。N 越大扩频增益越高,抗噪声能力越强,但占用带宽也越宽,这个矛盾关系是 CDMA 的核心思想,值得用一张图展示。

Ns = [15 31 63]; % 三种扩频码长 figure; for idx = 1:numel(Ns) % 每轮循环重新生成对应码长的扩频码并跑完整链路 % 这里把 4.2 的误码率仿真包成函数:berN = cdma_ber(N, EbN0_vec) berN = cdma_ber(Ns(idx), EbN0_vec); semilogy(EbN0_vec, berN, 'o-', 'LineWidth', 1.2); hold on; end semilogy(EbN0_vec, ber_theory, 'k--', 'LineWidth', 1.2); legend('N=15','N=31','N=63','BPSK理论'); grid on;

把 4.2 的仿真代码包成函数cdma_ber(N, EbN0_vec)后,扫描就只是一行调用。这里顺带说一个工程技巧:仿真函数里把随机种子和发送数据都作为参数传进去,这样不同码长之间对比时,数据比特完全相同,“变量只有 N”这件事在答辩时经得起追问。

Simulink 互证是另一个加分项。在 Simulink 里用通信模块库搭一条同样结构的链路,发射、信道、接收各用模块实现,把误码率结果和 MATLAB 脚本结果画在同一张图上。两条独立实现的链路只要数值一致,就说明你的仿真没有受某个特定函数的行为影响。这一步不需要把 Simulink 模型做得非常复杂,能把 AWGN 信道和误码率统计模块连起来就够。

最后说出图。答辩用的图建议用exportgraphics导出矢量格式,再转成 PDF 插进文档,不然 Word 里放大就发虚。老版本可以用print('-depsc', 'ber_curve.eps'),效果类似。图上记得把横轴标注成 Eb/N0(dB),纵轴用对数坐标,刻度范围从 1e-5 到 1 之间,理论曲线和仿真曲线用不同线型区分,答辩老师一眼就能看到结论。

我自己的教训是:第一次做这张曲线图时,前导序列和业务扩频码用了同一条 Gold 码,导致捕获峰值永远出现在零延迟处,换了一条独立的 Gold 码做前导后才正常出峰。后来每套仿真我都坚持“前导码和业务码分家”,这个习惯帮我避开了后面很多次类似翻车。这个方向值得做,直扩 CDMA 的同步与多址处理思路,是后续 OFDM、LoRa、扩频测控都绕不开的底子。希望帮到你。

本文还有配套的精品资源,点击获取

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

研究生必备Zotero插件清单:从文献管理到论文写作的完整工作流

/* 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:41:37

Windows文件夹时间分组视图原理与精准控制

/* 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:41:32

最小二乘法从原理到实战:手推正规方程与代码实现

/* 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:41:26

ComfyUI+Z-Image本地文生图:从环境搭建到工作流调参全指南

/* 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:41:08

C++生产级算法实现:从可运行到可交付

/* 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:41:04

408真题解析:DMA与外存磁道扇区计算全拆解

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

作者头像 李华