随机数生成的历史背景

Middle-Square 方法(中位平方法):

  • 已知最早的随机算法之一
  • 或由修道士 Brother Edvin 在 1245 年发明
  • 由 John von Neumann 在 1949 年重新发现
  • 缺点明显,但执行速度快

Monte Carlo 方法:

  • 起初是机密代码名
  • 由 Stanislaw Ulam 和 John von Neumann 在 1946 年为曼哈顿计划开发
  • 在 ENIAC 上实现
  • 通过大量模拟生成随机样本,再用统计方法分析其行为
  • 直到今天仍广泛用于复杂系统模拟

随机数的现代用途

应用示例
模拟蒙特卡洛方法、物理/金融模拟等
算法遗传算法、随机算法、Zobrist 哈希
排列洗牌、随机抽签、比赛排位、选举
选择游戏抽奖、学生点名、艺术创作、快速排序 pivot
抽样医药实验、调查问卷、审计、生产质检
加密需要额外强度,不适用 <random>

注意:密码学需求远高于 <random> 能提供的强度,需使用操作系统安全 API 或专用库(如 OpenSSL、libsodium 等)

识别“随机”本身就是难题

“假设神谕者给了你十个连续的 0,你会觉得这是随机的吗?”

  • 这是一个引发思考的问题:十个连续的零看起来像是非随机的,但事实上,在随机数序列中,这样的情况完全可能出现
  • Pete Becker 的观点是:十个数字太少了,无法判断一个随机数生成器的质量。
  • 同样,Scott Adams(Dilbert漫画作者) 用讽刺幽默的方式表达了类似的观点 —— 我们人类对“看起来随机”的直觉,往往靠不住。

人类直觉在“随机性”问题上常常失灵

P. Diaconis 的研究:硬币落地后落在原来同一面朝上的概率其实略大于 50%(约 51%)。

  • 表明真实世界中的“随机”也有物理偏差。
  • 比如,头朝上的硬币更容易再次朝上朝上——这打破了我们对抛硬币“完美对半概率”的直觉认知。

Joe Peach 的故事:一个人在赌桌上用硬币决定是否要要牌,结果硬币立在了边上 —— 一种极小概率但确实可能发生的“随机事件”。

实现真正的随机行为非常困难

  • Chris Wetzel 指出:统计意义上的“随机”意味着不可预测无法发现模式
  • Jeff Atwood 和 Mads Haahr 都提到:计算机是“决定性”机器,本质上无法直接生成真正的随机数。
  • 随机数相关的教材和论文中,充斥着最终被证明有缺陷或完全错误的算法,说明这确实是一个“看起来简单,实际上复杂”的问题领域。

培训严重不足

  • 编程教育很少系统讲授随机性、概率分布、随机变量等知识。
  • 一个看似简单的例子:
    如果 f() 是均匀分布的,那么 2 * f() 也是均匀分布,
    f() + f() 却变成了接近正态分布(高斯分布) —— 并非均匀。

    这意味着我们即使对随机数做了很小的变换,也可能改变其分布性质!

挑战性问题(由 von Neumann 提出):

给定一个有偏的硬币(概率未知,0 < p < 1)
如何用它生成公平(等概率)的结果?
提示:Wikipedia 上“公平硬币”条目有解法(可查找 von Neumann extractor)
→ 但作者建议你先自己思考

