C++如何生成随机数?(库完整用法)

2次阅读

应使用 std::random_device 初始化 std::mt19937 种子,而非 time(nullptr);std::random_device 用于获取熵源但不宜直接生成随机数,正确写法为 std::random_device rd; std::mt19937 gen{rd()};

C++如何生成随机数?(库完整用法)

std::random_devicestd::mt19937 生成真随机种子

直接用 time(nullptr) 初始化随机数引擎,基本等于没随机——同一秒内多次运行结果完全一样。现代 c++ 要求用 std::random_device 获取熵源,再喂给确定性引擎(比如 std::mt19937)。

  • std::random_device 不保证在所有平台都返回真随机(windows 上可能退化为伪随机),但它是最接近标准做法的起点
  • 别把它当随机数生成器直接用——它慢、调用次数有限制,只适合初始化一次种子
  • 常见错误:写成 std::mt19937 gen{std::random_device{}()};,这其实只取了一个 seed,正确做法是用它构造一个对象再调用 ()

推荐写法:

std::random_device rd; std::mt19937 gen{rd()}; // 或者更稳一点:std::mt19937 gen{rd() ^ (rd() << 32)};

std::uniform_int_distributionstd::uniform_real_distribution 控制范围

C++11 以后,rand()RAND_MAX 是遗留方案,分布不均、范围窄、不可控。真正可控的随机必须搭配分布类(distribution)。

  • std::uniform_int_distribution<int></int> 生成闭区间 [a, b] 的整数,注意是包含两端的,不是 Python 那种左闭右开
  • std::uniform_real_distribution<double></double> 默认生成 [0.0, 1.0) 的浮点数;若要 [a, b),得显式传参:std::uniform_real_distribution<double> dis(a, b)</double>
  • 分布对象可复用,但不要跨线程共享——它内部有状态,且非线程安全
  • 别把分布和引擎绑死在一个表达式里,比如 dis(gen)() + 1 这种链式调用容易误读,拆开写更清晰

示例:生成 [1, 6] 的骰子点数

立即学习C++免费学习笔记(深入)”;

std::uniform_int_distribution<int> dice(1, 6); int roll = dice(gen); // 注意:不是 dice(gen())

为什么不能把引擎和分布定义成全局/静态变量?

看似省事,实则埋雷。全局随机引擎在多线程下会竞争修改内部状态,导致未定义行为;而静态分布对象如果模板参数不同(比如 intlong),编译器可能实例化多个副本,但你未必意识到。

  • 最稳妥的方式是:在需要的地方按需构造引擎(用局部 std::random_device 初始化),再绑定局部分布对象
  • 若性能敏感(比如高频循环中),可将 gendis 封装进类成员,确保单个实例独占一套状态
  • 切忌在头文件里定义 Static inline std::mt19937 gen{...} —— 每个编译单元都会有一份,种子还可能重复

兼容旧代码时,rand() 的坑怎么绕?

有些老项目或第三方库依赖 rand(),强行替换风险高。这时可以临时桥接,但必须清楚代价:

  • rand() 只有 15 位有效精度(RAND_MAX 通常是 32767),远低于 std::mt19937 的 32 位输出
  • 它的默认分布严重偏向低位,rand() % N 会导致小数字概率偏高,尤其当 N 接近 RAND_MAX
  • 如果非要桥接,用 gen() 结果做掩码再模运算,比直接用 rand() 强:比如 (gen() & 0x7fff) % N
  • 但更推荐逐步替换:先封装一个 my_rand() 函数,内部用现代设施实现,再逐个替换调用点

事情说清了就结束。真要用好 C++ 随机数,核心就两条:种子别偷懒,分布别硬算。其他都是围绕这两条展开的细节。

text=ZqhQzanResources