事情的开始是这样的,在大二的时候,写了几种排序算法,为了测试,就要为数组(或者容器)赋予一些随机初值,自然就用到了C/C++中的随机函数。

  当时为了调用简单,将随机数赋值的过程写到了一个单独的函数里,这样一来,为数组(或容器)赋值就可以简洁高效。

  但是,问题就是,按理来说,每次调用都该得到不同的随机数列,然而事实证明,“接连被赋值的数组”得到的随机数列却是一样的...

  简述一下当时发现问题的地方:调用srand函数生成随机数序列,以当前计算机时间作为随机数种子,即srand((unsinged)time(NULL)),这个随机数初始容器函数是这样写的:

// 来自大二时候的Modnar,嗯,码写得很可爱...
void Random(int list[], int n) {
srand((unsigned)time(NULL));
for(int i = ; i < n; i++) {
list[i] = rand() % + ;
}
}

  结果当一个函数(比如main中)调用这个初始化函数时,就会发生问题,比如这样调用:

int main(int argc, char *argv[]) {
int lst1[], lst2[];
Random(lst1, );
Random(lst2, );
// Process...
return ;
}

  此时得到的lst1和lst2就会是相同的两个序列。

  这里简单解释一下,因为time这个函数,传入的参数是一个指向需要被修改的对象的指针,返回值是计算机自起始时间起经过的时间(单位为秒),所以... 由于这个调用过程,时间用不到1s,自然两次调用时传入srand的数值是一样的,因此其生成的随机数序列就是一样的...

  当然,这是现在了解了其中的原理后作出的解释,当时可爱的Modnar自然也有自己的想法,他是这样解释的:

  嗯,为年轻而又热爱思考的Modnar点赞。当时的Modnar是利用这段码来验证想法的:

 #include <time.h>
#include <stdlib.h> #include <iostream>
#include <fstream> #define N 20 using namespace std; ofstream out; int list[N]; //最开始定义的random函数。
void Random1(int list[], int n) {
srand((unsigned)time(NULL));
for(int i = ; i < n; i++) {
list[i] = rand() % + ;
}
} //代码同Random1()一样,而后发现其输出的序列同Random1()相同。
void Random2(int list[], int n) {
srand((unsigned)time(NULL));
for(int i = ; i < n; i++) {
list[i] = rand() % + ;
}
} //最初试图通过先进行for空循环延时,来实现time返回值不同,后发现行不通(表明srand()功能实现的原理)。
void Random3(int list[], int n) {
srand((unsigned)time(NULL));
int k = rand() % + ;
for(int i = ; i < k; i++) { }
for(int i = ; i < n; i++) {
list[i] = rand() % + ;
}
} //通过前三个“随机”函数,发现了可以实现目标功能的随机函数。
void RandomFinal(int list[], int n) {
static int k = ; //通过静态变量来找到上次调用rand()后,rand()的位置
srand((unsigned)time(NULL)); //随机数种子...
for(int i = ; i < k * n; i++) {
rand(); //每次执行本函数,就先“跳过” k * n 个rand()
}
k++;
for(int i = ; i < n; i++) {
list[i] = rand() % + ; //将跳过 k * n 个rand()后的随机序列赋值给list
}
} void showlist(int list[], int n) {
for(int i = ; i < n; i++) {
out << list[i] << " ";
}
out << endl;
} int main() {
out.open("output.txt");
if(!out) {
cout << "Error. Exit the program." << endl;
exit();
} else cout << "Open Succeessfully." << endl;
int locallist[N];
out << "Local List:" << endl;
out << "First time:" << endl;
out << "Random1:" << endl;
Random1(locallist, N);
showlist(locallist, N);
out << "Random2:" << endl;
Random2(locallist, N);
showlist(locallist, N);
out << "Random3:" << endl;
Random3(locallist, N);
showlist(locallist, N); out << "Second time:" << endl;
out << "Random1:" << endl;
Random1(locallist, N);
showlist(locallist, N);
out << "Random2:" << endl;
Random2(locallist, N);
showlist(locallist, N);
out << "Random3:" << endl;
Random3(locallist, N);
showlist(locallist, N);
out << endl; out << "Global List:" << endl;
out << "First time:" << endl;
out << "Random1:" << endl;
Random1(list, N);
showlist(list, N);
out << "Random2:" << endl;
Random2(list, N);
showlist(list, N);
out << "Random3:" << endl;
Random3(list, N);
showlist(list, N); out << "Second time:" << endl;
out << "Random1:" << endl;
Random1(list, N);
showlist(list, N);
out << "Random2:" << endl;
Random2(list, N);
showlist(list, N);
out << "Random3:" << endl;
Random3(list, N);
showlist(list, N); out << endl << "FinalRandom:" << endl;
out << "Local List:" << endl;
out << "First time" << endl;
RandomFinal(locallist, N);
showlist(locallist, N);
out << "Second time:" << endl;
RandomFinal(locallist, N);
showlist(locallist, N);
out << "Third time:" << endl;
RandomFinal(locallist, N);
showlist(locallist, N); out << endl << "Global List:" << endl;
out << "First time:" << endl;
RandomFinal(list, N);
showlist(list, N);
out << "Second time:" << endl;
RandomFinal(list, N);
showlist(list, N);
out << "Third time:" << endl;
RandomFinal(list, N);
showlist(list, N); out.close();
return ;
}