建议:务必极其小心!(

  • Stephen K. Park 和 Keith W. Miller:大多数随机数生成器其实都存在非随机行为,有些甚至“糟糕透顶”。
  • Brad Lucier:历史上确实有不少“非常糟糕的” RNG 算法被发表在权威期刊,并被广泛采用。
  • detly 博客名言:对随机数做任意运算,并不意味着输出依然是随机的!

最后 —— 一点讽刺幽默

“你得到了你要求的,但不是你想要的。”

  • 很多程序员写随机代码的时候“语法正确”,但输出的结果和预期完全不符。
  • 在 RNG 上更容易出这种“技术上没错、但实际上错了”的情况。

用户分类:

  1. 初级用户
    • 一个默认引擎类型(std::default_random_engine
    • 两个通用的均匀分布模板(整数、实数)
  2. 中高级用户
    • 9 个预设引擎类型(如 Mersenne Twister)
    • 18 个可配置的分布类型(如正态、泊松等)
    • 一个系统随机源类(std::random_device
    • 一个辅助种子类(std::seed_seq
  3. 专家用户
    • 6 个可配置引擎模板(如线性同余、混合引擎等)
    • 一个自定义分布模板工具
      说明:<random> 设计为支持从简单用途复杂科研级别的随机数生成。

C++ 中的术语(Slide 18)

术语混乱的历史:

传统术语“随机数生成器”太模糊了!

<random> 中,我们区分两个核心组件:

  1. Engine(引擎)
    • 生成原始、均匀、伪随机的比特序列。
    • 理想上是 不可预测 的(虽然计算机做不到真正随机)。
    • std::mt19937std::default_random_engine 等。
  2. Distribution(分布)
    • 从 engine 的比特流中抽样,转换为特定分布的数值。
    • 如:
      • std::uniform_int_distribution
      • std::normal_distribution
      • std::poisson_distribution
      • std::chi_squared_distribution
      • std::weibull_distribution
        结论:引擎负责“比特”,分布负责“形状”。

引擎类型和用法

  • 引擎对象 e 在每次调用 e() 时返回一串伪随机的比特(长度为 n),以 E::result_type 编码的整数形式返回。
  • 比如:
    • std::mt19937::min() = 0
    • std::mt19937::max() = 2³² - 1
  • 每次调用返回的数是可预测的(伪随机),但难以预测(设计目标)。
  • 一般不需要直接使用返回的比特串,而是通过分布对象来使用引擎。

分布类型和用法

  • 分布对象 d 通过调用 d(e) 获取一个符合分布的随机值。
  • 示例 1:掷骰子函数
#include <random>
int roll_a_fair_die() {static std::default_random_engine e{};static std::uniform_int_distribution<int> d{1, 6};return d(e); // 返回 1 到 6 的伪随机整数
}
  • 示例 2:生成长度为 N 的数组,填入掷骰子结果
constexpr std::size_t N = 1000;
int a[N];
std::generate(a, a + N, roll_a_fair_die); // 用骰子函数填数组

注意事项:

  • 使用 static 是为了避免每次调用重新构建引擎,影响性能与随机性。
  • 可将引擎设为全局、共享,便于多个模块使用同一随机序列。

小结这一部分的要点:

内容要点
rand()不可移植,质量差,应避免使用
<random>可扩展、高质量、现代化设计
Engine生成伪随机比特流
Distribution将比特流映射为具有特定统计特性的值
示例使用引擎 + 分布组合获得掷骰子的整数

对C++ <random> 头文件中随机数引擎和分布的深入讲解,重点涵盖了以下几个方面:

1. random_device 作为 URBG(Uniform Random Bit Generator)

  • random_device 设计用来访问物理或环境随机源,比如 /dev/random、CPU 的 RDRAND 指令,或者大气噪声等。
  • 它的构造函数带一个实现定义的字符串,用来指定设备。
  • 它有 entropy() 成员函数,用来估计该设备产生的随机性的熵值(信息量)。
  • 这保证了你可以用它作为真正随机(或接近真实随机)的随机数生成器。

2. 提供的 20 种分布

  • 包含五类:
    • 均匀分布uniform_int_distribution, uniform_real_distribution
    • 伯努利及相关分布bernoulli_distribution, binomial_distribution, geometric_distribution, negative_binomial_distribution
    • 泊松及相关分布poisson_distribution, exponential_distribution, gamma_distribution, weibull_distribution, extreme_value_distribution
    • 正态及相关分布normal_distribution, lognormal_distribution, chi_squared_distribution, cauchy_distribution, fisher_f_distribution, student_t_distribution
    • 采样分布discrete_distribution, piecewise_constant_distribution, piecewise_linear_distribution
  • 设计理由是这些分布是统计、工程和科学应用中最常见、最重要的。

3. 分布与引擎的互操作性

  • 任何实现了 URBG 接口的引擎都可以和任意分布搭配使用。
  • 这保证了扩展性,用户可以自定义新的引擎和分布,且和标准库无缝集成。
  • 例如,PCG、Random123 是第三方扩展引擎,GCC 自带了一些额外的分布。

4. 自定义分布的辅助工具

  • generate_canonical<>() 是一个模板工具,能从 URBG 中产生一个在 [0,1) 均匀分布的浮点数,作为构造自定义分布的基础。

5. 分布的统计测试

  • 通过均值、方差、卡方检验、Kolmogorov-Smirnov 检验等统计测试验证分布是否合理。
  • 偶尔失败是正常的,失败太多才要怀疑随机数生成的质量。

6. 不要滥用 rand() % n 取模法

  • 这个简单取余方法会导致偏差,因为 rand() 的范围通常不是 n 的整数倍。
  • 应该用标准库的分布,比如 std::uniform_int_distribution,或者通过拒绝采样(rejection sampling)实现均匀分布。

7. 如何正确生成随机数

  • 实例化一个引擎(如 std::default_random_engine
  • 实例化一个分布(如 std::uniform_int_distribution<int>)
  • 每次生成随机数时通过 distribution(engine) 产生,确保满足所需分布。

8. 种子与可复现性

  • 引擎种子很关键,单个整数种子对大型状态的引擎(如 Mersenne Twister)不足。
  • std::seed_seq 试图扩展种子,但不总是效果很好。
  • 种子来源可以是时间戳、进程ID等,但它们质量较低。

9. C++17 之后新增的随机数算法

  • std::sample:可以从一个范围中采样固定大小的随机子集。
  • std::shuffle:随机打乱序列。
  • 不再推荐使用 random_shuffle,它已被移除。

10. 迭代器接口(可选进阶)

  • 设计了 variate_iterator,可以把一个随机引擎 + 分布封装成输入迭代器,从而在标准算法中直接使用随机数。
    总结起来,这些内容帮你深入理解 <random> 设计理念、最佳实践、性能与质量权衡、以及如何用好它来写出健壮的随机数代码。

你这段内容介绍了 C++17 <random> 库中的一些高级用法,特别是 sample()shuffle() 的用法,还有用迭代器接口封装随机数分布生成的思路。

1. sample() 用法

C++17 新增了 std::sample(),功能是从一个区间(population)中随机采样固定数量元素。接口:

template<class PopulationIter, class SampleIter, class Size, class URBG>
SampleIter sample(PopulationIter first, PopulationIter last,SampleIter out, Size wanted_size, URBG&& g);
  • PopulationIter 是输入区间的迭代器。
  • SampleIter 是输出区间迭代器。
  • wanted_size 是采样元素数。
  • URBG 是随机数生成器。
    实现时,会根据输入迭代器能力,选择不同算法。

2. shuffle() 用法

用于将序列随机打乱。
示例:

using card_t = int;
using deck_t = std::array<card_t, 52>;
deck_t deck;
std::iota(deck.begin(), deck.end(), 0);  // 生成0~51
using engine_t = std::default_random_engine;
engine_t e{ std::random_device{}() };     // 真随机种子
std::shuffle(deck.begin(), deck.end(), e);
// 输出花色和点数
auto suit = [](card_t c) { return "♠♥♦♣"[c / 13]; };
auto rank = [](card_t c) { return "A23456789TJQK"[c % 13]; };
for (auto c : deck)std::cout << rank(c) << suit(c) << ' ';

3. shuffle_sort 的“反直觉”示例

template<class RA, class Engine>
void shuffle_sort(RA b, RA e, Engine g)
{while (!std::is_sorted(b, e))std::shuffle(b, e, g);
}
  • 这个算法随机打乱序列直到它排序好了,显然效率非常差,作为玩笑演示。

4. 自定义 shuffle 实现示例

这段代码体现了 Fisher–Yates 洗牌算法:

template<class RA, class URBG>
void shuffle(RA b, RA e, URBG&& g)
{using diff_t = decltype(e - b);using dist_t = std::uniform_int_distribution<diff_t>;using param_t = typename dist_t::param_type;static dist_t d;  // 分布范围由调用时动态传入diff_t L = e - b - 1;for (diff_t m = 0; m < L; ++m)std::iter_swap(b + m, b + d(g, param_t{m, L}));
}
  • d(g, param_t{m, L}) 生成 [m, L] 范围内的随机数。
  • 逐步交换未洗牌部分的元素,保证均匀性。

5. 设计基于 <random> 的迭代器 variate_iterator

这是一个可用作输入迭代器的类,封装了随机数引擎和分布,使其可以像迭代器一样访问随机数流。

代码草图解析

template<class URBG, class Dist>
class variate_iterator : public std::iterator<std::input_iterator_tag, typename Dist::result_type>
{
private:using val_t = typename Dist::result_type;URBG* u{nullptr};   // 非拥有指针,指向随机数生成器Dist* d{nullptr};   // 非拥有指针,指向分布val_t v{};          // 最近一次生成的随机数值bool valid{false};  // 当前缓存的值是否有效void step() noexcept { valid = false; }val_t& deref() {if (!valid) {v = (*d)(*u);  // 调用分布生成新的随机数valid = true;}return v;}
public:constexpr variate_iterator() noexcept = default;variate_iterator(URBG& u_, Dist& d_) : u(&u_), d(&d_) {}val_t& operator*() { return deref(); }val_t const* operator->() { return &deref(); }variate_iterator& operator++() { step(); return *this; }variate_iterator operator++(int) {variate_iterator tmp = *this;step();return tmp;}// TODO: 添加相等比较等必要接口,符合输入迭代器要求
};

用途示例

  • 生成随机数序列并复制到容器:
std::default_random_engine engine{std::random_device{}()};
std::uniform_real_distribution<double> dist(0.0, 1.0);
variate_iterator<std::default_random_engine, decltype(dist)> it(engine, dist);
std::vector<double> v(100);
std::copy_n(it, 100, v.begin());
  • 用于数学操作,如点积:
double prod = std::inner_product(v.begin(), v.end(), it, 0.0);
  • 对已有向量加随机噪声:
std::transform(v.begin(), v.end(), v.begin(), [&](double x) { return x + *it++; });

总结

  • <random> 提供了强大的随机数工具,不光是生成单个随机数,也可以对序列做采样、洗牌。
  • 通过封装迭代器,可以让随机数生成器与 STL 算法更好地结合。
  • 了解算法实现(如 Fisher-Yates shuffle)能帮助你理解随机过程的正确性。
  • sample() 函数允许随机采样,避免了先打乱再选取的低效方法。

几个简单实用的随机数小例子(小李子),用C++ <random>写的,涵盖不同类型的随机数生成:

1. 生成 0 到 99 的随机整数

#include <iostream>
#include <random>
int main() {std::random_device rd;  // 真随机种子std::mt19937 gen(rd()); // Mersenne Twister引擎std::uniform_int_distribution<> dist(0, 99); // 均匀分布 [0,99]for (int i = 0; i < 10; ++i) {std::cout << dist(gen) << " ";}std::cout << "\n";
}

2. 生成 0.0 到 1.0 之间的浮点数

#include <iostream>
#include <random>
int main() {std::random_device rd;std::mt19937 gen(rd());std::uniform_real_distribution<> dist(0.0, 1.0);for (int i = 0; i < 10; ++i) {std::cout << dist(gen) << " ";}std::cout << "\n";
}

3. 从容器中随机采样(使用 std::sample

#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
int main() {std::vector<int> population{1,2,3,4,5,6,7,8,9,10};std::vector<int> sample_result;std::random_device rd;std::mt19937 gen(rd());std::sample(population.begin(), population.end(), std::back_inserter(sample_result),4, gen);  // 从population随机采样4个元素for (int x : sample_result) {std::cout << x << " ";}std::cout << "\n";
}

4. 洗牌一副扑克牌(52张)

#include <iostream>
#include <array>
#include <algorithm>
#include <random>
int main() {using card_t = int;std::array<card_t, 52> deck;std::iota(deck.begin(), deck.end(), 0);  // 0~51std::random_device rd;std::mt19937 gen(rd());std::shuffle(deck.begin(), deck.end(), gen);auto suit = [](card_t c) { return "♠♥♦♣"[c / 13]; };auto rank = [](card_t c) { return "A23456789TJQK"[c % 13]; };for (auto c : deck) {std::cout << rank(c) << suit(c) << " ";}std::cout << "\n";
}

5. 用随机数给数组元素加点噪声

#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
int main() {std::vector<double> data = {1.0, 2.0, 3.0, 4.0, 5.0};std::random_device rd;std::mt19937 gen(rd());std::normal_distribution<> noise(0, 0.1); // 均值0,标准差0.1的高斯噪声std::transform(data.begin(), data.end(), data.begin(),[&](double x) { return x + noise(gen); });for (auto d : data) {std::cout << d << " ";}std::cout << "\n";
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/84264.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/84264.shtml
英文地址,请注明出处:http://en.pswp.cn/web/84264.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Origin:误差棒点线图绘制

1.首先将你的数据复制到表格 2.选中B(y)列数据&#xff0c;依次点击图示选项 3.选中图中红框数据&#xff0c;点击绘制点线图即可 4.结果展示

Spring 源码学习 1:ApplicationContext

Spring 源码学习 1&#xff1a;ApplicationContext Bean 定义和 Bean 实例 AnnotationConfigApplicationContext 首先&#xff0c;创建一个最简单的 Spring Boot 应用。 在入口类中接收SpringApplication.run的返回值&#xff1a; SpringBootApplication public class Dem…

CppCon 2017 学习:Design Patterns for Low-Level Real-Time Rendering

这段内容讲的是离散显卡&#xff08;Discrete GPU&#xff09;中的内存管理模型&#xff0c;重点是CPU和GPU各自独立管理自己的物理内存&#xff0c;以及它们如何通过虚拟内存和DMA引擎实现高效通信。以下是详细的理解和梳理&#xff1a; 1. 基本概念 CPU 和 GPU 是两个独立的…

【单调队列】-----【原理+模版】

单调队列 一、什么是单调队列&#xff1f; 单调队列是一种在滑动窗口或区间查询中维护候选元素单调性的数据结构&#xff0c;通常用于解决“滑动窗口最大值/最小值”等问题。 核心思想是&#xff1a;利用双端队列&#xff08;deque&#xff09;维护当前窗口内或候选范围内元素…

CSS语法中的选择器与属性详解

CSS:层叠样式表&#xff0c;Cascading Style Sheets 层叠样式表 内容和样式分离解耦&#xff0c;便于修改样式。 特殊说明&#xff1a; 最后一条声明可以没有分号&#xff0c;但是为了以后修改方便&#xff0c;一般也加上分号为了使用样式更加容易阅读&#xff0c;可以将每条代…

模拟设计的软件工程项目

考核题目 论文论述题&#xff1a;结合你 参与开发、调研或模拟设计的软件工程项目 &#xff0c;撰写一篇论文 完成以下任务&#xff0c;论文题目为《面向微服务架构的软件系统设计与建模分析》&#xff0c;总分&#xff1a; 100 分。 1. 考核内容&#xff1a; 一、系统论述…

个人理解redis中IO多路复用整个网络处理流

文章目录 1.redis网络处理流2.理解通知机制 1.redis网络处理流 10个客户端通过TCP与Redis建立socket连接&#xff0c;发送GET name指令到服务器端。服务器端的网卡接收数据&#xff0c;数据进入内核态的网络协议栈。Redis通过IO多路复用机制中的epoll向内核注册监听这些socket的…

【郑州轻工业大学|数据库】数据库课设-酒店管理系统

该数据课设是一个基于酒店管理系统的数据库设计 建库语句 create database hotel_room default charset utf8 collate utf8_general_ci;建表语句 use hotel_room;-- 房型表 create table room_type( id bigint primary key auto_increment comment 房型id, name varchar(50)…

TCP 三次握手与四次挥手详解

前言 在当今互联网时代&#xff0c;前端开发的工作范畴早已超越了简单的页面布局和交互设计。随着前端应用复杂度的不断提高&#xff0c;对网络性能的优化已成为前端工程师不可忽视的重要职责。而要真正理解并优化网络性能&#xff0c;就需要探究支撑整个互联网的基础协议——…

RTD2735TD/RTD2738 (HDMI,DP转EDP 高分辨率高刷新率显示器驱动芯片)

一、芯片概述 RTD2738是瑞昱半导体&#xff08;Realtek&#xff09;推出的一款高性能显示驱动芯片&#xff0c;专为高端显示器、便携屏、专业显示设备及多屏拼接系统设计。其核心优势在于支持4K分辨率下240Hz高刷新率及8K30Hz显示&#xff0c;通过集成DisplayPort 1.4a与HDMI …

C++实现手写strlen函数

要实现求字符串长度的函数&#xff0c;核心思路是通过指针或索引遍历字符串&#xff0c;直到遇到字符串结束标志 \0 。以下是两种常见的实现方式&#xff1a; 指针遍历版本 #include <iostream> using namespace std; // 指针方式实现strlen size_t myStrlen(const cha…

NVPL 函数库介绍和使用

文章目录 NVPL 函数库介绍和使用什么是 NVPLNVPL 的主要组件NVPL 的优势安装 NVPL基本使用示例示例1&#xff1a;使用 NVPL RAND 生成随机数示例2&#xff1a;使用 NVPL FFT 进行快速傅里叶变换 编译 NVPL 程序性能优化建议总结 NVPL 函数库介绍和使用 什么是 NVPL NVPL (NVI…

HTTP相关内容补充

目录 一、URI 和 URL 二、使用 Cookie 的状态管理 三、返回结果的 HTTP状态码 一、URI 和 URL URI &#xff1a;统一资源标识符 URL&#xff1a;统一资源定位符 URI 格式 登录信息&#xff08;认证&#xff09;指定用户名和密码作为从服务器端获取资源时必要的登录信息&a…

MySQL: Invalid use of group function

https://stackoverflow.com/questions/2330840/mysql-invalid-use-of-group-function 出错SQL: 错误原因&#xff1a; 1. 不能在 WHERE 子句中使用聚合&#xff08;或分组&#xff09;函数 2. HAVING 只能筛选分组后的聚合结果或分组字段 # Write your MySQL query statem…

C#财政票查验接口集成-医疗发票查验-非税收入票据查验接口

财政票据是企事业单位、医疗机构、金融机构等组织的重要报销凭证&#xff0c;其真实性、完整性和合规性日益受到重视。现如今&#xff0c;为有效防范虚假票据报销、入账、资金流失等问题的发生&#xff0c;财政票据查验接口&#xff0c;结合财政票据识别接口&#xff0c;旨在为…

浏览器基础及缓存

目录 浏览器概述 主流浏览器&#xff1a;IE、Chrome、Firefox、Safari Chrome Firefox IE Safari 浏览器内核 核心职责 主流浏览器内核 JavaScript引擎 主流的JavaScript引擎 浏览器兼容性 浏览器渲染 渲染引擎的基本流程 DOM和render树构建 html解析 DOM 渲染…

Ubuntu 安装Telnet服务

1. 安装Telnet 客户端 sudo apt-get install telnet 2. 安装Telnet 服务器 &#xff08;这样才能用A电脑的客户端连接B电脑的Telnet服务&#xff09; sudo apt-get install telnetd 3. 这时候Telnet服务器是无法自我启动的&#xff0c;需要网络守护进程服务程序来管理…

AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月19日第113弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀4-5个和值&#xff0c;可以做到100-300注左右。 (1)定…

观察者模式 vs 发布订阅模式详解教程

&#x1f31f;观察者模式 vs 发布订阅模式详解教程 收藏 点赞 关注&#xff0c;持续更新高频面试知识库&#xff01;&#x1f680; 一、核心概念&#xff08;总&#xff09; 在软件开发中&#xff0c;观察者模式&#xff08;Observer&#xff09; 和 发布订阅模式&#xff0…

【云馨AI-大模型】MD2Card:从Markdown到知识卡片的完美转变

Markdown的魅力与挑战MD2Card的核心功能使用体验与案例分析总结 在当今这个信息快速传播的时代&#xff0c;内容创作者们一直在寻找更有效的方式来呈现他们的想法和知识。无论是为了个人学习笔记、团队内部的知识分享还是对外的内容发布&#xff0c;一个清晰、美观的展示方式显…