当前位置:嗨网首页>书籍在线阅读

15-随机数函数和静态变量

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

12.2 随机数函数和静态变量

学习了不同存储类别的概念后,我们来看几个相关的程序。首先,来看一个使用内部链接的静态变量的函数:随机数函数。ANSI C库提供了 rand() 函数生成随机数。生成随机数有多种算法,ANSI C允许C实现针对特定机器使用最佳算法。然而,ANSI C标准还提供了一个可移植的标准算法,在不同系统中生成相同的随机数。实际上, rand() 是“伪随机数生成器”,意思是可预测生成数字的实际序列。但是,数字在其取值范围内均匀分布。

为了看清楚程序内部的情况,我们使用可移植的ANSI版本,而不是编译器内置的 rand() 函数。可移植版本的方案开始于一个“种子”数字。该函数使用该种子生成新的数,这个新数又成为新的种子。然后,新种子可用于生成更新的种子,以此类推。该方案要行之有效,随机数函数必须记录它上一次被调用时所使用的种子。这里需要一个静态变量。程序清单12.7演示了版本0(稍后给出版本1)。

程序清单12.7  rand0.c 函数文件

/* rand0.c --生成随机数*/
/* 使用 ANSI C 可移植算法 */
static unsigned long int next = 1;  /* 种子  */
unsigned int rand0(void)
{
     /* 生成伪随机数的魔术公式 */
     next = next * 1103515245 + 12345;
     return (unsigned int) (next / 65536) % 32768;
}

在程序清单12.7中,静态变量 next 的初始值是 1 ,其值在每次调用 rand0() 函数时都会被修改(通过魔术公式)。该函数是用于返回一个0~32767之间的值。注意, next 是具有内部链接的静态变量(并非无链接)。这是为了方便稍后扩展本例,供同一个文件中的其他函数共享。

程序清单12.8是测试 rand0() 函数的一个简单的驱动程序。

程序清单12.8  r_drive0.c 驱动程序

/* r_drive0.c -- 测试 rand0()函数 */
/* 与 rand0.c 一起编译*/
#include <stdio.h>
extern unsigned int rand0(void);
int main(void)
{
     int count;
     for (count = 0; count < 5; count++)
          printf("%d\n", rand0());
     return 0;
}

该程序也需要多文件编译。程序清单12.7和程序清单12.8分别使用一个文件。程序清单12.8中的 extern 关键字提醒读者 rand0() 被定义在其他文件中,在这个文件中不要求写出该函数定义。输出如下:

16838
5758
10113
17515
31051

程序输出的数字看上去是随机的,再次运行程序后,输出如下:

16838
5758
10113
17515
31051

看来,这两次的输出完全相同,这体现了“伪随机”的一个方面。每次主程序运行,都开始于相同的种子 1 。可以引入另一个函数 srand1() 重置种子来解决这个问题。关键是要让 next 成为只供 rand1()srand1() 访问的内部链接静态变量( srand1() 相当于C库中的 srand() 函数)。把 srand1() 加入 rand1() 所在的文件中。程序清单12.9给出了修改后的文件。

程序清单12.9  s_and_r.c 文件程序

/* s_and_r.c -- 包含 rand1() 和 srand1() 的文件    */
/*                使用 ANSI C 可移植算法      */
static unsigned long int next = 1;  /* 种子 */
int rand1(void)
{
     /*生成伪随机数的魔术公式*/
     next = next * 1103515245 + 12345;
     return (unsigned int) (next / 65536) % 32768;
}
void srand1(unsigned int seed)
{
     next = seed;
}

注意, next 是具有内部链接的文件作用域静态变量。这意味着 rand1()srand1() 都可以使用它,但是其他文件中的函数无法访问它。使用程序清单12.10的驱动程序测试这两个函数。

程序清单12.10  r_drive1.c 驱动程序

/* r_drive1.c -- 测试 rand1() 和 srand1() */
/* 与 s_and_r.c 一起编译 */
#include <stdio.h>
#include <stdlib.h>
extern void srand1(unsigned int x);
extern int rand1(void);
int main(void)
{
     int count;
     unsigned seed;
     printf("Please enter your choice for seed.\n");
     while (scanf("%u", &seed) == 1)
     {
          srand1(seed);    /* 重置种子 */
          for (count = 0; count < 5; count++)
               printf("%d\n", rand1());
          printf("Please enter next seed (q to quit):\n");
     }
     printf("Done\n");
     return 0;
}

编译两个文件,运行该程序后,其输出如下:

Please enter your choice for seed.
1
16838
5758
10113
17515
31051
Please enter next seed (q to quit):
513
20067
23475
8955
20841
15324
Please enter next seed (q to quit):
q
Done

设置 seed 的值为 1 ,输出的结果与前面程序相同。但是设置 seed 的值为 513 后就得到了新的结果。

注意 自动重置种子 如果C实现允许访问一些可变的量(如,时钟系统),可以用这些值(可能会被截断)初始化种子值。例如,ANSI C有一个 time() 函数返回系统时间。虽然时间单元因系统而异,但是重点是该返回值是一个可进行运算的类型,而且其值随着时间变化而变化。 time() 返回值的类型名是 time_t ,具体类型与系统有关。这没关系,我们可以使用强制类型转换:

#include <time.h> /* 提供time()的ANSI原型*/
srand1((unsigned int) time(0)); /* 初始化种子 */

一般而言, time() 接受的参数是一个 time_t 类型对象的地址,而时间值就存储在传入的地址上。当然,也可以传入空指针(0)作为参数,这种情况下,只能通过返回值机制来提供值。

可以把这个技巧应用于标准的ANSI C函数 srand()rand() 中。如果使用这些函数,要在文件中包含 stdlib.c 头文件。实际上,既然已经明白了 srand1()rand1() 如何使用内部链接的静态变量,你也可以使用编译器提供的版本。我们将在下一个示例中这样做。