Random.cpp

  其实我很懂Modnar的想法(23333,手动狗头),也就是说,其核心思想,就是srand会根据一个已有的巨大随机数序列来确定具体抽取哪段来作为函数返回(即所需的随机数序列)。

  而且,当时的Modnar给出的解决方法是正确的(多么令人惊喜)!通过用static来“储存这时的随机数序列状态”。

  如今,在略有了解std::C++11后,可以继续讨论一下这个问题(由于涉及到了一点儿很简单的自己写的东西,这里就不贴代码了):   

  在C++中,可以通过随机数引擎(相当于srand)来生成这个随机数序列,通过随机数分布(即图中的distribution)来进一步映射。

  需要注意的是distribution的参数是随机数引擎,因为一些分布可能不只需要一个数值...(先这么理解吧,这儿不是主角)

  下面是程序输出:

  重点就是,可以看到,通过定义成static类型的引擎、分布对象,可以使得生成的随机序列不同!

  (这里,不禁感叹当时Modnar的奇思妙想... 尽管这些东西翻翻书就能学到,哈哈哈哈)

  这部分,在《C++ Primer 5th》有所提及:“一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static的。否则,每次调用函数都会生成相同的序列。”(原书中文版第662页,英文版第748页)

  书中强调的是直接使用引擎对象的情况下(即不给引擎初始时传入一个随机数种子),当然,上面已经分析过,尽管传入一个随机数种子(比如(unsigned)time(nullptr)这个种子值),往往会因为调用时间间隔太短而无法生效。

  顺便说一下,在C++11中,对随机数的支持也趋渐完善,通过简单地调用随机数引擎以及不同类型的分布(例如正态分布等),也可以实现很多实用的数据生成功能,还是很值得一玩的。

  当然,偶尔胡思乱想、任意猜猜也是挺有意思的,日后想起来,真的很深刻...(手动调皮)

  @编辑于2019.3.7

