C语言实现栅栏密码:古典置换算法的原理与健壮代码实践
1. 项目概述从“栅栏”到“密文”在信息安全领域加密算法是构建信任的基石。对于初学者或需要快速实现简单数据混淆的场景那些结构复杂、数学理论深厚的现代密码学算法如AES、RSA往往让人望而却步。这时古典密码学中的一些经典算法以其直观的原理和易于实现的特点成为了绝佳的教学工具和轻量级应用选择。“栅栏式加解密算法”正是其中之一。这个名字听起来就很有画面感想象一下你要把一段明文比如“HELLO WORLD”写在一张纸条上然后像翻越栅栏一样按照某种规则重新排列字母的顺序得到一堆看似杂乱无章的字符这就是密文。接收方只要知道你是如何“翻越”这个栅栏的就能把字母顺序还原读懂信息。它不涉及复杂的数学运算核心就是“位置置换”。对于C/C学习者而言实现它不仅能巩固对数组、字符串、循环等基础语法的掌握更能深入理解“算法即流程”这一核心思想。本文将彻底拆解栅栏算法的原理并用C语言提供清晰、健壮、可直接复用的源码同时分享在实现过程中那些教科书上不会写的“坑”与技巧。2. 栅栏密码原理深度拆解栅栏密码也称为栅栏置换密码其核心思想非常简单将明文中的字符按照一定的规则栅栏的栏数重新排列形成密文。解密则是这一过程的逆操作。最常见的两种形式是“W型”或称“之字形”栅栏和“矩阵式”栅栏。网络上大多数文章只浅显地提一下概念我们这里要深入到它的数学描述和内存布局层面。2.1 “W型”栅栏Zigzag Cipher的运作机制这是最经典、最直观的栅栏形式。设定一个栏数或称深度key比如3。写入阶段将明文字符按“之”字形或“W”形填入一个具有key行的虚拟栅栏中。读出阶段按行顺序读出所有字符连接起来即为密文。以明文“HELLOWORLD”和key3为例行1栅栏顶: H O L 行2栅栏中: E L W R D 行3栅栏底: L O按行读出行1:HOL 行2:ELWRD 行3:LO。 拼接得到密文HOLELWRDLO。背后的数学规律字符在原明文中的下标i从0开始与其在虚拟栅栏中的行号r0到key-1存在周期性关系。一个完整的“V”形周期长度是2*(key-1)。对于第i个字符其行号r可以通过以下公式计算周期索引 i % (2*(key-1)) 如果 周期索引 key: r 周期索引 否则: r 2*(key-1) - 周期索引这个公式是编程实现的关键它让我们无需真正构建一个二维数组来模拟“之”字形可以直接通过计算确定每个密文字符来源于明文的哪个位置极大提升了效率。2.2 “矩阵式”栅栏Rail Fence Cipher的运作机制这种方法更接近于“栅栏”的原始比喻。它先将明文按行写入一个key行的矩阵写满一列再下一列然后按行读出。同样以“HELLOWORLD”和key3为例按列写入一个3行的矩阵最后一列可能不满行1: H L O L 行2: E L W D 行3: L O R注意这里我们是按列优先的顺序H, E, L, L, O, W, R, L, O, D填充到3行的结构中。然后按行读出行1:HLOL, 行2:ELWD, 行3:LOR。 拼接得到密文HLOLELWDLOR。你会发现同样的参数两种方法得到的密文不同。“W型”是密码学中更常指的“栅栏密码”因为它能产生更强的混淆效果。“矩阵式”有时被称为“列置换”的一种简单形式。在接下来的实现中我们将聚焦于更经典、更具教学意义的“W型”栅栏。注意明确你实现的算法变体至关重要。在交流或使用时必须说明是“W型”还是“矩阵式”或者提供具体的加密示例否则会导致通信双方无法正确加解密。2.3 算法特性与安全性分析栅栏密码是一种置换密码它只改变字符的位置而不改变字符本身。这意味着频率分析失效因为字符本身未变对密文进行单字母频率分析结果与明文语言如英语的频率特征一致这为破解留下了线索。安全性极低在现代计算能力下即使不知道栏数key通过穷举所有可能的key值从2到明文长度n进行尝试解密并观察输出结果是否是有意义的单词或句子破解几乎是瞬间完成的。因此它绝不能用于任何真正的安全通信。主要价值教学、算法思维训练、简单的数据混淆例如防止信息被一眼看穿但不对抗有意的分析或是作为更复杂加密算法中的一个步骤。3. C语言实现从原理到健壮代码理解了原理我们用C语言来实现它。我们的目标是写出不仅正确而且健壮、清晰、可复用的代码。我们会实现“W型”栅栏的加密和解密函数。3.1 核心数据结构与函数设计我们选择使用字符数组C风格字符串来存储明文和密文。为了清晰和安全我们将遵循以下原则明确区分输入、输出缓冲区。处理字符串终止符\0。对输入参数进行有效性校验。首先定义我们的函数接口/** * brief 使用W型栅栏算法加密字符串 * param plaintext 输入明文以\0结尾 * param ciphertext 输出密文缓冲区需由调用者分配足够空间至少strlen(plaintext)1 * param key 栅栏栏数深度必须 2 * return 成功返回0失败返回-1如参数无效 */ int rail_fence_encrypt(const char *plaintext, char *ciphertext, int key); /** * brief 使用W型栅栏算法解密字符串 * param ciphertext 输入密文以\0结尾 * param plaintext 输出明文缓冲区需由调用者分配足够空间至少strlen(ciphertext)1 * param key 栅栏栏数深度必须与加密时一致且 2 * return 成功返回0失败返回-1 */ int rail_fence_decrypt(const char *ciphertext, char *plaintext, int key);3.2 加密函数实现详解加密函数的核心任务是按照“之”字形规则将明文字符填入虚拟的行中然后按行收集。最直观的方法是模拟一个key行的字符串数组或二维字符数组遍历明文根据计算出的行号将字符追加到对应行的末尾最后拼接所有行。#include stdio.h #include string.h #include stdlib.h int rail_fence_encrypt(const char *plaintext, char *ciphertext, int key) { // 参数校验 if (!plaintext || !ciphertext || key 2) { return -1; } int len strlen(plaintext); if (len 0) { ciphertext[0] \0; return 0; } // 为每一行分配动态数组。使用动态数组是为了适应任意长度的明文。 // 另一种更优的方法是计算每行字符数此处为清晰起见使用动态增长。 char **rails (char **)malloc(key * sizeof(char *)); if (!rails) return -1; for (int i 0; i key; i) { // 每行最多可能拥有 (len key -1) / key 个字符但为简单起见分配len1确保安全。 rails[i] (char *)malloc((len 1) * sizeof(char)); if (!rails[i]) { // 分配失败清理已分配内存 for (int j 0; j i; j) free(rails[j]); free(rails); return -1; } rails[i][0] \0; // 初始化为空字符串 } // 模拟“之”字形填充 int rail 0; int direction -1; // 方向-1表示向下1表示向上 for (int i 0; i len; i) { // 将当前字符添加到对应行的末尾 int current_len strlen(rails[rail]); rails[rail][current_len] plaintext[i]; rails[rail][current_len 1] \0; // 更新行号到达顶部或底部时转向 if (rail 0 || rail key - 1) { direction -direction; } rail direction; } // 按行拼接所有字符到输出缓冲区 int idx 0; for (int i 0; i key; i) { int sub_len strlen(rails[i]); strncpy(ciphertext idx, rails[i], sub_len); idx sub_len; free(rails[i]); // 释放每行内存 } ciphertext[idx] \0; // 确保字符串终止 free(rails); // 释放行指针数组 return 0; }实现要点解析动态内存管理我们为每一行分配了足够大的空间len1。在实际产品代码中可以预先计算每行精确的长度来优化内存使用但动态分配len1在大多数教学和简单应用场景下是可接受的。关键是记得释放方向控制使用direction变量1或-1来控制行号的增减模拟“之”字形移动。当rail到达顶部0或底部key-1时反转方向。字符串操作我们使用strlen获取当前行字符串长度然后手动追加字符并设置终止符。这比反复调用strcat更高效因为strcat每次都要遍历字符串找到末尾。3.3 解密函数实现详解解密比加密稍复杂。我们需要逆向思考密文是按行读取的所以我们首先要知道加密后每一行有多少个字符然后才能知道该从密文的哪个位置取字符来填充回“之”字形的正确位置。解密步骤计算每行字符数模拟一遍加密的“之”字形路径但不填充字符只统计每一行会分配到多少个字符。这一步是解密的关键。分割密文根据第一步计算出的每行字符数将密文字符串分割成key个子串分别对应每一行的密文内容。重构“之”字形再次模拟“之”字形路径但这次是从第一步得到的各行密文中按顺序取出一个字符填充到明文的对应位置。int rail_fence_decrypt(const char *ciphertext, char *plaintext, int key) { if (!ciphertext || !plaintext || key 2) { return -1; } int len strlen(ciphertext); if (len 0) { plaintext[0] \0; return 0; } // 步骤1计算每行应有多少字符 int *rail_sizes (int *)calloc(key, sizeof(int)); if (!rail_sizes) return -1; int rail 0; int direction -1; for (int i 0; i len; i) { rail_sizes[rail]; if (rail 0 || rail key - 1) { direction -direction; } rail direction; } // 步骤2根据每行字符数从密文中分割出各行内容 char **rails (char **)malloc(key * sizeof(char *)); if (!rails) { free(rail_sizes); return -1; } const char *cipher_ptr ciphertext; for (int i 0; i key; i) { rails[i] (char *)malloc((rail_sizes[i] 1) * sizeof(char)); if (!rails[i]) { for (int j 0; j i; j) free(rails[j]); free(rails); free(rail_sizes); return -1; } strncpy(rails[i], cipher_ptr, rail_sizes[i]); rails[i][rail_sizes[i]] \0; // 确保终止 cipher_ptr rail_sizes[i]; // 移动密文指针 } // 步骤3重构明文 // 我们需要跟踪从每一行取到了第几个字符 int *rail_indices (int *)calloc(key, sizeof(int)); if (!rail_indices) { for (int i 0; i key; i) free(rails[i]); free(rails); free(rail_sizes); return -1; } rail 0; direction -1; for (int i 0; i len; i) { plaintext[i] rails[rail][rail_indices[rail]]; rail_indices[rail]; if (rail 0 || rail key - 1) { direction -direction; } rail direction; } plaintext[len] \0; // 清理内存 for (int i 0; i key; i) free(rails[i]); free(rails); free(rail_sizes); free(rail_indices); return 0; }解密实现难点rail_sizes数组它记录了加密过程中每一行实际分配到的字符数。这是正确分割密文的唯一依据。双重模拟解密过程需要两次模拟“之”字形路径。第一次计算rail_sizes是“空跑”第二次是“实跑”并填充明文。这体现了算法逆向思维的精妙。内存管理解密函数分配了更多临时内存rail_sizes,rails,rail_indices务必确保在所有出口包括错误处理都正确释放避免内存泄漏。3.4 完整可运行的示例程序将上述函数整合并提供一个简单的main函数进行测试#include stdio.h #include string.h #include stdlib.h // 此处插入上面实现的 rail_fence_encrypt 和 rail_fence_decrypt 函数 int main() { const char *original_text HELLOWORLD; int key 3; // 加密 char ciphertext[256] {0}; if (rail_fence_encrypt(original_text, ciphertext, key) 0) { printf(明文: %s\n, original_text); printf(密钥(key): %d\n, key); printf(密文: %s\n, ciphertext); } else { printf(加密失败\n); return 1; } // 解密 char decrypted_text[256] {0}; if (rail_fence_decrypt(ciphertext, decrypted_text, key) 0) { printf(解密后明文: %s\n, decrypted_text); if (strcmp(original_text, decrypted_text) 0) { printf(加解密成功\n); } else { printf(错误解密结果与原文不符\n); } } else { printf(解密失败\n); return 1; } // 测试另一个例子 printf(\n--- 测试空格和标点 ---\n); const char *text2 THIS IS A SECRET MESSAGE!; char ct2[256], dt2[256]; rail_fence_encrypt(text2, ct2, 4); printf(明文: %s\n, text2); printf(密文(key4): %s\n, ct2); rail_fence_decrypt(ct2, dt2, 4); printf(解密文: %s\n, dt2); return 0; }编译并运行这个程序你会看到类似以下的输出明文: HELLOWORLD 密钥(key): 3 密文: HOLELWRDLO 解密后明文: HELLOWORLD 加解密成功 --- 测试空格和标点 --- 明文: THIS IS A SECRET MESSAGE! 密文(key4): TASG!H SSAI ECSETEMER 解密文: THIS IS A SECRET MESSAGE!4. 关键问题排查与性能优化在实际编码和调试中你可能会遇到以下几个典型问题4.1 常见问题与解决方案问题现象可能原因解决方案加密后密文乱码或程序崩溃1. 未给输出缓冲区分配足够空间。2. 动态内存分配失败未检查。3. 字符串未正确终止\0。1. 确保ciphertext/plaintext缓冲区长度 strlen(input)1。2. 检查malloc返回值是否为NULL。3. 在所有字符串操作后手动添加ciphertext[idx] \0;。解密结果末尾多出奇怪字符解密时明文字符串没有正确终止。在解密循环结束后执行plaintext[len] \0;。加解密结果不一致1. 加密和解密使用的key不同。2. 加密/解密算法实现逻辑有误尤其是方向控制或行计数。3. 处理了包含\0的二进制数据栅栏算法本质是文本算法。1. 核对传入的key值。2. 使用短字符串如“ABC”和key2进行单步调试观察“之”字形路径。3. 明确该算法用于文本字符串对二进制数据需先编码如Base64。当key大于字符串长度时结果异常算法逻辑未处理此边界情况。当key len时“之”字形无法形成所有字符都在第一行。在函数开始处添加检查if (key len) { strcpy(ciphertext, plaintext); return 0; }。4.2 性能优化与替代实现我们上面的实现为了清晰使用了动态内存和字符串操作。对于性能敏感的场景可以进行优化避免动态内存分配可以预先计算每行最大长度使用栈上分配的二维数组如果key和len不大或者一次性分配一大块内存然后划分。“计算位置”法加密这是最高效的方法。直接利用2.1节提到的数学公式计算出密文中每个位置对应的明文字符位置。// 优化加密算法伪代码思路 void encrypt_fast(const char *plain, char *cipher, int key, int len) { int idx 0; for (int r 0; r key; r) { int step1 2 * (key - 1 - r); int step2 2 * r; int pos r; int use_first 1; while (pos len) { cipher[idx] plain[pos]; if (r 0 || r key - 1) { pos 2 * (key - 1); // 顶行和底行步长相同 } else { if (use_first) { pos step1; } else { pos step2; } use_first !use_first; } } } cipher[idx] \0; }这种方法完全没有内存分配开销直接计算索引性能最好。解密也可以采用类似的计算方法但逻辑更复杂一些。使用更紧凑的数据结构如果不追求极致的计算性能但想减少内存分配次数可以只分配一个长度为len的int数组rail_index在第一次模拟时记录每个明文字符所属的行号然后根据行号收集字符。这只需要两次遍历和一次排序或收集操作。实操心得在学习和面试中清晰第一优化第二。面试官更希望看到你对算法流程的深刻理解、健壮的代码习惯如错误处理、边界检查和清晰的表达能力。在解释完基础版本后如果能主动提出“这里为了清晰使用了动态内存在实际应用中如果已知字符串长度不大可以用栈数组优化如果对性能要求高可以采用直接计算索引的方法”这绝对是加分项。5. 从栅栏算法延伸的编程思维训练实现一个栅栏密码远不止是写出能跑的代码。它是一个绝佳的练手项目可以训练你多方面的能力算法可视化调试尝试在加密过程中打印出那个虚拟的“栅栏”二维数组这能让你直观地理解“之”字形是如何工作的。对于解密打印出rail_sizes数组和分割后的各行内容能帮你理清思路。边界条件测试空字符串输入。key1应视为无效或等同于不加密。key值等于或大于明文长度。包含空格、标点、甚至中文多字节字符的字符串。注意我们的基础实现是针对单字节字符ASCII/UTF-8的一个字节对于中文需要先处理为字节数组或宽字符。模块化与接口设计我们将加解密函数设计成独立的、带有明确输入输出和错误返回的接口。你可以很容易地将它们打包成库.h和.c文件供其他项目调用。探索变种尝试实现“矩阵式”栅栏。更进一步可以研究“变种栅栏”比如加密时先按“之”字形写入但按列读出这会让算法更有趣也更能锻炼你的抽象思维能力。最后始终记住栅栏密码的定位它是一个教学玩具和古典密码的标本。通过亲手实现它你收获的不仅仅是一个加密函数而是对“置换”这一密码学基本操作的理解以及对C语言字符串、数组、内存管理和算法逻辑的扎实练习。当你下次看到更复杂的加密算法时你会意识到它们很多也是由这些基础的思想模块构建而成的。

