随机数的种子问题
扫描二维码
随时随地手机看文章
开发过程中,经常会遇到生成随机数的需求,本文会详细介绍C++中生成随机数的方法以及一些注意事项。
随机数核心组件
C++11引入了库,这个库提供了随机数生成工具。
下面是C++中生成随机数的核心组件:
- 随机数引擎:生成伪随机数的算法。
- 随机数分布:将随机数引擎生成的数映射到特定范围或分布。
- 真随机数生成器:如std::random_device,用于生成高质量的随机数。
种子(Seed)
- 种子是随机数生成器的初始值。
- 相同的种子会生成相同的随机数序列。
- 常用种子来源:
- 当前时间:std::time(0)。
- 真随机数生成器:std::random_device。
伪随机数引擎
- std::mt19937:基于Mersenne Twister算法,周期长,随机性好。
- std::minstd_rand:线性同余生成器,速度快,但随机性较差。
- std::default_random_engine:默认引擎,实现可能因平台而异。
随机数分布
- 均匀分布:std::uniform_int_distribution、std::uniform_real_distribution。
- 正态分布:std::normal_distribution。
- 伯努利分布:std::bernoulli_distribution。
真随机数生成器
- std::random_device:依赖于硬件或操作系统提供的随机数源。
- 适用于生成种子或高安全性场景。
生成随机数的方法
std::rand
std::rand是C标准库中的随机数生成函数,C++中仍然可以使用,但它的随机性较差,且范围固定。
#include #include // for std::rand and std::srand #include // for std::time int main() { // 使用当前时间作为种子 std::srand(std::time(0)); // 生成一个随机数 int random_value = std::rand(); std::cout << "Random value: " << random_value << std::endl; // 生成一个范围在 [0, 99] 的随机数 int random_in_range = std::rand() % 100; std::cout << "Random value in [0, 99]: " << random_in_range << std::endl; return 0; }
缺点:
- std::rand生成的随机数质量较低。
- 范围限制需要手动调整(如%操作符)。
- 种子设置不够灵活。
std::random_device真随机数
std::random_device是一个真随机数生成器,通常用于生成高质量的随机数种子。
示例代码:
#include #include int main() { std::random_device rd; // 真随机数生成器 std::cout << "Random value: " << rd() << std::endl; return 0; }
注意:
- 在某些平台上,std::random_device可能会退化为伪随机数生成器。
- 大量每次生成随机数都使用std::random_device,性能较差。
- 通常用于生成种子,而不是直接用于生成大量随机数。
伪随机数引擎和分布
C++11引入了多种伪随机数引擎和分布,可以生成高质量的随机数。
常用随机数引擎:
- std::default_random_engine:默认的随机数引擎。
- std::mt19937:Mersenne Twister算法,高质量随机数引擎。
- std::minstd_rand:线性同余生成器。
常用随机数分布:
- std::uniform_int_distribution:均匀分布的整数。
- std::uniform_real_distribution:均匀分布的浮点数。
- std::normal_distribution:正态分布的浮点数。
- std::bernoulli_distribution:伯努利分布(布尔值)。
示例代码:
#include #include int main() { // 使用 Mersenne Twister 引擎 std::mt19937 rng(std::random_device{}()); // 均匀分布的整数 [1, 100] std::uniform_int_distribution<int> dist(1, 100); int random_int = dist(rng); std::cout << "Random integer in [1, 100]: " << random_int << std::endl; // 均匀分布的浮点数 [0.0, 1.0) std::uniform_real_distribution<double> dist_double(0.0, 1.0); double random_double = dist_double(rng); std::cout << "Random double in [0.0, 1.0): " << random_double << std::endl; // 正态分布的浮点数(均值 0.0,标准差 1.0) std::normal_distribution<double> dist_normal(0.0, 1.0); double random_normal = dist_normal(rng); std::cout << "Random normal value: " << random_normal << std::endl; return 0; }
优点:
- 随机数质量高。
- 分布灵活,支持多种分布类型。
生成随机字符串
可以使用随机数生成器生成随机字符串。
示例代码:
#include #include #include std::string generate_random_string(size_t length) { const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; std::mt19937 rng(std::random_device{}()); std::uniform_int_distribution<size_t> dist(0, charset.size() - 1); std::string result; for (size_t i = 0; i < length; ++i) { result += charset[dist(rng)]; } return result; } int main() { std::string random_str = generate_random_string(10); std::cout << "Random string: " << random_str << std::endl; return 0; }
注意事项
std::mt19937 e{std::random_device{}()};
直接这样写有什么问题?
如果你只需要生成一次随机数,这样写没问题。但如果你需要多次生成随机数,最好避免反复创建和销毁std::random_device对象,因为这会带来不必要的开销。
原因:
- 每次创建std::random_device对象时,都会初始化一个新的文件句柄(在类Unix系统上,它通常是对/dev/urandom的封装),这会带来文件系统操作的开销。(不同操作系统的实现不同)
- 每次从std::random_device读取随机数时,都会触发系统调用(如read系统调用),这可能会影响性能。
- 在Windows系统上,std::random_device通常是对微软加密API的封装,每次创建和销毁std::random_device对象时,都会初始化和销毁加密库的接口,这也会带来额外的开销。
因此,如果需要频繁生成随机数,这种写法可能会导致性能问题。当然,如果你的应用程序对性能要求不高,这种写法也是可以接受的。
常见的写法:
在许多示例、网站和文章中,通常会看到以下写法:
std::random_device rd; std::mt19937 e{rd()}; // 或者 std::default_random_engine e{rd()}; std::uniform_int_distribution<int> dist{1, 5};
这种写法的优点是:
- std::random_device只被创建一次,避免了反复初始化和销毁的开销。
- std::mt19937是一个伪随机数生成器,它的初始化只需要一个种子(由std::random_device提供),之后的所有随机数生成都在用户进程中完成,不会涉及系统调用。
std::mt19937vsstd::random_device
std::mt19937
- 伪随机数生成器,基于Mersenne Twister算法。
- 它是自包含的,完全在用户进程中运行,不会调用操作系统或其他外部资源。
- 代码非常稳定,跨平台性能一致,在任何平台上编译和运行它,都会得到相似的性能和结果。
std::random_device
- 是一个真随机数生成器,但其实现是不透明的,我们无法确切知道它的底层实现是什么,它会做什么,或者它的效率如何,不同系统实现不一定相同。
- 每次从std::random_device读取随机数时,可能会触发系统调用,因此其性能(每字节的周期数)可能远低于std::mt19937。
- 它通常用于生成种子,而不是直接生成大量随机数。
- 如果你为某些嵌入式设备或手机进行交叉编译,它的行为可能更加不可预测。
总结
- 对于简单的随机数需求,可以使用std::rand。
- 对于高质量的随机数,推荐使用std::mt19937引擎和分布,这是一个高效且可靠的伪随机数生成器,适合在用户进程中生成大量随机数。
- 对于高安全性场景,可以使用std::random_device生成真随机数。
- std::random_device的行为因平台而异,通常用于生成种子,而不是直接生成大量随机数。
- 如果需要频繁生成随机数,建议避免反复创建和销毁std::random_device对象,而是将其作为种子生成器,只初始化一次。





