这个问题涉及到关于推荐的srand()初始化方式的评论。第一条评论中提到,srand()
在应用程序中只应该被调用一次。为什么会这样呢?
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
gettimeofday
已经过时。取而代之的是 clock_gettime
,可能需要链接 -lrt
。然而,在许多平台上它可能还不可用。在 Linux 上,这是可以的。在 Mac 上,我认为它尚不可用。在 Windows 上,它可能永远不会可用。 - Shahbaz生成的随机数其实是伪随机数。首先设置一个种子,每次调用 rand
函数都会得到一个随机数,并修改内部状态,下一次调用 rand
时就会使用这个新状态来获取另一个随机数。因为使用某个公式生成这些“随机数”,所以在每次调用 rand
后设置相同的种子值会返回相同的数字。例如 srand(1234); rand();
将返回相同的值。仅用种子值初始化初始状态一次将生成足够多的随机数,因此当你不使用 srand
设置内部状态时,这些数字更有可能是随机的。
通常我们使用 time(NULL)
返回的秒数值来初始化种子值。假设 srand(time(NULL));
在一个循环中,那么循环可以在一秒内迭代多次,因此循环中的每次调用 rand
都将返回相同的“随机数”,这是不希望发生的。在程序启动时初始化一次将只设置一次种子,每次调用 rand
都会生成一个新的数字并修改内部状态,因此下一次调用 rand
将返回足够随机的数字。
例如来自 http://linux.die.net/man/3/rand 的这段代码:
static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
next = next * 1103515245 + 12345;
return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
next = seed;
}
内部状态 next
被声明为全局变量。每个 myrand
调用都会修改内部状态并更新它,然后返回一个随机数。因此,每次调用 myrand
都将有不同的 next
值,导致该方法每次调用都返回不同的数字。
看一下 mysrand
的实现;它只是将你传递给 next
的种子值设置为相同的。因此,如果你在调用 rand
之前每次设置相同的 next
值,它将返回相同的随机值,因为应用于它的公式是相同的,这是不希望发生的,因为该函数是用来生成随机数的。
但是根据你的需求,你可以将种子设置为某个固定值,以便每次运行时生成相同的“随机序列”,例如用于基准测试或其他情况。
man srand
的代码片段。范围从0到32767(假设RAND_MAX),远小于long
的范围。状态变量next
被设置为long
,因为内部乘法和加法将超出unsigned int
的范围。之后,结果在上述指定的范围内进行缩放或修改。虽然您可以使种子成为long
。 - phoxissrand()
不像为随机数生成器“掷骰子”,也不像洗牌。如果说有什么类似的话,它更像是只切一副牌。rand()
从一副大牌中发牌,每次调用时,它只是从牌堆顶部选择下一张牌,给您该值,并将该牌返回到牌堆底部。(是的,这意味着“随机”序列会在一段时间后重复。不过这是非常大的一副牌:通常为4,294,967,296张。) 此外,每次程序运行时,都会从游戏商店购买全新的牌组,而且每个全新的牌组总是具有相同的序列。 因此,除非您进行特殊操作,否则每次程序运行时,都将从rand()
获得完全相同的“随机”数字。rand
和srand
而言,答案是没有办法洗牌。srand
是做什么的呢?基于我一直在构建的类比,调用srand(n)
基本上就像是在说:“从顶部切掉n
张牌”。但是等等,还有一件事:实际上是开始使用另一个全新的牌组,并将其从顶部切掉n
张。n
调用srand(n)
、rand()
、srand(n)
、rand()
...,您不仅会得到一个不太随机的序列,而且实际上每次都会从rand()
中得到相同的数字。(可能不是您传递给srand
的相同数字,但是从rand
中反复得到相同的数字。)srand()
一次,并使用一个相当随机的n
,这样每次运行程序时都会从大牌堆的不同随机位置开始。对于rand()
,这确实是你能做到的最好的。
[附言:是的,我知道,在现实生活中,当你购买全新的扑克牌时,它通常是有序的,而不是随机的顺序。为了让这里的比喻起作用,我想象每个你从游戏商店购买的牌组都是看似随机的顺序,但确切相同的看似随机的顺序。有点像桥牌比赛中使用的完全相同洗牌的牌组。]
补充说明:如果一个伪随机数生成算法和一个给定的种子值相同,那么你总是会得到相同的序列。可以参考this question进行非常可爱的演示(该问题与C语言无关,但仍然适用)。
srand()
设置了随机生成器的初始状态,如果您在其间不干扰该状态,生成器产生的所有值都足够“随机”。例如,您可以这样做:int getRandomValue()
{
srand(time(0));
return rand();
}
如果您多次调用该函数,使得time()
在相邻的调用中返回相同的值,则生成的值也将相同——这是设计如此的。
srand函数用于初始化伪随机数生成器。如果你调用它超过一次,就会重新初始化该随机数生成器。而且,如果你使用相同的参数再次调用它,它将重新启动相同的序列。
为了证明这一点,如果你执行以下简单操作,你将看到相同的数字被打印100次:
#include <stdlib.h>
#include <stdio.h>
int main() {
for(int i = 0; i != 100; ++i) {
srand(0);
printf("%d\n", rand());
}
}
srand()
生成在同一秒运行的应用程序实例不同种子的解决方案如下所示。srand(time(NULL)-getpid());
此方法使您的种子非常接近随机,因为无法猜测线程启动的时间并且 pid 也会不同。
似乎每次运行 rand()
,它都会为下一个 rand()
设置一个新的种子。
如果 srand()
运行多次,问题在于如果两个运行发生在同一秒钟内(time(NULL)
不变),那么下一个 rand()
将与前一个 srand()
后的 rand()
相同。
srand()
进行初始化将导致rand()
返回相同的值。 - King Thrushbeard
srand()
的推荐方法? 使用time(0)
作为传递给srand()
的值是简单的,因此可能足够,因为srand()
通常也是简单的(请参见C标准中的示例实现,它假定RAND_MAX为32,767)。 链接的Q&A中的一个解决方案使用一个函数来混合3个值——来自clock()
、time(0)
和getpid()
的结果。使用混合函数是一个好主意。也可以使用CRC。 - Jonathan Leffler