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

用 std::random_device 和 std::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_distribution 和 std::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())
为什么不能把引擎和分布定义成全局/静态变量?
看似省事,实则埋雷。全局随机引擎在多线程下会竞争修改内部状态,导致未定义行为;而静态分布对象如果模板参数不同(比如 int 和 long),编译器可能实例化多个副本,但你未必意识到。
- 最稳妥的方式是:在需要的地方按需构造引擎(用局部
std::random_device初始化),再绑定局部分布对象 - 若性能敏感(比如高频循环中),可将
gen和dis封装进类成员,确保单个实例独占一套状态 - 切忌在头文件里定义
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++ 随机数,核心就两条:种子别偷懒,分布别硬算。其他都是围绕这两条展开的细节。









