C++抽象编程——接口(4)——随机接口的设计

说明接口设计原理的最简单的方法是进行简单的设计练习。 为此,我们就来介绍一个能产生随机数的接口 random.h接口的设计过程,这样可以编写看似随机选择的程序。能够模拟随机的行为是有必要掌握的,例如,你想编写一个涉及翻转硬币或滚动骰子的电脑游戏。但在更实际的情况下也是有用的。模拟这种随机事件的程序称为非确定性程序(nondeterministic programs
让计算机以随机方式运行涉及一定的复杂性。为了客户端程序员的利益,我们希望在接口之后隐藏这种复杂性。所以接下来我们就分三个方面去了解接口的产生:接口设计器,实现者和客户端

随机与伪随机数(Random versus pseudorandom numbers)

部分原因是因为早期的计算机主要用于数值应用,使用计算机产生随机性的想法通常表现为能够在特定范围内生成随机数。从理论的角度来看,如果我们没有办法预先在一组等可能出现的数组中确定将会产生哪一个具体的值,则数字是随机的。(From a theoreticalperspective, a number is random if there is no way to determine in advance what value it will have among a set of equally probable possibilities) 例如,滚动骰子会产生1到6之间的随机数。如果模具是公平的,则无法预测哪个数字将出现。 六个可能的值也是可能的。
虽然随机数的概念是很清楚了,但是在计算机内部代表却是一个困难的概念。因为计算机通过在存储器中遵循一系列指令进行操作,因此以确定性模式工作。如何通过遵循确定性的规则来产生不可预测的结果? 如果一个数字的产生是确定性过程的结果,那么任何用户都应该能够通过同一组规则,并预期计算机的计算结果。
然而电脑实际上使用确定性过程来产生我们所谓的随机数,这个策略是还是有效的,因为即使用户在理论上可以遵循相同的规则并预期计算机的响应,但是没有人会无聊到去这样做。在大多数实际应用中,数字是否是真正的随机无关紧要,重要的是数字看起来似乎是随机的。对于出现随机的数字,它们应该从
1. 统计学的角度来看,就像随机数一样(behave like random numbers from a statistical point of view
2. 用户很难提前预测产生的结果。(be sufficiently difficult to predict in advance that no user would bother.

计算机内的算法进程产生的“随机”数字称为伪随机数(pseudorandom numbers),以强调不涉及真正的随机活动的事实。

< cstdlib >出口的 RAND_MAX常数

< cstdlib >库导出一个称为rand的低级函数,用来产生伪随机数。rand的原型是:

int rand();

这表示rand不使用参数并返回一个整数。对rand的每次调用都会产生一个不同的值,这对客户来说很难预测,因此似乎是随机的。rand的结果保证为非负数,不大于常量RAND_MAX,这也定义在< cstdlib >中。因此,每次调用rand时,它返回0和RAND_MAX之间的不同整数,包括0和RAND_MAX
如果你想感受一下rand函数是怎么样工作的,我们就写一个小程序测试一下:

#include <iostream>
#include <cstdlib>
using namespace std;
const int N_TRIALS = 10; //设置产生随机数的个数 
int main(){
    cout << "这台计算机的RAND_MAX是:" << RAND_MAX << endl;
    cout << "第一次产生的 " << N_TRIALS << " 个调用rand()函数" << endl;
    for (int i = 0; i < N_TRIALS; i++) {
        cout << rand() << endl;
    }
    return 0; 
}

结果如下:

既然C++库包含一个用于生成伪随机数的函数,那么我们为什么还会开发一个新的接口来支持这个过程?
答案是,rand函数本身不返回客户端可能直接使用的值。一方面,RAND_MAX的值取决于硬件和软件环境。在大多数系统上,它被定义为最大的正整数,通常为2,147,483,647。但在不同系统上可能具有不同的值(比如我这台电脑就是32767)。此外,即使我们可以依赖具有该特定值的RAND_MAX,只需要几个(如果有的话)应用程序,需要的是0到2,147,483,647之间的随机数。作为一个客户,你更有可能想要一些其他范围的值,通常要小得多。例如,如果我们正在尝试模拟翻转硬币,您需要一个只有两个结果的功能:正面和反面。同样地,如果你想表示滚动一个骰子,则需要产生1到6之间的随机整数。如果你试图模拟物理世界中的实验,你通常需要在连续的范围内产生随机值,其中结果需要表示为 double 而不是int。如果你可以设计出更适合这些客户端需求的接口,则该界面将会更加灵活,易于使用。
设计更高级别界面的另一个原因是,使用< cstdlib >的低级功能会引入几个复杂性,正如我们看到的,当将注意力转移到实现时,客户端将很乐意忽略它们。作为接口设计师的一部分工作是尽可能地避免客户端的复杂性(至少少一点for循环吧)。定义更高级别的random.h接口使得这样做成为可能,因为所有的复杂性都可以在实现中进行本地化。

选择一系列合适的函数

作为接口设计师,你的主要挑战之一是选择要导出的功能。虽然接口的设计比科学更具有艺术性,但你可以应用一些一般原则,包括我们之前提到过的简洁型和充足性概述的内容。 特别地,我们通过random.h导出的功能应该很简单,并且应该尽可能隐藏基本的复杂性。他们还应提供必要的功能来满足广泛客户的需求,这意味着我们需要了解客户可能需要的操作。了解这些需求部分取决于您自己的经验,但往往需要与潜在客户进行互动,以更好地了解他们的需求。
假设我们的接口需要下列这些的功能:

  • 在指定范围内选择随机整数(The ability to select a random integer in a specified range)。例如,如果您想要模拟滚动骰子的过程,则需要选择1到6之间的随机整数。
  • 在指定范围内选择随机实数(The ability to choose a random real number in a specified range)。 如果要将对象放置在空间中的随机点,则需要在适用于应用程序的任何限制内选择随机x和y坐标。
  • 以特定概率模拟随机事件(The ability to simulate a random event with a specific probability)。如果你想模拟一个硬币,你要生成一个值,其产生的概率为0.5,对应于50%的时间都是生成这个值。

将这些概念操作转换为一组函数原型是一项比较简单的任务。random.h接口中的前三个函数randomInteger,randomReal和randomChance对应于这三个操作。 完整的接口也将导出一个名为setRandomSeed的函数,这将在后面的博客介绍。现在我们先写个接口文件:

#ifndef _random_h
#define _random_h

/*
* 函数: randomInteger
* 用法: int n = randomInteger(low, high);
* ----------------------------------------
* 返回一个范围为 low到high之间的一个整数.
*/
int randomInteger(int low, int high);
/*
* 函数: randomReal 
* 用法: double d = randomReal(low, high);
* ----------------------------------------
* 返回一个范围为 low到high之间的一个实数(不包括high).
*/
double randomReal(double low, double high);
/*
* 函数: randomChance(p); 
* 用法: if (randomChance(p));
* ----------------------------------------
* 以p表示返回true的概率。 参数p必须是0和1(始终)之间的浮点数。 
*例如,调用randomChance(0.30)返回true的概率为30%
*/
bool randomChance(double p);
/*
* 函数: setRandomSeed
* 用法: setRandomSeed(seed);
* ---------------------------
* 将内部随机数种子设置为指定值。 
* 你可以使用此功能设置伪随机序列的特定起始点,
* 确保程序行为在调试阶段可重复.
*/
void setRandomSeed(int seed);

#endif 

这是个标准的接口模板,结构为:

#ifndef XXX(_接口名_h)
#define XXX

/*注释
*函数:
*用法:
*--------------
*解释:
*/
函数原型
......

#endif

这个时候我们就可以去模拟一下投掷骰子的随机产生数:

int randomInteger(1, 6);

函数randomReal在概念上类似于randomInteger。它需要两个浮点值lower和high,并返回浮点值r,条件是lower ≤ r < high。 例如,调用randomReal(0,1)返回一个可以小到0但是总是严格小于1的随机数(即 [0, 1))。在数学中,可以等于一个端点而不是另一个端点的实数范围称为开区间(half-open interval)。

函数randomChance用于模拟以固定概率发生的随机事件。为了与统计的惯例一致,概率表示为0和1之间的数字,其中0表示事件永远不会发生,1表示它始终发生。调用randomChance(p)以概率p返回true。 因此,调用randomChance(0.75)表示有75%的概率返回true。
这个时候我们就可以模拟投硬币的事件了,下面的函数以各为一半的概率返回正面或者反面:

string flipCoin() {
    if (randomChance(0.50)) {
        return "heads";
    } else {
        return "tails";
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值