前言:本文主要解决问题如下
rand 函数生成随机数进行语法、应用方法的介绍。在使用 rand 函数时出现每次运行结果都相同的情况时,使用 srand 函数初始化 rand使用 time 函数作为 srand 函数的参数(种子),从而实现 rand 函数生成数字的随机化(本质还是伪随机数)重点讨论:为何 srand 函数在循环中不可多次调用的情况以及其底层代码运行原理对随机数的生成问题进行实践应用——写一个猜数字游戏总结:揭秘 rand 函数 ,如何实现真正的随机数
一、rand、srand、time的语法与应用
1、 rand 函数 语法以及注意事项
对于rand 函数的具体用法,我们首先来看看C\C++官网rand - C++ Reference对于其的定义以及用法说明
我们会发现,rand 函数是一个没有参数,返回值是一个介于 0~RAND_MAX 的整数值,需注意:RAND_MAX的大小是依赖于编译器实现的,但是大部分编译器上是32767。
rand 函数的使用需要包含头文件:stdlib.h
如果再深入了解一下,我们就不难发现,其实 rand 生成的随机数是为随机的,伪随机数不是真正的随机数,是通过某种算法生成的随机数。真正的随机数是没法预测下一个值是多少的。而rand 函数是对一个叫“ 种子 ”的基准值进行运算后生成的随机数。而种子是由 srand 函数定义的。
那么,我们来看看VS2022上RAND_MAX的数值是多少:
不难发现,VS2022中 RAND_MAX 的数值为32767。
接下来,我们就测试一下 rand 函数 ,下例中多次调用,产生五个随机值。
第一次运行:
第二次运行:
我们可以发现,虽然依次运行中产生的5个数是相对随机的,但是下一次运行程序生成的结果和上次一模一样,这就说明有点问题了。上例所示每次运行程序产生的序列是一样的,那是因为 rand 函数生成随机数的默认种子是1.如果要生成不同的随机数,就要让种子变化(见下方srand 函数使用说明)。
为了验证上述示例 rand 函数默认种子是1,请见下例:
我们不难发现,通过 srand 函数将 rand 函数的种子改为1以后,运行结果尽然和上方例子运行结果一致。
2、srand 函数 语法以及注意事项
C语言中又提供了一个函数叫srand ,用来初始化随机数的生成器的。还是依照 rand 函数,我们下来看看官网的说明
程序在调用 rand 函数之前先调用 srand 函数,通过 srand 函数的参数 seed 来设置 rand 函数生成随机数的时候的种子,只要种子在变化,每次生成的随机数列也就变化起来了。
也就是说,给srand 的种子如果是随机的,rand就能生成随机数;在生成随机数的时候又需要生成一个随机数,这就矛盾了。所以,我们引入 time 函数来使 srand 函数的种子不同,从而使得rand 函数产生每次运行结果不同的随机数。下面我们来看看time 函数。
3、time 函数
在程序中我们一般是使用程序运行的时间作为种子的,因为时间是时刻都发生变化的。
在C语言中有一个函数叫 time ,就可以获得时间,time 函数官网解释如下:
time 函数会返回当前的日历时间,其实返回的是1970年1月1日0时0分0秒到现在程序运行时间之间的差值,单位是秒。返回的类型是time_t类型,time_t类型本质上是32位或者64位的整型类型。
time 函数的参数timer 如果是非NULL的指针的话,函数也会将这个返回的差值放在timer 指向的内存中带回去。
如果,timer 是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳。
time 函数的使用需要包含头文件:time.h
下面我们来看一下时间戳:
如果只是让 time 函数 返回时间戳,我们就可以这样写:
time(NULL);
那么我们就可以让生成随机数的代码写为:
#include
#include
#include
int main()
{
//使用time函数的返回值设置种子
//因为srand的参数是unsigneed int 类型,我们将time函数的返回值强制类型转换
srand((unsigned int)time(NULL));
printf("%d\n" , rand());
printf("%d\n" , rand());
printf("%d\n" , rand());
printf("%d\n" , rand());
printf("%d\n" , rand());
return 0;
}
第一次运行结果:
第二次运行结果;
我们就会发现两次运行结果不一样了。
二、设置随机数的范围
1、如果我们要生成0~99之间的随机数,方法如下:
rand() % 100;//余数的范围为0~99
2、如果要生成1~100之间的随机数,方法如下:
rand() % 100 + 1;//余数范围为0~99,0~99数字加1,范围是1~100
3、如果要生成100~200之间的随机数,方法如下:
100 + rand() % (200-100+1);//余数范围是0~100,加100后是100~200
4、如果要生成a~b之间的随机数,方法如下:
a + rand() % (b-a+1);
三、猜数字游戏
参考代码:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
void menu();
void game();
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//此处将time函数作为srand函数的参数,放在主函数中
do
{
menu();
printf("请选择:>");
scanf("%d" , &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("选择错误请重新选择\n");
break;
}
} while (input);
return 0;
}
void menu()
{
printf("****************\n");
printf("*** 1.play ***\n");
printf("*** 0.exit ***\n");
printf("****************\n");
}
void game()
{
int r = rand() % 100 + 1;
int guess = 0;
while (1)
{
printf("请输入数字:>\n");
scanf("%d" , &guess);
if (guess < r)
{
printf("猜小了\n");
}
else if (guess > r)
{
printf("猜大了\n");
}
else
{
printf("恭喜你,猜对了\n");
break;
}
}
}
四、srand函数不可多次调用的原因
上面猜数字游戏是将srand 函数写在了主函数之中,每次程序运行只执行一次,但是我们来想一想,可不可以放在void game();这个函数的中呢?放进去会怎样呢。
同样的原理,我们来写一个简单的程序验证一下。
4.1 将srand 函数放在循环中多次用:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
void game();
int main()
{
int input = 0;
printf("请选择数字1\n");
scanf("%d" , &input);
while(1==input)
{
game();
}
return 0;
}
void game()
{
int r = 0;
srand((unsigned int)time(NULL));
r = rand()%100 + 1;
printf("%d\n" , r);
}
srand 函数在循环中多次使用
看上方视频,我们会发现这个程序为一个无终止的循环,并且开始生成的数字都一样,每过一秒相同的数字就会发生改变,这种对于我们要求的生成随机书的诉求有点不太符合,所以为什么会出现这种情况呢?那把srand 函数放到主函数里只被调用一次会怎样呢?下面我们来验证一下。
4.2 将srand 函数放在主函数中只被调用一次:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
void game();
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
printf("请选择数字1\n");
scanf("%d", &input);
while (1 == input)
{
game();
}
return 0;
}
void game()
{
int r = 0;
r = rand() % 100 + 1;
printf("%d\n", r);
}
srand函数只执行一次
看上方视频,我们就会发现,每次生成的随机数都不一样,这不就满足了我们生成随机数的诉求了吗。为什么srand函数只执行一次与在一个循环中多次执行会有如此大的差距呢?(也就是为何srand 函数不可多次调用)所以其中的原因到底是什么呢?接下来我们探讨一下。
我们来仿写一个srand 与 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;
}
int getRandomValue()
{
srand(time(0));
return rand();
}
从上述我们可以看出,首先在 int getRandomValue() 函数中,我们先执行的是 srand 函数,将时间戳赋值给 next 变量,再执行rand 函数,进行下述随机算法生成,再将求出的 next 变量数值赋值给srand 函数的参数,依次作为循环。我们会发现在最开始srand 函数中时间戳这个参数给予了最开始的种子,后续即可以用随机算法为底层生成后续的srand 函数的参数以及rand 函数返回值(即 next 变量的数值)。
int myrand(void)
{
next = next * 1103515245 + 12345;
return((unsigned)(next / 65536) % 32768);
}
明白了,上一段所表述的底层逻辑,我们返回之前的问题:为何 srand 函数不可多次调用?这个问题中来。
我们来想象一下,在同一个时间戳(同一秒内)中,若多次调用srand 函数,其参数总是相同的值,所以依照我们上文所说的rand 函数的性质,rand 函数种子相同时每一次执行程序结果总是相同,所以也就解释了上文(4.1将srand 函数放在循环中多次用 )中,为何开始生成的数字相同并且每隔一秒数字会变换一下的原因。
其次,回答(4.2 将srand 函数放在主函数中只被调用一次)为何可以生成连续的完全不同的数,其原因就在于:当我们通过当前的时间戳这个参数生成了rand 函数的种子后,rand 函数会自行通过随机算法生成next 这个随机数,然后这个随机数就会作为下个srand 函数的参数来影响rand 函数的种子,从而生成下一个随机数值(next),依次循环,所以每个生成的数字就不会受秒数的影响了。
总结
本文探讨了C语言中随机数生成函数rand()的使用方法,重点分析了其生成的伪随机数特性及如何实现程序每次运行时产生不同随机数序列。通过srand()函数设置种子值,结合time()函数获取时间戳作为动态种子,解决了rand()默认种子导致的重复序列问题。文章详细解释了srand()应在程序初始化时调用一次而非循环中重复调用的原理,并通过仿写rand/srand底层代码揭示了其随机数生成机制。最后以猜数字游戏为例展示了实际应用,并提供了设置不同范围随机数的方法。
(如果对大家有帮助的话,麻烦留下点赞、收藏和关注,以此作为对作者的鼓励与认可哦)