C++11 库优于 rand():它分离随机源与分布,std::mt19937 引擎配合 std::uniform_int_distribution 可精准生成闭区间整数,避免偏差;浮点分布默认左闭右开,需手动处理闭区间;多线程须隔离引擎实例。

为什么 rand() 不该再用了
因为不可靠:默认种子只在程序启动时设一次,rand() % N 会产生严重偏差(尤其当 N 不是 RAND_MAX + 1 的约数),且无法控制分布类型。C++11 的 库从源头解决这两个问题——它把「随机源」和「数值变换」彻底分离。
-
std::random_device提供真随机或高质量伪随机种子(注意:某些平台如 MinGW 可能退化为常量,建议只用于初始化) -
std::mt19937(Mersenne Twister)是推荐的默认引擎,周期长、速度快、统计性质好 - 分布对象(如
std::uniform_int_distribution)负责把引擎输出的整数映射成你需要的范围和类型,不修改引擎状态
std::uniform_int_distribution 怎么用才不越界
它的构造参数是闭区间 [a, b],不是半开区间。传入 0, 9 就生成 0~9 共 10 个整数,这点和 Python 的 random.randint 一致,但和 C 的 rand() % 10 表面结果相同、底层逻辑完全不同。
std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distributiondist(1, 6); // 生成 1~6 的整数,不是 1~5 int dice = dist(gen); // 每次调用都取新值,gen 状态自动推进
- 分布对象可复用,不必每次新建;但不要跨线程共享同一个
gen实例(无锁访问不安全) - 若需多个不同范围的随机数,定义多个分布对象,共用一个引擎实例即可
- 传入负数没问题:
std::uniform_int_distribution是合法的neg(-5, 5)
浮点随机数为什么不能直接用 std::uniform_real_distribution 默认构造
默认构造的 std::uniform_real_distribution 生成的是 [0.0, 1.0),但很多场景需要 [0.0, 1.0] 或其他闭区间。注意:这个分布**不支持闭右端点**,标准规定它始终是左闭右开。
- 若要模拟「严格落在 [a,b] 内」,必须手动处理边界,例如:
a + (b - a) * dist(gen)中的dist仍是[0,1),结果天然落在[a, b)—— 你无法让b被取到(除非引擎恰好输出最大可能值,概率极低) - 对精度敏感的场景(如蒙特卡洛积分),应避免用
float引擎配double分布;统一用std::mt19937_64+std::uniform_real_distribution - 不要用
std::random_device直接生成浮点数(它只产生整数),必须经分布转换
多线程下怎么避免 std::mt19937 状态冲突
引擎实例不是线程安全的:并发调用 operator() 会破坏内部状态。常见错误是全局/静态引擎变量被多个线程同时读写。
立即学习“C++免费学习笔记(深入)”;
- 最简单方案:每个线程持有一个独立的
std::mt19937实例(用std::random_device初始化,或用线程 ID + 时间戳做种子) - 若必须共享,加互斥锁(性能差,不推荐);或改用无状态函数式接口(如 C++17 的
std::scoped_allocator_adaptor不适用,这里没捷径) - 注意:
std::random_device本身是线程安全的,但频繁调用可能耗尽熵池(Linux 下 /dev/random 阻塞),建议仅用于种子生成
真正麻烦的是调试时发现随机序列“突然重复”——往往是因为多个线程误用了同一个引擎对象,或者种子全用 time(nullptr) 导致初始化雷同。分布函数本身无状态,问题永远出在引擎生命周期管理上。









