当前课程知识点:基于Linux的C++ > 第五讲 程序组织与开发方法 > 5.3 随机数库(一) > LinuxCPP0503
现在就来考虑
怎么设计一个自己的随机数库
这里面就涉及到几个主题
第一个怎么来生成随机数
第二个库的基本的设计原则
应该遵照什么样的方案
来进行库的设计
第三个设计随机数库的接口
第四个 给出它的编码
就是随机数库的实现
最后一个步骤要对随机数库进行测试
整个库的设计就是按照基本的原则
定义好它的接口 给出它的实现
最后完成测试
首先来看随机数的生成
要求同学们写一个程序调用rand函数
来生成五个随机数
可以看到代码是这个样子
输出程序的提示信息之后
写了一个for循环
调用rand函数来生成一系列的随机数
rand函数特别的简单
一个参数都没有
生成的随机整数到底是哪一个呢
我们是不知道的
但是确确实实地知道
生成的这个随机数在什么样的范围里
就在0、RAND_MAX 在这个区间里
包括0和RAND_MAX
RAND_MAX这个值到底有多大
不同的操作系统
对这个值的定义是不一样的
同学们要根据你的操作系统来去看
RAND_MAX定义的最大的随机整数
到底是多少
rand调用一次
就在0、RAND_MAX的闭区间里边
生成随机整数
像这个例子一运行 生成五个随机数
看上去好像它们之间没有什么关系
再运行一遍
看出什么来没有 只要运行两次
一下就能够发现一个最重要的地方
就是虽说生成的这五个随机数
看上去就像一点关系都没有的样子
但是几次生成的随机序列本身
是一模一样的
也就是说 这个序列压根就不随机
问题是出在什么地方呢
就是操作系统
替你生成这个随机数的时候
它不是真的随机数
这是标准库替我们提供的
一个随机数发生器
它哪能真的随机呢
它的基本工作原理
就是使用一个特殊的值
作为随机数发生器的初始值
通过一系列的复杂的运算生成下一个数
然后使用下一个数作为新的初始值
再生成下下一个数
它是按照这样的方式
不断地去滚动地生成下一个随机数的
因为它内部计算做得很巧妙
看上去就像这些数之间
是完全无关的一样
但事实上只要你给了它一个
固定的初始值
那么你生成的那个随机序列
就一定是一样的
那怎么办呢
处理这个问题就需要
做一次额外的调用 就是srand
srand函数需要接受一个参数
返回值呢 没有
前面那个“s”是种子的意思
就是说调用一次srand
将会设定随机数发生器的种子
决定随机数发生器初始值是几
如果程序运行的时候
每次初始的x0都不一样
那么随机数发生器就会根据不同的x0
来替你生成随机数序列
那么怎么样才能让这个srand函数
在运行的时候
两次传递进去的参数是不一样的呢
找到两次运行的时候
它们之间的差异在什么地方
很容易就发现两次运行的时候
时间是不一样的
我们的时间是单向流逝的
那么在这种情况下边
我用当前时间 直接转换成整数
作为srand这个函数的种子传进去
程序第一次执行
去取它这次执行的那个时间
作为种子传给srand
程序第二次再执行
它就取第二次执行的时间
作为种子传给srand
你看两次传的参数是不一样的吧
传的是不同的x0
那两次运行得到的那个随机数
一定是不一样的
第一个随机数都不一样了
那后边的随机数肯定就都不一样了
整个序列就全变了
怎么得到当前的时间呢
在Linux操作系统下边
C标准库里提供一个函数
这个函数叫time
使用time来获得CPU的时间
time带一个参数
这个参数不用管 现在传0
事实上 那个参数不是要传进去的参数
其实讲完指针同学们就了解了
它是一个指向结构体的指针
它就是我带回来的那个时间
分成年月日 它是给你分解开的
time函数的返回值 事实上是一个整数
time_t的格式
就把它直接转换成整数 传给srand
但是同学们在调用srand函数的时候
需要特别注意
这个函数在你的程序运行的过程中
只能调用一次
生成一个随机数调用一次
那就不成了
因为像这样一个程序
它运行的时间是非常非常短的
生成了一个随机数
然后你再调用一次srand
再做一个种子 那时间都没变
每次都设置同样的种子
那生成的随机数
可不就是一模一样的吗
所以说特别记住这一条
srand只能调用一次
而且必须是在你生成
第一个随机数之前调用
此后再也不需要调用srand
就直接调用rand函数 生成随机数
接下来我们来看怎么设计随机数库
我把它总结出来
称之为接口设计的四项基本原则
一个用途一致
这个接口里边要提供一系列的函数
这些函数理论上来讲
应该是属于同一类问题的
用途不一致 别人就很难用
第二个操作要简单
在你给出设计方案的时候
要特别的注意这个库的操作
应该足够的简单 让别人很容易用
能够最大限度隐藏库的实现细节
第三个是功能充足
你设计了一个库
要提供足够的功能给别人用
如果功能本身就不足
那这个库的使用
实际上是非常非常受限的
第四个 性能稳定
库肯定要经过严格的测试
然后才能够知道
这样的一个库有没有存在
显而易见的缺陷
至少要保证库的实现
不存在显而易见的缺陷才行
这就是接口设计的四项基本原则
那么现在就来看
随机数库这个接口应该设计一些
什么样的函数
我们就想 定义这样的随机数库
提供什么样的功能呢 本来啊
C标准库里边提供rand函数
能够生成0、RAND_MAX
这样一个区间里的随机整数的随机数
可能不在0、RAND_MAX
这样一个区间里
比如说 在1、2、3、4、5、6
这6个数里边随便生成一个
模拟掷骰子 还比如讲
我想生成1到52之间的某一个数
模拟扑克牌
去掉大小王剩下就是52张牌
这个就叫随机数库
设计随机数库的时候要处理这个问题
就是说能够随机的生成
在指定的范围内的随机数
而不是固定的[0, RAND_MAX]
第二个我还要能够生成
指定范围内的随机的小数
尤其是0、1之间的随机的小数
它可以用来描述很多问题
比如说涉及到概率的
都可以用它来进行描述
这就是两个最主要的功能
随机数库就应该完成
能够生成指定范围内的随机整数
和指定范围内的随机实数
还有一个 需要对它进行随机化
就是设定随机数发生器的种子
有了这个概念之后
就可以定义随机数库的接口
这里面就提供了三个函数的原型
一个随机化Randomize
一个生成一个指定范围内的随机整数
一个生成指定范围内的随机实数
就三条函数原型
第一个没参数 没返回值
第二个双参数 双整数参数 返回整数
第三个双浮点数参数 返回浮点数
就这个格式 非常非常简单
接下来看怎么实现它
这是第一个 要随机化 调用srand
用time取当前的时间
把返回值转换成整数然后传给srand
就用它做种子
time这个函数这里面传的是NULL
这是预定义的宏
意思就是0
这就是随机数库srand这个函数的调用
用它来实现Randomize
接下来是两个函数的实现
生成一个随机的整数
和一个随机的实数
在实现这两个函数的时候
同学们需要特别注意
要保证这两个数能够平均地映射到
这个新的区间里面去
原来生成的数是0、RAND_MAX之间的
现在要把生成的随机数映射到
指定的low、high这样的区间里
这个区间的数可能 比较少 比如1到6
保证那些数据能够平均地映射成
1到6这六个数才成
如果是一个整数的话
那么如果不做特殊的运算
它是没有办法保证平均映射的
这里面还需要注意的地方
就是调用了exit函数退出
看到传的退出码
退出exit(1) 这个是exit(2)
传不同的退出码是可以的
接下来要对随机数库进行测试
在这里面的测试
不仅仅要测试随机数库里面
提供的每一个函数
单独要进行测试 还要联合测试
什么意思 就是说这个随机数库里面
提供三个函数嘛
每一个函数都需要测试
这样保证每一个函数
单独运行的时候没有问题的
第二个要保证这些函数
混合在一块运行的时候也没有问题
比如说随机化一次调用
然后随机生成几个整数
紧跟着又随机生成几个浮点数
再生成几个整数
然后我来看它的情况对不对
同学们在使用库的时候
实际上需要注意
先生成一个随机整数
紧跟着生成一个随机实数
又生成一个随机整数
又生成一个随机实数
交替着生成这些随机数有没有关系呢
原始生成的随机数就是0、RAND_MAX之间的
随机整数
然后映射成实数的 映射成小整数的
生成随机整数 随机实数
随机整数 随机实数 没关系
一定要记得 在生成随机数之前
你一定要调用Randomize进行随机化
库的使用者就需要知道这一条
对于库的设计者来讲方便了
三个函数写完就行了
对库的使用者来讲这就不太方便
在生成第一个随机实数
或者随机整数之前
一定要调用Randomize
如果忘了 就用错了
库的设计者怎么处理这个问题
我把这个问题留到课后
怎么重新设计随机数库
让用户不需要调用Randomize
就能够正确的生成随机序列呢
同学们课后好好地思考一下
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口