相关新闻

从零构建AI Agent:报错诊断实战指南

从零构建AI Agent:报错诊断实战指南

1. 从零构建你的第一个实用Agent:极简实战指南在AI工程领域,最危险的陷阱莫过于"架构先行"——还没跑通一个最简单的案例,就开始设计复杂的多Agent系统和监控体系。三年前我带队实施第一个企业级AI项目时,就曾因过度设计…

2026/10/12 17:34:23 阅读更多 →
放飞炬人集团行政总裁方达炬批准筹备  地下特殊材料工业公司,专门保障 战斗机器人公司(九天机器人(武汉)有限公司)特殊材料大规模高质量采购需求   及地下工程需要的地下特殊材料制造供应。

放飞炬人集团行政总裁方达炬批准筹备 地下特殊材料工业公司,专门保障 战斗机器人公司(九天机器人(武汉)有限公司)特殊材料大规模高质量采购需求 及地下工程需要的地下特殊材料制造供应。

放飞炬人集团行政总裁方达炬批准筹备 地下特殊材料工业公司,专门保障 战斗机器人公司(九天机器人(武汉)有限公司)特殊材料大规模高质量采购需求 及地下工程需要的地下特殊材料制造供应。

2026/9/27 16:46:07 阅读更多 →
AI助力实习报告撰写:技术解析与实用指南

