我们有时候写代码,不可避免得可能会用到一些无序的数列,比如说写了一个排序算法,我这个时候可能就需要用到一组随机数列来做测试,再比如我们可能写了一个猜数字游戏,这个时候我们可能就需要电脑给我生成一个随机的数。C语言提供了一个专门用于生成随机数的函数rand()函数,现在跟着我一起去了解一下吧!
特别鸣谢:日本计算机教授·柴田汪洋老师,对本次作品制作的详细指导。
一、认识随机数函数rand:
rand | |
头文件 | #include<stdlib.h> |
格式 | int rand(void); |
功能 | 每次调用可以生成一个0~RAND_MAX的伪随机数。 |
返回值 | 返回所生成的伪随机数整数。 |
注释:rand函数所生成的随机数是基于某种规律生成的,由于这样的随机数可以被用户所预测,故称rand函数生成的是一组伪随机数。
通过表格所呈现的信息我们不难发现随机数函数rand的两个重要特点:
其一,rand函数生成的最小数为0,测试表明在所有编译环境其最小值均为0;
其二,rand生成的最大数为RAND_MAX,这个值的大小取决于我们所使用的编译环境,这个值的大小通常为32767且不小于32767(读者朋友也可以在自己的电脑上打印看一下自己的编译环境中这个值具体是多少),我们通过<stdlib.h>头文件将其定义为一个RAND_MAX的对象宏,即:
#define RAND_MAX 32767 //宏的一些使用我们将在下一篇目《#define:对象式宏和函数式宏的使用》详细介绍
二、通过rand函数生成的随机数是伪随机数:
为什么我们说rand函数生成得是个伪随机数呢,为什么是可以被预测的呢?我们可以通过这么一串代码来进行测试:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
int retry;
do
{
printf("生成了随机数%d\n", rand());
printf("再运行一次?...(0)否(1)是");
scanf("%d", &retry);
} while (retry);
return 0;
}
我想大部分的读者朋友第一次去运行都会得到这么一个结果(不同的编译环境结果略有差异,请以实际的编译结果为准):
现在读者朋友们关闭窗口,继续运行一下你的代码吧!对比一下这个结果和之前的这个结果存在什么差异......诶,你发现了没有,所生成的随机数基本没有什么变化!
三、设置用于生成随机数的种子srand函数及时间戳的使用,让随机数变得更加随机:
通过前面的测试我们已经清楚了,rand函数每次运行所生成的随机数的序列是非常固定的,有没有什么方法,能改变这个固定的随机数序列呢?有的,rand所生成的随机数的序列是根据一个初始种子进行确定的,如果有办法改变这个初始种子,我们就能够生成新的随机数的序列。因此srand函数也就应运而生了。
srand | |
头文件 | #include<stdlib.h> |
格式 | void srand(unsigned seed); |
功能 | 给后续调用的rand函数设置一个种子(seed),用于生成新的伪随机序列。默认种子的值为1,同一个种子的值传递给该函数,会生成相同的随机数序列。 |
返回值 | 无。 |
我们不如通过srand函数将其种子设置为50,运行一下看一看是否产生了新的伪随机序列。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
int retry;
srand(50);
do
{
printf("生成了随机数%d\n", rand());
printf("再运行一次?...(0)否(1)是");
scanf("%d", &retry);
} while (retry);
return 0;
}
运行结果表明:当种子值为1时,生成:41——>18467——>6334——>26500——>19169...序列
当种子值为50时,生成:201——>20851——>29710——>25954——>296...序列
因此,这样一来,我们如果让seed的值变化起来,就可以完美地生成出一系列的随机序列了!比如通过for循环让这个值变化起来,而这固然可行,但是你有没有想过,有些值其本身就是在变化的,比如说时间。而在计算机科学领域有一个更为常见的用于记录时间的工具——时间戳(时间戳是一串数字,用于表示从某个固定的时间点开始到现在所经历的时间的长度,单位为秒(s).)。
C语言所提供的time的函数可以获取到这么一个时间戳,它的相关信息如下所示:
time | |
头文件 | #include<time.h> |
格式 | time_t time(time_t *timer); |
功能 | 记录了从1970年1月1日0时0分0秒到当前时间所经过的时间长度,,单位为:秒(s)。 |
返回值 | 时间戳,当timer指针不为空(NULL)时,会将这个时间戳赋给timer所指向的对象,出现错误返回(time_t)-1。 |
注释:time_t类型又被称之为日历时间型,是能够进行加减乘除运算的算数型,具体等同于哪一个类型取决于编译环境,通常使用<time.h>头文件定义,下面是在Visual Studio 2022编译环境下的定义示例: typedef _int64 time_t; //_int64是有符号64位整数数据类型,typedef作用就相当于给_int64类型重命名。
从表格所呈现的信息我们不难发现time函数需要一个time_t指针类型的参数,能够返回一个time_t类型的数据,这里我们不妨使用空指针(NULL)作为参数传递给我们的time函数,即:time(NULL),这样我们直接去使用time(NULL)即可,这样一个随着时间不断变化的数据time(NULL)就产生了!
结合前面的srand函数的相关介绍,srand函数呢需要的是一个unsigned int类型的参数,于是我们使用一次强制类型转换,代码就变成了现在的这个样式:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int retry;
srand((unsigned int)time(NULL));
do
{
printf("生成了随机数%d\n", rand());
printf("再运行一次?...(0)否(1)是");
scanf("%d", &retry);
} while (retry);
return 0;
}
温馨提示:测试表明没有进行强制类型转换,编译器也不会给你报错亦或是警告,但是出于严谨性的考虑建议使用强制类型转换!
来看几组运算结果,是不是真正的随机起来了呢:
第一次:
第二次:
第三次:
运行的结果表明:我们通过rand函数生成的结果确实随机起来了呢!
四、控制随机数生成的范围:
rand函数生成随机数的范围是0~RAND_MAX,话虽如此,但是我们所需要的随机数不会每次都恰好给在这个范围以内,现在给大家总结和介绍一下控制随机数生成范围的公式:
公式 | 介绍说明 |
rand()%(a+1) | 大于等于0且小于等于a的随机数 |
b+rand()%(a+1) | 大于等于b且小于等于(b+a)的随机数 |
五、总结:
这篇博客主要给大家去介绍了rand函数以及和它相关的有关函数srand,time函数,和大家一起简单了解了一个新的数据类型time_t数据类型,这样一些库函数和技巧的使用,在我们日常学习和工作中都会有很大的帮助,比如设计下棋游戏时电脑的随机下棋,猜数字游戏时数据范围的控制等等。这篇博客也借鉴了部分的计算机相关的专业书籍,比如广受欢迎的《明解C语言》系列,在此表示衷心感谢。同时最后也期待未来和大家有更多技术上的交流和探讨以及更长久的进步,谢谢大家!