C++中的随机数的更多相关文章

  1. 为什么说Java中的随机数都是伪随机数?

    什么是伪随机数?  1.伪随机数是看似随机实质是固定的周期性序列,也就是有规则的随机. 2.只要这个随机数是由确定算法生成的,那就是伪随机,只能通过不断算法优化,使你的随机数更接近随机.   (随机这 ...

  2. Linux中的随机数文件 /dev/random /dev/urandom

    Linux中的随机数可以从两个特殊的文件中产生,一个是/dev/urandom.另外一个是/dev/random.他们产生随机数的原理是利用当前系统的熵池来计算出固定一定数量的随机比特,然后将这些比特 ...

  3. Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom

    Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom 文中的 Random即:java.util.Random,ThreadLocalRandom 即: ...

  4. numpy中的随机数模块

    https://www.cnblogs.com/td15980891505/p/6198036.html numpy.random模块中提供啦大量的随机数相关的函数. 1 numpy中产生随机数的方法 ...

  5. Java课程课后作业190315之从文档中读取随机数并得到最大连续子数组

    从我上一篇随笔中,我们可以得到最大连续子数组. 按照要求,我们需要从TXT文档中读取随机数,那在此之前,我们需要在程序中写入随机数 import java.io.File; import java.i ...

  6. Java中产生随机数的两个方法

    Java中产生随机数的两个方法 一.利用random方法来生成Java随机数. 在Java语言中生成Java随机数相对来说比较简单,因为有一个现成的方法可以使用.在Math类中,Java语言提供了一个 ...

  7. 关于LUA中的随机数问题

    也许很多人会奇怪为什么使用LUA的时候,第一个随机数总是固定,而且常常是最小的那个值,下面我就简要的说明一下吧,说得不好,还请谅解.我现在使用的4.0版本的LUA,看的代码是5.0的,呵呵 LUA4. ...

  8. C语言中生产随机数 rand()函数

    参考资料:C语言中产生随机数 一:如果你只要产生随机数而不需要设定范围的话,你只要用rand()就可以了:rand()会返回一随机数值, 范围在0至RAND_MAX 间.RAND_MAX定义在stdl ...

  9. LoadRunner中的随机数

    LoadRunner中的随机数 Action() { int i; ]; srand(time(NULL)); i=rand()%; lr_save_datetime("%m%d%H%M%S ...

  10. [Linux] 如何在 Linux 中提取随机数

    如何在 Linux 中提取随机数 一.设备文件 /dev/random & /dev/urandom 字符特殊文件 /dev/random 和 /dev/urandom (存在于Linux 1 ...

随机推荐

  1. 线上BUG定位神器(阿尔萨斯)-Arthas2019-0801

    1.下载这个jar 2.运行这个jar 3.选取你需要定位的问题应用进程 然后各种trace -j xx.xxx.xx.className methodName top -n 3 这个后面要补充去看, ...

  2. asp.net 大文件上传配置

    <system.web> <httpRuntime requestValidationMode=" ></httpRuntime> <!--单位:K ...

  3. Spring Boot 文件上传简易教程

    上传文件是我们日常使用最为广泛的功能之一,比如App端上传头像.本章演示如何从客户端上传到 Spring Boot 开发的 Api中. 本项目源码 github 下载 1 新建 Spring Boot ...

  4. RTT学习之sensor设备

    Sensor设备的常用操作: 首先查找传感器设置获取设备句柄.rt_device_find 以轮询.FIFO.中断.任意一种方式打开传感器,中断和FIFO需要设置接收回调函数(释放一个信号量给接收线程 ...

  5. Java数组和方法

    1. 数组可以作为方法的参数 package cn.itcast.day05.demo04; /* 数组可以作为方法的参数. 当调用方法的时候,向方法的小括号进行传参,传递进去的其实是数组的地址值. ...

  6. 【JavaScript基础#2】

    " 目录 #. 函数 1. 定义 2. arguments 参数 3. 全局变量与局部变量 4. 语法分析 #. 内置对象和方法 1. 自定义对象 2. 类之继承 3. Date 4. JS ...

  7. C++文件写入,读出函数ofstream,ifstream的使用方法

    ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间. 1.插入器(<<)  向流输出数据.比如说系统有一个默认的标准输出流(cout),一般情况下 ...

  8. Net Core解决ZipFile解压中文出现乱码

    一.在main方法中添加 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 二.解压添加 //sourceArchiveFi ...

  9. SpringBoot RESTful API 架构风格实践

    如果你要问 Spring Boot 做什么最厉害,我想答案就在本章标题 RESTful API 简称 REST API . 本项目源码下载 1 RESTful API 概述 1.1 什么是 RESTf ...

  10. yp寒假训练一

    19年东北四省省赛 做了J G C 补了E H J签到题 G 题意: 给n个正方形的两个斜对角点坐标,问最小的移动可以重叠(移动上下左右一格) 思路: 一开始想的是中心pos移动,但是可能有小数,而且 ...