在C++中,当我们需要生成一个随机数的时候,我们通常会使用rand()函数,但你知道使用rand()可能会遇到什么问题吗?在工程实践中生成随机数还有什么更好的方案吗?如果我们想要生成满足正态分布的随机数,该怎么做?让我们一起来了解一下吧!
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;
}
如果你多次运行这个程序,你会发现每次得到的随机数以及随机数的顺序都是一样的,如下图:
这是因为 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()函数能够满足一些基本的随机数生成需求,但是它们却存在着一些已知缺陷:
为了解决以上问题,C++11 引入了 <random> 库,它提供了多个不同算法的随机数生成器和随机数分布选项(如均匀分布、正态分布、伯努利分布等),可以满足更为复杂要求更高的随机数生成需求,同时也具备了多线程的安全性。
下面是一个使用<random>
库的示例,该示例生成一个在1到6之间的均匀分布的随机整数,模拟掷骰子的情况:
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 在操作系统和硬件支持的情况下可以生成真随机数,否则的话会退化成伪随机数生成器。
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个符合该分布的随机数。
运行示例:
可以看到每次运行的结果都是不一样的。
下面这段代码是在多线程程序中使用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 许可协议。转载请注明出处!