Random Numbers On C Language

面向小白的 C 语言随机数详解

// 生成一系列0~9之间的随机整数

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand( time(NULL) );
    int ret;
    for (int i = 0; i < 20; i++) {
    	ret = rand() % 10; // ret是一个0~9之间的随机整数
    	printf("%d ", ret);
    }

    return 0;
}

输出结果

7 6 6 1 6 9 2 7 1 8 2 0 5 9 2 5 4 0 0 0

这是啥啊?看不懂啊。

不要着急,我来慢慢讲

首先是一个最简单的随机数:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int ret = rand();
    printf("%d", ret);

    return 0;
}

输出结果

41

这里用到了rand()这个函数,使用这个函数需要引入stdlib.h这个头文件,函数返回一个随机数,生成的随机数都是整数,所以我们把它交给一个int的变量。然后使用printf函数输出即可看到结果。当然,由于我们使用了printf函数,我们还需要引入stdio.h 这个头文件。

但是我们如果多次运行,发现结果始终是 41,这是怎么回事呢?

这里要了解 C 语言随机数的生成机制:

在 C 语言中,rand()函数可以用来产生随机数,但是这不是真真意义上的随机数,是一个伪随机数,是根据一个数,我们可以称它为种子,为基准以某个递推公式推算出来的一系数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是定了的,除非你破坏了系统,为了改变这个种子的值,C 提供了 srand()函数,它的原形是 void srand( int a)。

这又是啥啊?看不懂啊。

没关系,继续向下看

看这个程序

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int ret;
    for (int i = 0; i < 200; i++) {
        ret = rand();
        printf("%d ", ret);
    }

    return 0;
}

输出结果

41 18467 6334 26500 19169 15724 11478 29358 26962 24464 5705 28145 23281 16827 9961 491 2995 11942 4827 5436 32391 14604 3902 153 292 12382 17421 18716 19718 19895 5447 21726 14771 11538 1869 19912 25667 26299 17035 9894 28703 23811 31322 30333 17673 4664 15141 7711 28253 6868 25547 27644 32662 32757 20037 12859 8723 9741 27529 778 12316 3035 22190 1842 288 30106 9040 8942 19264 22648 27446 23805 15890 6729 24370 15350 15006 31101 24393 3548 19629 12623 24084 19954 18756 11840 4966 7376 13931 26308 16944 32439 24626 11323 5537 21538 16118 2082 22929 16541 4833 31115 4639 29658 22704 9930 13977 2306 31673 22386 5021 28745 26924 19072 6270 5829 26777 15573 5097 16512 23986 13290 9161 18636 22355 24767 23655 15574 4031 12052 27350 1150 16941 21724 13966 3430 31107 30191 18007 11337 15457 12287 27753 10383 14945 8909 32209 9758 24221 18588 6422 24946 27506 13030 16413 29168 900 32591 18762 1655 17410 6359 27624 20537 21548 6483 27595 4041 3602 24350 10291 30836 9374 11020 4596 24021 27348 23199 19668 24484 8281 4734 53 1999 26418 27938 6900 3788 18127 467 3728 14893 24648 22483 17807 2421 14310 6617 22813 9514

这一次我们不是输出一次,而是输出 200 次,这时候我们发现,得到的结果有大有小,几乎都不同。rand函数的返回值确实是随机数,但是和上面一样,我们多次运行得到的还是一样的结果。

为什么一样?问题出在哪了呢?

随机数种子

如果单纯从结果来看,我们得到的这些数据确实是随机的(符合正态分布),也就是说,系统会使用系统内部预定义的随机数种子来进行某个复杂的计算得到一系列数,这些数符合正态分布,也就实现了随机数,由于算法是一致的,只要不去破坏系统,系统给定的种子不会改变,那么我们得到的结果就是一样的。

如果你不想考虑太多的话,我们可以直接使用rand()函数生成随机数,比如这样:

// 生成一系列0~9之间的随机整数

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int ret;
    for (int i = 0; i < 20; i++) {
    	ret = rand() % 10; // ret是一个0~9之间的随机整数
    	printf("%d ", ret);
    }

    return 0;
}

输出结果

1 7 4 0 9 4 8 8 2 4 5 5 1 7 1 1 5 2 7 6

这样确实可以实现随机数的功能,唯一的缺点就是得到的结果在每次运行程序都是是一致的。

那么如何改变种子呢?

使用srand()函数

在不破坏系统的情况下,如果我们想要设置自己的种子的话,可以使用srand()函数。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    srand( 20191218 ); // 在这里设置种子,你可以填写任何你喜欢的数字
    int ret;
    for (int i = 0; i < 20; i++) {
    	ret = rand() % 10; // ret是一个0~9之间的随机整数
    	printf("%d ", ret);
    }

    return 0;
}

输出结果

4 6 1 6 2 3 6 8 2 3 0 6 4 4 7 0 8 7 4 4

我们得到了和上面不一样的结果,但是,如果我们继续运行的话,结果依然是不变的。

这时你可能会气急败坏:

从一个圈里跳到另一个圈里?有什么用啊?还不是一样的?难道要我每次运行之前都去手动更改随机数种子吗?

不要生气嘛,至少你学会了如何改变随机数的种子。

我不想要这样的结果啊,我想要每次运行都能得到不同的结果。

不要着急,这里有完美的解决方案

首先我们要先认识一下time()函数

#include <stdio.h>
#include <time.h>

int main()
{
    printf("%d", time(NULL));

    return 0;
}

输出结果

1576683441

调用time()后,我们得到了一个非常大的数字。

这是什么啊?

当前日历时间

函数原型: time_t time(time_t *timer)

参数说明: timer=NULL 时得到当前日历时间(从 1970-01-01 00:00:00 到现在的秒数),timer=时间数值时,用于设置日历时间,time_t 是一个 unsigned long 类型。如果 timer 不为空,则返回值也存储在变量 timer 中。

函数功能: 得到当前日历时间或者设置日历时间

函数返回: 当前日历时间

也就是说,调用time(NULL)会得到当前时间,回到之前的问题,每次运行程序的时间一般是不同的,如果我们使用time(NULL)作为种子的话,每次运行程序计算机都会生成一个不同的数字给我们,那么我们就可以得到不同的随机数了。

原来如此,但是如果我想得到固定范围内的随机数改怎么做呢?

取余

我们得到的数都是比较大的整数,如果我们需要特定范围内的数,直接进行取余操作就可以了。

例如:

rand() % 100         // 0~99
rand() % 20          // 0~19
rand() % 40 + 1      // 1~40
rand() % 41 - 10     // -10~30

其他依次类推。

好了,我学会了!

不要急,还有呢。

到这里基本的操作应该都结束了,但是如果你觉得这样就结束的话,那真是:

Too young too simple.

(狗头保命)

为了便于使用,当然是要封装成函数来调用啊!

int randNum(int start, int end)
{
    end++;
    int ret;
    int det;
    det = end - start;
    ret = rand() % (det) + start;
    return ret;
}

这样用起来是不是舒服很多了呢?

好了,本教程到此结束。