AI助力实习报告撰写:技术解析与实用指南

1. 实习报告撰写的痛点与现状分析写实习报告是每个大学生都要经历的必修课,但现实中90%的学生都在重复同样的错误——把报告写成"工作日志"。我见过太多这样的案例:学生花了三个月时间在一家知名企业实习,最后交上来的报告却像是一…

2026/10/14 10:53:44 阅读更多 →

最新新闻

业务驱动挖金融SRC:从逻辑漏洞到越权的高价值挖掘思路

业务驱动挖金融SRC:从逻辑漏洞到越权的高价值挖掘思路

做金融SRC这五六年,我最大的体会是:漏洞挖掘的瓶颈往往不在工具,而在你对业务的理解深度。同样一批人,手里工具差不多,漏洞库也差不多,但产出高价值漏洞的能力可以差出好几倍。通用扫描器擅长发现组件版本漏…

2026/10/14 21:39:33 阅读更多 →
MinGW-gcc-4.4实战:老编译器解决老代码编译兼容性难题

MinGW-gcc-4.4实战:老编译器解决老代码编译兼容性难题

简介:MinGW-gcc-4.4 是一套面向 Windows 平台的开源 GCC 编译工具链,旨在帮助开发者在脱离 Visual Studio 等商业软件的情况下,编译并构建 C/C 程序。该版本于 2010 年发布,对 C0x 标准提供了初步支持,涵盖 lambda 表达…

