2023-09-18
编程
00
请注意,本文编写于 367 天前,最后修改于 178 天前,其中某些信息可能已经过时。

目录

rand() 和 srand()
缺陷
random 库
示例1:生成均匀分布的随机整数
示例2:生成正态分布的随机数
多线程的安全性
结论

在C++中,当我们需要生成一个随机数的时候,我们通常会使用rand()函数,但你知道使用rand()可能会遇到什么问题吗?在工程实践中生成随机数还有什么更好的方案吗?如果我们想要生成满足正态分布的随机数,该怎么做?让我们一起来了解一下吧!

rand() 和 srand()

int rand (void);

rand()是标准库 <cstdlib>中的一个函数,这个函数不接受任何参数,并“随机”返回一个在 0 和 RAND_MAX 之间的整数。RAND_MAX 是在标准库中预定义的一个常量,通常为32767。

如果想要获得指定范围里的随机数,则需要通过下面这种方法:

c++
v1 = rand() % 100; // v1 in the range 0 to 99 v2 = rand() % 100 + 1; // v2 in the range 1 to 100 v3 = rand() % 30 + 1985; // v3 in the range 1985-2014

这是一个简单的连续生成五个随机数的代码:

c++
#include <cstdlib> #include <iostream> int main() { // 生成随机数 for (int i = 0; i < 5; ++i) { std::cout << "Random Number: " << rand() << std::endl; } return 0; }

如果你多次运行这个程序,你会发现每次得到的随机数以及随机数的顺序都是一样的,如下图:

image.png

这是因为 rand() 实际上是一个伪随机数生成器,它基于一个种子值生成随机数序列,如果种子是固定的,那么生成的随机数序列也是固定的。这个种子值默认是1,所以每次得到的结果都是一样的。如果你想要每次运行程序时都得到不同的随机数,则需要在每次程序运行时都通过 srand() 显式设置不同的种子值:

c++
#include <cstdlib> // for rand() and srand() functions #include <iostream> #include <ctime> // for time() function int main() { srand(time(0)); // 设种子值为当前系统时间 int randomNumber = rand(); std::cout << "Generated random number: " << randomNumber << std::endl; return 0; }

在这个例子中,我们使用当前时间作为种子值,因此每次运行程序时,都会生成不同的随机数。

缺陷

尽管rand()和srand()函数能够满足一些基本的随机数生成需求,但是它们却存在着一些已知缺陷:

  1. 低质量的随机数:rand() 在生成随机数时使用的算法相对简单,导致生成的随机数质量不高。比如说最终的输出在全范围内可能并不满足均匀分布。
  2. 种子生成问题:虽然使用 srand(time(0)) 可以生成不同的随机数,但如果在一秒内多次调用,会生成相同的种子,从而生成相同的随机数。
  3. 线程安全问题:rand() 和 srand() 函数在多线程环境中是不安全的,因为它们共享一个全局的种子值,如果某个线程改变了种子值,也会同时改变其他的线程生成随机数的过程。

random 库

为了解决以上问题,C++11 引入了 <random> 库,它提供了多个不同算法的随机数生成器和随机数分布选项(如均匀分布、正态分布、伯努利分布等),可以满足更为复杂要求更高的随机数生成需求,同时也具备了多线程的安全性。

下面是一个使用<random>库的示例,该示例生成一个在1到6之间的均匀分布的随机整数,模拟掷骰子的情况:

示例1:生成均匀分布的随机整数

c++
#include <random> #include <iostream> int main() { // 创建一个随机种子 std::random_device rd; // 使用 Mersenne Twister 算法初始化一个随机数生成器 std::mt19937 gen(rd()); // 创建一个在 1 到 6 之间的整数的均匀分布 std::uniform_int_distribution<> dis(1, 6); // 使用分布对象生成一个随机整数并打印 std::cout << "Random dice roll: " << dis(gen) << std::endl; return 0; }

这里的 std::random_device 在操作系统和硬件支持的情况下可以生成真随机数,否则的话会退化成伪随机数生成器。

示例2:生成正态分布的随机数

c++
std::random_device rd; // 随机设备 std::mt19937 gen(rd()); // 初始化 Mersenne Twister 伪随机数生成器 std::normal_distribution<> d(5, 2); // 均值为5,标准差为2的正态分布 for(int n=0; n<10; ++n) std::cout << d(gen) << ' '; // 输出10个符合该正态分布的随机数 std::cout << '\n';

在这段中,我们创建了一个均值为 5,标准差为 2 的正态分布。然后生成了10个符合该分布的随机数。

运行示例:

image.png

可以看到每次运行的结果都是不一样的。

多线程的安全性

下面这段代码是在多线程程序中使用rand()生成随机数的例子:

c++
#include <iostream> #include <thread> #include <cstdlib> void generate_random_numbers(int id, int seed) { srand(seed); for (int i = 0; i < 5; ++i) { int random_number = rand(); std::cout << "Thread " << id << ": " << random_number << "\n"; } } int main() { std::thread t1(generate_random_numbers, 1, 123); std::thread t2(generate_random_numbers, 2, 456); t1.join(); t2.join(); return 0; }

在这个例子中,两个线程都在生成随机数,由于 srand() 改变的是全局的种子值,当一个线程调用的 srand() 时,另一个线程生成随机数的种子也会随之改变,导致输出的结果不符合预期。

相比之下,如果我们使用 <random> 库,每个线程可以有自己的随机数生成器,这样就可以避免这个问题,下面的代码展示了如何在多线程环境中使用 <random> 库:

c++
#include <iostream> #include <thread> #include <random> void generate_random_numbers(int id, int seed) { std::mt19937 engine(seed); std::uniform_int_distribution<int> dist(0, 100); for (int i = 0; i < 5; ++i) { int random_number = dist(engine); std::cout << "Thread " << id << ": " << random_number << "\n"; } } int main() { std::thread t1(generate_random_numbers, 1, 123); std::thread t2(generate_random_numbers, 2, 456); t1.join(); t2.join(); return 0; }

在这个例子中,每个线程都有自己的随机数引擎对象,所以它们的随机数生成行为不会互相干扰。

结论

虽然 rand() 和 srand() 函数在某些简单的情况下仍然是可用的,但是由于它们的缺陷和限制,建议在现代C++程序中使用<random>库来生成随机数。<random>库提供了强大且灵活的随机数生成和分布机制,能够满足各种各样的需求。

希望这篇博客能帮助你理解C++中的随机数生成,并了解如何使用<random>库来生成高质量的随机数。事实上,<random>库的功能极其丰富,你可以根据自己需要查阅更多的有关资料。如果你有任何问题或者想要讨论更多关于C++的话题,欢迎在评论区留言。

参考链接:

https://learn.microsoft.com/en-us/cpp/standard-library/random?view=msvc-170

本文作者:Rowlet

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!