C/C++ 隨機數函數

學習了不同存儲類別的概念後,我們來看幾個相關的程序。
首先,來看 一個使用內部鏈接的靜態變量的函數:隨機數函數。
ANSI C庫提供了rand() 函數生成隨機數。生成隨機數有多種算法,ANSI C允許C實現針對特定機器 使用最佳算法。
然而,ANSI C標準還提供了一個可移植的標準算法,在不 同系統中生成相同的隨機數。實際上,rand()是“僞隨機數生成器”,意思是 可預測生成數字的實際序列。但是,數字在其取值範圍內均勻分佈。

爲了看清楚程序內部的情況,我們使用可移植的ANSI版本,而不是編譯器內置的rand()函數。可移植版本的方案開始於一個“種子”數字。該函數使用該種子生成新的數,這個新數又成爲新的種子。然後,新種子可用於生 成更新的種子,以此類推。該方案要行之有效,隨機數函數必須記錄它上一 次被調用時所使用的種子。這裏需要一個靜態變量。

/* 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是具有內部鏈接的靜態變量(並非無鏈接)。這是爲了方便稍後擴展本例,供同一個文件中的其他函數共享。

/* 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給出了修改後的文件。

/* 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);
897
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;
}

編譯兩個文件,運行該程序後,其輸出如下:

1
16838
5758
898
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()如何使用內部鏈接的靜態變量,你也可以使用編譯器提供 的版本。我們將在下一個示例中這樣做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章