2026/10/14 21:39:33 阅读更多 →
SpringBoot+Vue+MyBatis+MySQL就业管理系统架构设计与部署全解析

SpringBoot+Vue+MyBatis+MySQL就业管理系统架构设计与部署全解析

“企业级”这个前缀,在我把源码完整跑通之前,一直当成营销词来对待。大学生就业需求分析系统管理,名字听着很唬人,拆开看就是一套典型的权限管理 信息档案 数据统计组合项目。真正让我觉得它值得写一篇东西的,是它在…

2026/10/14 21:39:33 阅读更多 →
社交媒体英语口语怎么学?从看懂评论区到轻松接梗的实战指南

社交媒体英语口语怎么学?从看懂评论区到轻松接梗的实战指南

教英语口语这些年,我见过不少挺反常的现象:一个学生四六级高分,刷了三年美剧,读英语新闻毫不费力,结果真到了海外社交平台上,看着评论区一下就懵了。一条普通到不能再普通的评论就能让他怀疑自己学的是不是…

2026/10/14 21:39:33 阅读更多 →
Spring Boot+Vue家乡旅游宣传系统全栈开发实战解析

Spring Boot+Vue家乡旅游宣传系统全栈开发实战解析

我一直认为,像“基于 Spring Boot Vue 的家乡特色旅游宣传系统”这类题目,是检验 Java 全栈能力很合适的练手项目。它逻辑不复杂,但涉及的技术点足够全:用户端展示、管理后台、数据库设计、文件上传、接口联调、服务器部署&#…

2026/10/14 21:39:33 阅读更多 →
AI辅助开发实战:用Python重做一个mini版Photoshop

AI辅助开发实战:用Python重做一个mini版Photoshop

最近技术社区和设计圈都在讨论一件事:Adobe 更新应用内许可条款后,不少开发者担心“用 AI 辅助开发软件”会撞上版权红线;随后 Adobe 官方人员在社区公开回应,表示不会对使用 AI 辅助开发的非商业开源替代品采取法律行动&#xff…

2026/10/14 21:38:32 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29: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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →