C++原始字符串字面量:简化正则表达式与多行文本处理
1. 项目概述为什么我们需要原始字符串字面量在C编程的日常里处理字符串是家常便饭。但不知道你有没有遇到过这样的场景写一个正则表达式里面充满了反斜杠\比如\\d\\.\\d一眼看去密密麻麻的转义字符让人头晕眼花或者你需要把一个包含大量引号和换行符的JSON字符串、一段HTML代码、甚至是Windows文件路径直接写在代码里。这时候传统的字符串字面量就显得力不从心了你必须小心翼翼地给每一个特殊字符加上反斜杠进行转义不仅容易出错代码的可读性也直线下降。C11标准引入的原始字符串字面量就是为了解决这个“痛点”而生的。它就像给你的字符串加上了一个“保护罩”让字符串中的内容“所见即所得”。在这个保护罩里反斜杠不再是转义字符它就是普通的反斜杠换行符就是换行符不需要用\n来表示。这极大地简化了包含复杂字符序列的字符串编写工作。简单来说原始字符串字面量让你能以一种更自然、更清晰的方式在代码中嵌入复杂的文本数据。对于需要处理正则表达式、文件路径、多行文本、数据格式XML/JSON的开发者来说这绝对是一个能提升幸福感和代码质量的功能。接下来我们就从多个维度把它彻底拆解明白。2. 原始字符串字面量的核心语法与基本使用原始字符串字面量的语法初看有点奇怪但理解其结构后就非常直观。它的基本形式如下R“分隔符(原始字符序列)分隔符”我们来拆解这个结构前缀R这是原始字符串字面量的标志必须大写。双引号标志着字符串的开始和结束但在这里它和后面的括号是一体的。分隔符序列这是可选的可以是一个空序列也可以是由除括号、反斜杠和空白字符外的任何字符组成的序列。它出现在开头的(之前和结尾的)之后必须成对匹配。括号()括号内的所有内容就是原始的字符串数据。最常见的也是最简单的用法就是使用空分隔符std::string path R(C:\Users\Project\data.txt); std::string regex_pattern R(\d\.\d);在上面的例子中C:\Users\Project\data.txt被原封不动地存储无需将\写成\\。\d\.\d这个正则表达式也清晰可见点号.前面的反斜杠就是它本身。如果没有原始字符串它们将不得不写成std::string path C:\\Users\\Project\\data.txt; // 反斜杠需要转义 std::string regex_pattern \\d\\.\\d; // 反斜杠和点号都需要转义高下立判。原始字符串让代码意图一目了然极大地减少了因转义错误导致的Bug。2.1 处理包含括号的字符串分隔符的妙用原始字符串的强大之处在于其“分隔符”机制。如果我们的原始字符串内容里本身就包含了)和的组合比如)这就会提前终止原始字符串的解析导致编译错误。因为编译器会认为第一个出现的)就是字符串的结束标记。为了解决这个问题我们可以使用自定义的分隔符。分隔符可以是除了括号、反斜杠和空白字符外的任何字符序列。基本规则是开头的分隔符必须和结尾的分隔符完全一致。假设我们需要嵌入这样一段文本This is a test) and its tricky。直接使用空分隔符会失败// 错误编译器会在第一个 ) 处认为字符串结束导致后面的内容成为语法错误。 std::string bad_str R(This is a test) and its tricky);正确的做法是引入一个不会在字符串内容中出现的分隔符比如delimstd::string good_str Rdelim(This is a test) and its tricky)delim;现在编译器会寻找)delim作为结束标记。由于字符串内容里没有这个序列所以整个This is a test) and its tricky都会被正确识别为字符串内容。分隔符的选择很灵活常用下划线、字母组合等只要确保唯一性即可。// 使用 _ 作为分隔符 std::string str1 R_(Content with ) inside)_; // 使用 xyz 作为分隔符 std::string str2 Rxyz(Even more complex ))xyz)xyz;注意分隔符是大小写敏感的R“AA(...)AA”和R“aa(...)aa”是不同的。同时虽然理论上分隔符可以很长但为了代码清晰通常选择简短且能体现上下文意义的字符序列。3. 多行字符串与编码前缀的结合原始字符串字面量的另一个巨大优势是天然支持多行字符串。在传统字符串中换行需要显式写入\n字符串本身不能跨越多行除非在行尾使用反斜杠连接但这会影响格式。而原始字符串则允许你直接按照文本的视觉格式来书写std::string html_fragment R( !DOCTYPE html html head titleTest Page/title /head body h1Hello, Raw String!/h1 /body /html );这段代码定义了一个字符串其内容完全保留了缩进和换行。这对于嵌入模板、SQL语句、配置文本等场景非常方便。需要注意的是开头(之后的换行符以及结尾\n)之前的换行符都会被包含进字符串中。如果你不希望字符串以换行符开始或结束需要调整括号的位置。3.1 与其它字符串前缀结合使用C中字符串字面量可以有前缀来指定其编码类型例如u8表示 UTF-8 编码C11起用于字符串字面量。L表示宽字符wchar_t。u表示 UTF-16 编码char16_t。U表示 UTF-32 编码char32_t。原始字符串字面量可以完美地与这些前缀结合语法是将前缀放在R之前。这让你能方便地处理多行、无需转义的多语言文本。// UTF-8 编码的原始多行字符串 const char* utf8_raw_str u8R( 这是一个UTF-8编码的原始字符串。 它可以包含中文你好世界 也可以包含特殊符号\n\t 在这里就是字面意义上的反斜杠-n和反斜杠-t。 ); // 宽字符原始字符串 const wchar_t* wide_raw_str LR(C:\Program Files\MyApp\config.ini); // UTF-16 原始字符串 const char16_t* utf16_raw_str uR({ name: Raw String, value: 42 });这种组合极大地增强了字符串字面量的表达能力使得在源代码中直接初始化复杂的、多行的、特定编码的字符串数据变得异常简洁。实操心得在编写需要国际化的应用程序时将UI文本、提示信息等以UTF-8原始字符串的形式存放在独立的头文件或源文件中是一种非常清晰可维护的做法。比起从外部文件加载在编译时初始化能避免运行时I/O错误代码逻辑也更集中。4. 深入解析原始字符串在编译器眼中的样子理解原始字符串的编译过程能帮助我们更好地使用和调试它。从编译器的视角看原始字符串的处理发生在翻译过程的早期阶段特别是在“预处理”之后“语法和语义分析”之前的一个称为“字符串字面量连接”的阶段。词法分析编译器首先识别出R“分隔符(...)分隔符”这个完整的词法单元Token。去除分隔符和括号编译器会剥离掉前缀R、双引号、分隔符以及最外层的括号()。这个过程不进行任何转义字符的处理。括号内的所有字符包括反斜杠、换行符、制表符、引号等都被原样保留作为字符串的初始内容。编码转换如果指定了编码前缀如u8编译器会将保留下来的原始字节序列按照指定的编码方式进行解释和转换。生成字符串对象最终转换后的字符序列被用来初始化对应的字符串对象如std::string、const char[N]等。关键在于第2步——无转义处理。这意味着\n不会被转换成换行符而是保存为两个独立的字符反斜杠\和字母n。如果你需要一个真正的换行符你必须在源代码里直接按下回车键而不是写入\n。#include iostream #include string int main() { std::string raw R(Line1\nLine2); // 包含字符 \ n L i n e 2 std::string normal Line1\nLine2; // 包含换行符 std::cout Raw string output:\n raw std::endl; std::cout Normal string output:\n normal std::endl; // 查看长度和内容通过调试器或打印每个字符的整数值 std::cout Raw string length: raw.length() std::endl; // 输出可能是 12 (\n算两个字符) std::cout Normal string length: normal.length() std::endl; // 输出可能是 11 (\n算一个换行符) return 0; }这个例子清晰地展示了原始字符串和普通字符串在内容上的本质区别。理解这一点对于调试和与期望特定转义序列的API交互时至关重要。5. 实战应用场景与代码示例理论说再多不如看实战。原始字符串在以下几个场景中堪称“神器”。5.1 场景一正则表达式这是最经典的应用。正则表达式大量使用反斜杠原始字符串能让其逻辑一目了然。#include iostream #include regex #include string int main() { // 使用普通字符串 - 难以阅读和编写 // std::regex pattern(\\b(\\w)(\\s\\1\\b)); // 使用原始字符串 - 清晰直观 std::regex pattern(R(\b(\w)(\s\1\b))); std::string test_text hello hello world world; std::smatch matches; if (std::regex_search(test_text, matches, pattern)) { std::cout Found repeated word: matches[1] std::endl; } return 0; }5.2 场景二文件系统路径特别是WindowsWindows路径使用反斜杠在普通字符串中必须转义。#include filesystem // C17 #include iostream namespace fs std::filesystem; int main() { // 传统方式容易漏掉转义 // fs::path old_path C:\\Users\\Alice\\Documents\\report_2023\\final.docx; // 使用原始字符串路径清晰可辨 fs::path raw_path R(C:\Users\Alice\Documents\report_2023\final.docx); if (fs::exists(raw_path)) { std::cout File size: fs::file_size(raw_path) bytes\n; } else { std::cout File not found.\n; } return 0; }5.3 场景三嵌入式多行数据JSON/XML/SQL在测试、原型开发或配置中直接内嵌数据块非常方便。#include iostream #include string // 模拟一个解析JSON字符串的函数 void parse_json(const std::string json_str) { std::cout Parsing JSON:\n json_str.substr(0, 100) ...\n; // ... 实际解析逻辑 } int main() { // 使用原始字符串嵌入多行JSON std::string config_json R({ app_name: MyAwesomeApp, version: 1.0.0, settings: { debug: false, log_level: info, max_connections: 100 }, plugins: [plugin_a, plugin_b] }); parse_json(config_json); // 同样适用于SQL查询字符串 std::string complex_query R( SELECT u.name, o.order_id, o.total_amount FROM users u INNER JOIN orders o ON u.id o.user_id WHERE o.created_at 2023-01-01 AND o.status completed ORDER BY o.total_amount DESC LIMIT 10; ); std::cout \nGenerated SQL:\n complex_query std::endl; return 0; }5.4 场景四编写测试用例中的预期输出当测试函数的输出包含多行或特殊字符时原始字符串让预期值的定义变得简单。#include iostream #include string #include cassert std::string generate_table(int rows) { std::string result ID | Value\n; result -- | -----\n; for(int i 0; i rows; i) { result std::to_string(i) | std::to_string(i * i) \n; } return result; } void test_table_generator() { std::string expected R(ID | Value -- | ----- 0 | 0 1 | 1 2 | 4 3 | 9 ); std::string actual generate_table(4); assert(actual expected Table output mismatch!); std::cout Test passed!\n; } int main() { test_table_generator(); return 0; }6. 常见陷阱、疑难解答与性能考量即使是这样便利的功能在使用时也有一些“坑”需要注意。6.1 陷阱一字符串内容意外包含结束序列这是最常遇到的问题。如果你选择的字符串内容中恰好包含了你自己定义的分隔符结束序列)分隔符编译就会失败。解决方案选择更独特的分隔符增加分隔符的长度或使用不常见的字符组合。例如如果你担心)_可以使用MyDelim_123。代码审查在团队协作中对于使用长分隔符的原始字符串进行简单的审查确保内容中不包含结束序列。动态构建对于极端复杂或不可预测的内容考虑放弃使用原始字符串字面量转而使用普通字符串并动态构建例如使用字符串流std::ostringstream。6.2 陷阱二与需要转义序列的API不兼容有些库函数或系统API期望接收的字符串中包含的是真正的转义序列如\n代表换行\t代表制表符。如果你传递一个原始字符串其中的\n只是两个字符就会导致行为不符合预期。解决方案了解API契约阅读你所使用库的文档确认它需要的是字面字符还是转义后的字符。必要时转换如果API需要转义序列那么你应该使用普通字符串字面量或者将原始字符串中的字面序列如\n替换为真正的控制字符。这通常意味着原始字符串在此场景下不适用。// 假设有一个API要求字符串包含换行符 void print_with_newline(const char* str); std::string raw_str R(Hello\nWorld); // 这不行API看到的是 \ 和 n std::string normal_str Hello\nWorld; // 这才是正确的 // 或者如果你必须从原始字符串开始需要手动替换但通常设计上就有问题 // ... 复杂的替换逻辑 ...6.3 疑难原始字符串中的空格与缩进原始字符串会保留所有的空格、制表符和换行符。这在嵌入多行文本时是优点但有时也会引入不必要的空白字符特别是当你为了代码对齐而添加的缩进。std::string text R( 这行前面有4个空格。 这行没有。 );字符串text的第二行开头包含了4个空格。如果这不是你想要的你需要将左括号(放在行首或者调整代码格式。6.4 性能与存储考量从性能和存储角度看原始字符串字面量和普通字符串字面量在编译后的结果上没有本质区别。它们都是被存储在程序的只读数据段如.rodatasection中。主要的区别在于源代码级别的表示和编译器初始化的内容。编译时间原始字符串的解析可能略微简单因为不需要处理转义序列但这对于现代编译器来说差异微乎其微。运行时内存最终存储在二进制中的字符串内容取决于字符串的实际字节序列。一个原始字符串R“(\n)”在内存中占2字节\和n而普通字符串“\n”在内存中占1字节一个换行符。原始字符串不会自动节省内存它只是改变了源代码的书写方式。建议无需担心性能差异。选择原始字符串的首要标准是代码的清晰度和可维护性。在需要处理大量复杂字面量时它能显著减少错误。7. 原始字符串与C现代字符串操作的配合原始字符串字面量返回的是一个普通的字符数组它可以无缝地与C标准库中的字符串类型配合使用特别是std::string和std::string_viewC17。7.1 与std::string结合这是最直接的用法用于初始化或赋值。// 直接初始化 std::string sql_query R(SELECT * FROM table WHERE id 100); // 作为函数参数 void log_message(const std::string msg); log_message(R([ERROR] File not found: C:\temp\data.bin)); // 字符串拼接注意原始字符串字面量是const char[]可以参与拼接 std::string full_msg std::string(R()) value R(); // 结果为 \value\7.2 与std::string_view结合C17std::string_view提供字符串的非拥有式视图避免拷贝。原始字符串字面量是编译期常量非常适合用string_view来引用。#include string_view constexpr std::string_view kBaseUrl R(https://api.example.com/v1); constexpr std::string_view kDefaultConfig R({ timeout: 30, retries: 3 }); void connect_to_service(std::string_view endpoint) { // 使用endpoint避免拷贝长的字符串字面量 } connect_to_service(kBaseUrl);使用constexpr string_view来持有原始字符串是一种高效且表达力强的最佳实践尤其适用于全局或静态的配置字符串。7.3 在用户自定义字面量中的应用C11C11也引入了用户自定义字面量。虽然不能直接为原始字符串定义用户字面量因为原始字符串语法R“...”本身已经是一个完整的字面量但你可以定义一个操作const char*的用户字面量然后将其应用于原始字符串的结果上。// 定义一个将字符串转换为自定义类型的用户字面量示例 struct MyType { std::string data; }; MyType operator _my(const char* str, std::size_t len) { return MyType{std::string(str, len)}; } // 使用先有原始字符串再应用用户字面量 auto my_obj R({key: value})_my;这里R“(...)”首先产生一个const char数组然后_my操作符被调用处理这个数组。这展示了原始字符串如何融入C更广泛的字面量生态系统。我个人在实际项目中的体会是一旦习惯了原始字符串就再也回不去了。它在处理正则表达式、路径和嵌入式数据模板时带来的清晰度提升是颠覆性的。刚开始可能会觉得分隔符语法有点怪但用上几次后它就成了直觉。最关键的是它几乎没有任何运行时开销是纯粹的“语法糖”但却是一颗非常甜的糖能实实在在地减少错误提高代码的可读性和编写效率。下次当你手指准备输入一连串反斜杠时不妨停下来想想是不是该请出R“...”这个利器了。

相关新闻

AI 数码相机高能效小型化功率 MOSFET 选型方案

AI 数码相机高能效小型化功率 MOSFET 选型方案

随着 AI 计算摄影、实时HDR及高速连拍成为数码相机的核心功能,内部电源架构面临挑战:瞬时功耗大、空间受限、热管理要求高。微碧半导体(VBsemi)基于先进的Trench及SGT工艺,为您提供覆盖镜头驱动、电源管理、闪光灯控制…

2026/9/30 23:34:44 阅读更多 →
Linux 源码编译|私有YUM仓库|定时任务|进程调度 全套实操指南本文基于 CentOS7 环境,完整记录 软件源码安装、自建YUM私有仓库、一次性/周期性计划任务、Linux进程

Linux 源码编译|私有YUM仓库|定时任务|进程调度 全套实操指南本文基于 CentOS7 环境,完整记录 软件源码安装、自建YUM私有仓库、一次性/周期性计划任务、Linux进程

踩坑无数整理!Linux 源码编译|私有YUM仓库|定时任务|进程调度 全套实操指南 本文基于 CentOS7 环境,完整记录 软件源码安装、自建YUM私有仓库、一次性/周期性计划任务、Linux进程调度与优先级管理 全套实操命令与原理…

2026/10/5 11:00:43 阅读更多 →
AI排名实时快报:竞品动态分析

AI排名实时快报:竞品动态分析

AI排名实时快报:竞品动态分析繁淼信息——企业AI可见度综合健康度仅为59.6/100,竞争力57.5/100,可信度31.2/100。本报告基于多引擎数据,深度解析竞品真实提及量与空白查询率,为企业提供排名优化策略。市场格局&#xf…

2026/10/5 13:24:59 阅读更多 →

最新新闻

嵌入式Android 5.0编译环境搭建:Ubuntu版本选择与JDK配置避坑指南

嵌入式Android 5.0编译环境搭建:Ubuntu版本选择与JDK配置避坑指南

1. 为什么这套老环境至今还有人折腾如果你手里正好有一块 tiny4412 的开发板,或者正在跟着某套经典课程补嵌入式 Android 的底层知识,那你大概率绕不开一个现实问题:官方给的资料是十几年前的,配套的 Ubuntu 版本、JDK 版本、编译…

2026/10/12 10:13:00 阅读更多 →
016A轻量路由协议:嵌入式设备自组网的底层通信逻辑

016A轻量路由协议:嵌入式设备自组网的底层通信逻辑

1. 这不是教科书里的“路由协议”,而是你真正该懂的通信底层逻辑“016A 路由协议简单介绍”——看到这个标题,很多人第一反应是:又一个枯燥的网络概念?翻两页PPT就困了?但我想先说一句实话:这不是讲OSPF、B…

2026/10/12 10:13:00 阅读更多 →
IOS Safari 样式问题合集:从 backdrop-filter 到 input/button 的兼容排查与修复(持续更新)

IOS Safari 样式问题合集:从 backdrop-filter 到 input/button 的兼容排查与修复(持续更新)

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

2026/10/12 10:13:00 阅读更多 →
豫见OpenClaw·人工智能技术交流沙龙成功举办 埃文科技受邀主讲共探数智新路径|TaoToken 统一 Key 打通 OpenClaw 私有化落地

豫见OpenClaw·人工智能技术交流沙龙成功举办 埃文科技受邀主讲共探数智新路径|TaoToken 统一 Key 打通 OpenClaw 私有化落地

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

2026/10/12 10:13:00 阅读更多 →
第四次作业实战全流程复盘:从需求拆解到交付

第四次作业实战全流程复盘:从需求拆解到交付

很多人把“第四次作业”当成一次普通的例行任务,其实这个节点很特殊。前面三次作业大概率还是单项练习,测的是某个孤立的知识点,但第四次作业通常会发生一次明显跳跃:它开始要求你把前几次的技术点串起来,做成一个能跑…

2026/10/12 10:13:00 阅读更多 →
Hadoop实战:2000万行酒店数据从清洗到Spark分析全链路

Hadoop实战:2000万行酒店数据从清洗到Spark分析全链路

简介:这是一份面向大数据初学者与Hadoop实践者的完整项目资料,围绕全国各省市酒店数据的分析与处理展开,帮助读者掌握分布式存储与MapReduce编程的核心流程。资源共79个文件,以21个Java源码与21个class编译文件为主体,…

2026/10/12 10:11:59 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练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/11 14:36:54 阅读更多 →