c++中如何生成随机数_c++产生随机数函数用法

现代C++推荐用std::random_device和std::mt19937替代rand():前者提供真随机种子,后者为高质量伪随机引擎;须用random_device初始化mt19937,配合uniform_int_distribution等分布对象生成指定范围的均匀随机数,避免取模破坏均匀性。

std::random_devicestd::mt19937 生成高质量随机数

现代 C++ 推荐弃用 rand(),改用 `` 头文件中的引擎 + 分布组合。核心是:std::random_device 提供真随机种子(通常来自系统熵),std::mt19937 是高效、周期长的伪随机数引擎。

常见错误是直接用 std::mt19937{} 默认构造——它用固定种子,每次运行结果都一样。

  • 正确做法:用 std::random_device 初始化引擎,例如 std::mt19937 gen{std::random_device{}()}
  • std::mt19937 适合 32 位整数;若需 64 位,用 std::mt19937_64
  • 引擎对象应复用,不要每次生成一个新 gen,否则可能因构造开销或种子重复影响性能和随机性

std::uniform_int_distribution 生成指定范围整数

引擎只负责产出均匀分布的原始整数(如 uint32_t),要映射到自定义区间(比如 [1, 6] 模拟骰子),必须配合分布对象。直接对 gen() 结果取模(% 6 + 1)会破坏均匀性,尤其当范围不能整除引擎最大值时。

示例:生成 [10, 99] 的随机两位整数

std::mt19937 gen{std::random_device{}()};
std::uniform_int_distribution dist(10, 99);
int x = dist(gen); // 每次调用都返回一个符合分布的 int
  • 分布对象可复用,且线程安全(只要引擎不共享)
  • 模板参数 int 必须与传入的上下界类型一致,否则编译失败
  • 闭区间语义:dist(a, b) 包含 ab 两个端点

为什么不用 rand()srand(time(nullptr))

rand() 是 C 风格遗留函数,存在多个硬伤:低序位随机性差、最大值由 RAND_MAX 限定(常为 32767)、无法指定分布、且 srand() 只能设一次种子——如果程序启动太快(比如被脚本快速连启多次),time(nullptr) 返回相同值,导致多进程生成完全相同的随机序列。

  • 即使加 usleep(1000) 或用 getpid() 混合,也无法解决底层算法缺陷
  • 在竞赛或加密场景中,rand() 生成的序列容易被预测,std::mt19937 则更可靠
  • 某些平台(如 macOS)的 rand() 实现甚至不是线性同余,但依然缺乏标准分布支持

生成浮点随机数:用 std::uniform_real_distribution

整数分布不能直接用于 floatdouble;必须显式选用实数分布。注意:它默认生成半开区间 [a, b),即包含 a,不包含 b。

示例:生成 [0.0, 1.0) 的 double

std::mt19937_64 gen{std::random_device{}()};
std::uniform_real_distribution dist(0.0, 1.0);
double d = dist(gen);
  • 若需要闭区间 [a, b],可改为 dist(a, std::nextafter(b, INFINITY)),但通常没必要
  • 对精度敏感场景(如蒙特卡洛模拟),优先用 double 分布而非 float,避免舍入偏差累积
  • 不要用 static_cast(rand()) / RAND_MAX,它分辨率极低且分布不均
C++11 之后的随机数设施设计清晰、可组合、可测试,但引擎和分布必须配对使用——漏掉任一环节(比如只声明引擎不声明分布,或反向操作)都会编译失败或逻辑错误。最易忽略的是:分布对象本身不保存状态,真正维持随机序列的是引擎;因此,若需重现某次运行的随机行为,只需保存并复用引擎的当前状态(通过 gen.seed() 或序列化其内部数组),而不是分布。