🥭随机数函数和静态变量
2021-1-24
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
来看一个使用内部链接的静态变量的函数:随机数函数。ANSI C库提供了rand()函数生成随机数。生成随机数有多种算法,ANSI C允许C实现针对特定机器使用最佳算法。然而,ANSI C标准还提供了一个可移植的标准算法,在不同系统中生成相同的随机数。实际上,rand()是“伪随机数生成器”,意思是可预测生成数字的实际序列。但是,数字在其取值范围内均匀分布
 
为了看清楚程序内部的情况,使用可移植的ANSI版本,而不是编译器内置的rand()函数。可移植版本的方案开始于一个“种子”数字。该函数使用该种子生成新的数,这个新数又成为新的种子。然后,新种子可用于生成更新的种子,以此类推。该方案要行之有效,随机数函数必须记录它上一次被调用时所使用的种子。这里需要一个静态变量。
静态变量next的初始值是1,其值在每次调用rand0()函数时都会被修改(通过魔术公式)。该函数是用于返回一个0~32767之间的值。注意,next是具有内部链接的静态变量(并非无链接)。这是为了方便稍后扩展本例,供同一个文件中的其他函数共享。
这两次的输出完全相同,这体现了“伪随机”的一个方面。每次主程序运行,都开始于相同的种子1。可以引入另一个函数srand1()重置种子来解决这个问题。关键是要让next成为只供rand1()srand1()访问的内部链接静态变量(srand1()相当于C库中的srand()函数)。把srand1()加入rand1()所在的文件中。
注意,next是具有内部链接的文件作用域静态变量。这意味着rand1()和srand1()都可以使用它,但是其他文件中的函数无法访问它。
设置seed的值为1,输出的结果与前面程序相同。但是设置seed的值为513后就得到了新的结果。 注意 自动重置种子如果 C 实现允许访问一些可变的量(如,时钟系统),可以用这些值(可能会被截断)初始化种子值。例如,ANSI C有一个time()函数返回系统时间。虽然时间单元因系统而异,但是重点是该返回值是一个可进行运算的类型,而且其值随着时间变化而变化。time()返回值的类型名是time_t,具体类型与系统有关。这没关系,我们可以使用强制类型转换:
一般而言,time()接受的参数是一个 time_t 类型对象的地址,而时间值就储存在传入的地址上。当然,也可以传入空指针(0)作为参数,这种情况下,只能通过返回值机制来提供值。
可以把这个技巧应用于标准的ANSI C函数srand()和rand()中。如果使用这些函数,要在文件中包含stdlib.c头文件。实际上,既然已经明白了srand1()和rand1()如何使用内部链接的静态变量,也可以使用编译器提供的版本。
 

掷骰子

模拟一个非常流行的游戏——掷骰子。骰子的形式多种多样,最普遍的是使用两个6面骰子。在一些冒险游戏中,会使用5种骰子:4面、6面、8面、12面和20面。聪明的古希腊人证明了只有5种正多面体,它们的所有面都具有相同的形状和大小。各种不同类型的骰子就是根据这些正多面体发展而来。也可以做成其他面数的,但是其所有的面不会都相等,因此各个面朝上的几率就不同。
 
计算机计算不用考虑几何的限制,所以可以设计任意面数的电子骰子。先从6面开始。
我们想获得1~6的随机数。然而,rand()生成的随机数在0~RAND_MAX之间。RAND_MAX被定义在stdlib.h中,其值通常是INT_MAX。因此,需要进行一些调整,方法如下。 1.把随机数求模6,获得的整数在0~5之间。 2.结果加1,新值在1~6之间。 3.为方便以后扩展,把第1步中的数字6替换成骰子面数。 下面的代码实现了这3个步骤:
我们还想用一个函数提示用户选择任意面数的骰子,并返回点数总和。
该文件加入了新元素。第一,rollem()函数属于该文件私有,它是roll_n_dice()的辅助函数。第二,为了演示外部链接的特性,该文件声明了一个外部变量roll_count。该变量统计调用rollem()函数的次数。这样设计有点蹩脚,仅为了演示外部变量的特性。第三,该文件包含以下预处理指令:#include "diceroll.h”
如果使用标准库函数,如 rand(),要在当前文件中包含标准头文件(对rand()而言要包含stdlib.h),而不是声明该函数。因为头文件中已经包含了正确的函数原型。我们效仿这一做法,把roll_n_dice()函数的原型放在diceroll.h头文件中。把文件名放在双引号中而不是尖括号中,指示编译器在本地查找文件,而不是到编译器存放标准头文件的位置去查找文件。“本地查找”的含义取决于具体的实现。一些常见的实现把头文件与源代码文件或工程文件(如果编译器使用它们的话)放在相同的目录或文件夹中。
该头文件中包含一个函数原型和一个 extern 声明。由于 direroll.c 文件包含了该文件, direroll.c实际上包含了roll_count的两个声明:
这样做没问题。一个变量只能有一个定义式声明,但是带 extern 的声明是引用式声明,可以有多个引用式声明。使用 roll_n_dice()函数的程序都要包含 diceroll.c 头文件。包含该头文件后,程序便可使用roll_n_dice()函数和roll_count变量。
  • C
  • 存储类别分配内存:malloc()和free()
    目录