std::random 随机数库
定义与作用
<random> 是 C++11 引入的随机数标准库,通过"随机数引擎 + 分布"的分离架构,提供高质量、可复现、多场景的随机数生成能力,全面替代 C 风格的 rand() / srand()。
#include <random>
std::mt19937 engine{std::random_device{}()}; // 梅森旋转引擎
std::uniform_int_distribution<int> dist(1, 6); // 均匀分布 [1, 6]
int dice = dist(engine); // 掷骰子
| 对比 | rand() / srand() | <random> |
|---|---|---|
| 质量 | 实现简陋,周期短 | 多种高质量引擎可选 |
| 范围控制 | 手动取模(有偏) | 分布类精确控制 |
| 可复现性 | 依赖全局状态 srand | 每个引擎独立状态 |
| 线程安全 | 全局状态,不安全 | 每个引擎独立,天然安全 |
| 分布类型 | 仅均匀分布 | 均匀/正态/伯努利/泊松等 |
核心原理
引擎与分布分离架构
常用引擎
| 引擎 | 特点 | 推荐场景 |
|---|---|---|
std::default_random_engine | 实现定义(通常 mt19937) | 通用场景 |
std::mt19937 | 梅森旋转,周期 2^19937-1,高质量 | 需要高质量伪随机数的场景 |
std::mt19937_64 | mt19937 的 64 位版本 | 需要 64 位随机数 |
std::minstd_rand | 线性同余,快速但质量低 | 对质量要求不高的快速场景 |
std::ranlux48 | 带奢侈消偏,极高质量 | 科学计算 |
常用分布
完整示例
示例一:飞翔科技抽奖系统
场景说明:赵鸣为飞翔科技年会设计抽奖系统,需要从 1000 名员工中公平抽取中奖者。
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <string>
#include <iomanip>
int main() {
std::cout << "===== 飞翔科技 2018 年会抽奖 =====" << std::endl;
// 员工名单(模拟)
std::vector<std::string> employees = {
"大翔", "白歌", "小崔", "黄俪", "李眉",
"孔蓝", "赵鸣", "孙鹤", "高英", "杨英",
"朱璐", "林鸥"
};
// 创建引擎:用 random_device 获取真随机种子
std::random_device rd;
std::mt19937 engine(rd());
// ---- 一等奖 (1名):均匀分布抽样 ----
std::cout << "\n[一等奖 — 均匀分布]" << std::endl;
std::uniform_int_distribution<std::size_t> firstPrize(0, employees.size() - 1);
auto winner1_idx = firstPrize(engine);
std::cout << "🥇 " << employees[winner1_idx] << " 获得一等奖!" << std::endl;
// ---- 二等奖 (2名):不重复抽取 ----
std::cout << "\n[二等奖 — 不重复抽样]" << std::endl;
std::vector<std::size_t> indices(employees.size());
for (std::size_t i = 0; i < indices.size(); ++i) indices[i] = i;
// 用 shuffle 代替逐个抽取避免重复
std::shuffle(indices.begin(), indices.end(), engine);
std::cout << "🥈 " << employees[indices[0]] << std::endl;
std::cout << "🥈 " << employees[indices[1]] << std::endl;
// ---- 三等奖:正态分布模拟评分 ----
std::cout << "\n[项目评分 — 正态分布模拟]" << std::endl;
std::normal_distribution<double> scoreDist(75.0, 10.0); // 均值75,标准差10
std::cout << "模拟 10 位评委对孔蓝提案的评分:" << std::endl;
for (int i = 0; i < 10; ++i) {
double score = std::clamp(scoreDist(engine), 0.0, 100.0);
std::cout << " 评委" << (i + 1) << ": "
<< std::fixed << std::setprecision(1) << score << " 分" << std::endl;
}
// ---- 阳光普照奖:伯努利分布决定随机金额 ----
std::cout << "\n[阳光普照奖 — 伯努利随机金额]" << std::endl;
std::bernoulli_distribution isBig(0.3); // 30% 概率获得大红包
std::uniform_int_distribution<int> smallRed(50, 100);
std::uniform_int_distribution<int> bigRed(188, 888);
for (const auto& name : {"黄俪", "李眉", "杨英"}) {
int amount = isBig(engine) ? bigRed(engine) : smallRed(engine);
std::cout << " " << name << ": ¥" << amount << std::endl;
}
// ---- 固定种子复现 ----
std::cout << "\n[复现验证 — 固定种子]" << std::endl;
std::mt19937 fixedEngine(42); // 固定种子 = 42
std::uniform_int_distribution<int> fixedDist(1, 100);
std::cout << "种子=42 的前5个随机数: ";
for (int i = 0; i < 5; ++i) {
std::cout << fixedDist(fixedEngine) << " ";
}
std::cout << "\n(每次运行结果相同,便于调试复现)" << std::endl;
return 0;
}
预期输出(每次运行一等奖获奖者不同,但固定种子部分相同):
===== 飞翔科技 2018 年会抽奖 =====
[一等奖 — 均匀分布]
🥇 小崔 获得一等奖!
[二等奖 — 不重复抽样]
🥈 孔蓝
🥈 林鸥
[项目评分 — 正态分布模拟]
模拟 10 位评委对孔蓝提案的评分:
评委1: 82.3 分
评委2: 68.7 分
...
[阳光普照奖 — 伯努利随机金额]
黄俪: ¥550
李眉: ¥72
杨英: ¥95
[复现验证 — 固定种子]
种子=42 的前5个随机数: 65 25 80 23 17
(每次运行结果相同,便于调试复现)
逐段分析:
std::random_device{}()获取硬件熵源的真随机种子——确保每次运行不可预测std::shuffle配合引擎实现不重复抽样,比手动循环 + 检查更简洁高效normal_distribution(75.0, 10.0)生成均值为 75、标准差为 10 的正态分布——适合模拟自然评分std::clamp(C++17)将评分裁剪到 [0, 100] 范围内,避免异常值bernoulli_distribution(0.3)以 30% 概率返回 true——实现概率性决策- 固定种子
engine(42)使序列可复现——调试和测试时的关键需求
示例二:随机数生成性能对比与陷阱
场景说明:小崔测试随机数性能并演示常见陷阱。
#include <iostream>
#include <random>
#include <chrono>
#include <cstdlib> // rand / srand
#include <ctime>
// 计时辅助函数
template <typename F>
double measure_ms(F&& func, int iterations) {
auto start = std::chrono::steady_clock::now();
func(iterations);
auto end = std::chrono::steady_clock::now();
return std::chrono::duration<double, std::milli>(end - start).count();
}
int main() {
std::cout << "===== 飞翔科技随机数性能测试 =====" << std::endl;
const int N = 10'000'000;
// ---- rand() 方案 ----
auto test_rand = [](int n) {
volatile long long sum = 0;
for (int i = 0; i < n; ++i) {
sum += rand() % 6 + 1; // 模拟掷骰子
}
};
// ---- mt19937 + uniform_int_distribution ----
auto test_mt = [](int n) {
std::mt19937 eng(42);
std::uniform_int_distribution<int> dist(1, 6);
volatile long long sum = 0;
for (int i = 0; i < n; ++i) {
sum += dist(eng);
}
};
// ---- minstd_rand(快速引擎) ----
auto test_minstd = [](int n) {
std::minstd_rand eng(42);
std::uniform_int_distribution<int> dist(1, 6);
volatile long long sum = 0;
for (int i = 0; i < n; ++i) {
sum += dist(eng);
}
};
std::srand(static_cast<unsigned>(std::time(nullptr)));
std::cout << "模拟 " << N << " 次掷骰子:" << std::endl;
std::cout << " rand(): "
<< measure_ms(test_rand, N) << " ms" << std::endl;
std::cout << " mt19937 + distribution: "
<< measure_ms(test_mt, N) << " ms" << std::endl;
std::cout << " minstd_rand + distribution: "
<< measure_ms(test_minstd, N) << " ms" << std::endl;
// ---- 常见陷阱1:每次调用都创建引擎 ----
std::cout << "\n[陷阱1: 每次创建引擎]" << std::endl;
auto bad_loop = []() {
// 每次循环都创建一个新引擎并播种——极慢且随机性差
for (int i = 0; i < 5; ++i) {
std::random_device rd;
std::mt19937 eng(rd());
std::uniform_int_distribution<int> dist(1, 6);
std::cout << " " << dist(eng);
}
std::cout << std::endl;
};
auto good_loop = []() {
// 引擎和分布创建一次,复用
std::random_device rd;
std::mt19937 eng(rd());
std::uniform_int_distribution<int> dist(1, 6);
for (int i = 0; i < 5; ++i) {
std::cout << " " << dist(eng);
}
std::cout << std::endl;
};
std::cout << "错误做法(每次创建新引擎):"; bad_loop();
std::cout << "正确做法(复用引擎和分布):"; good_loop();
// ---- 常见陷阱2:每次调用都创建 random_device(某些平台可能抛异常) ----
std::cout << "\n[陷阱2: random_device 可能不可用]" << std::endl;
try {
std::random_device rd;
std::cout << "random_device 熵: " << rd.entropy() << std::endl;
std::cout << "生成一个随机数: " << rd() << std::endl;
} catch (const std::exception& e) {
std::cout << "random_device 异常: " << e.what()
<< " — 回退到时间种子" << std::endl;
std::mt19937 fallback(
static_cast<unsigned>(std::chrono::steady_clock::now()
.time_since_epoch().count()));
}
// ---- rand() 取模偏差问题 ----
std::cout << "\n[陷阱3: rand() % N 的取模偏差]" << std::endl;
std::cout << "rand() 范围: [0, "
<< RAND_MAX << "]" << std::endl;
std::cout << "对于 rand() % 6 + 1 掷骰子:" << std::endl;
std::cout << " 1~4 出现的概率略高于 5~6" << std::endl;
std::cout << " 因为 32767 % 6 = 1,前 1 个余数多出现一次" << std::endl;
std::cout << " uniform_int_distribution 无此问题" << std::endl;
return 0;
}
预期输出:
===== 飞翔科技随机数性能测试 =====
模拟 10000000 次掷骰子:
rand(): 245.3 ms
mt19937 + distribution: 312.7 ms
minstd_rand + distribution: 189.5 ms
[陷阱1: 每次创建引擎]
错误做法(每次创建新引擎): 3 3 3 3 3
正确做法(复用引擎和分布): 4 2 6 1 5
[陷阱2: random_device 可能不可用]
random_device 熵: 32
生成一个随机数: 1842304721
[陷阱3: rand() % N 的取模偏差]
rand() 范围: [0, 32767]
对于 rand() % 6 + 1 掷骰子:
1~4 出现的概率略高于 5~6
因为 32767 % 6 = 1,前 1 个余数多出现一次
uniform_int_distribution 无此问题
逐段分析:
minstd_rand是速度最快的标准引擎,适合对随机质量要求不高但速度敏感的场景,性能甚至优于rand()mt19937比rand()略慢,但质量远超后者——2^19937-1 的周期 vs 通常 2^31 的周期- 陷阱 1:每次循环都创建
random_device+mt19937极慢,且random_device的熵可能耗尽,导致连续产生相同"随机数" - 陷阱 2:
random_device在某些嵌入式平台或 MinGW 上可能不可用或熵为 0——需要提供std::chrono回退方案 - 陷阱 3:
rand() % N当 N 不能整除RAND_MAX + 1时产生取模偏差——uniform_int_distribution内部使用拒绝采样消除偏差
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
| 不设种子或种种子为 0 | 每次运行相同随机序列 | 用 std::random_device 或时间戳做种子 |
| 每次调用都创建引擎 | 性能极差、随机性被破坏 | 引擎和分布是重对象,应复用 |
用 rand() % N | 取模偏差 + 低质量 | 用 uniform_int_distribution |
random_device 不可用未处理 | MinGW 下可能抛异常 | 提供回退方案(时间戳种子) |
| 多线程共享同一引擎 | 数据竞争 | 每个线程独立的引擎实例,或用 thread_local |
常见面试问题
<random>比rand()/srand()好在哪?——三方面:(1) 引擎质量高(mt19937 周期远超 rand()),(2) 分布丰富(正态、伯努利等),(3) 引擎状态独立,天然线程安全,可复现。rand() 有全局状态和取模偏差问题。引擎和分布为什么要分离?——单一职责。引擎负责产生均匀分布的原始随机比特,分布负责将原始比特映射到目标分布。分离后可以独立替换:换引擎不影响分布逻辑,换分布不影响引擎。
std::random_device是什么?和伪随机引擎有何区别?——random_device尽量使用硬件熵源产生真随机数,不可复现,适合做种子。伪随机引擎(如 mt19937)是确定性的,给定种子产出相同序列,适合需要复现的场景。为什么推荐用
std::shuffle而非手写 Fisher-Yates?——std::shuffle接受 URBG(Uniform Random Bit Generator)接口,可直接传入标准库引擎,避免手写rand() % N的偏差问题。
小结
- 核心架构:引擎(产生随机比特) + 分布(映射到目标分布),两者独立可换
std::random_device提供真随机种子,std::mt19937是最常用的高质量引擎- 引擎和分布对象应复用,不要每次调用都创建
- 分布类(
uniform_int_distribution/normal_distribution/bernoulli_distribution等)覆盖常用概率模型 - 固定种子使序列可复现——调试和单元测试的关键能力
- 多线程中每个线程应有独立的引擎实例,或使用
thread_local存储