首先,來看一個隨機數函數,該函數使用了一個具備內部連接的靜態變量。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; /*種子*/ int rand0(void) { /*產生僞隨機數的魔術般的公式 */ next = next * 1103515245 + 12345; return (unsigned int)(next/65536) % 32768; }
在程序清單12.7中靜態變量next的初始值爲1,在每次調用函數時它的值被一個魔術般的公式修改。結果是一個從0到32767範圍內的返回值。注意next是靜態、具備內部連接的,而不僅是靜態、空連接的。這是爲了稍後在將本例擴展時,便於next爲同一文件中的兩個函數共享。ui
咱們用程序清單12.8所示的簡單驅動程序來測試一下rand0()函數指針
程序清單12.8 r_drive0.c驅動程序code
/*r_drive0.c --測試rand0()函數*/ /*與rand0.c一塊兒編譯*/ #include <stdio.h> extern int rand0(void); int main(void) { int count; for(count=0;count<5;count++) printf("%hd\n",rand0()); return 0; }
如今又有一個機會來練習使用多文件。程序清單12.7和程序清單12.8分別使用一個文件。關鍵字extern提醒您rand0()在一個單獨的文件中定義。對象
輸出以下:作用域
16838 5758 10113 17515 31051
輸出看起來像隨機的。但讓咱們再來運行一次,此次結果以下:原型
16838 5758 10113 17515 31051
看起來很像,這就是「僞」的特徵了。每次運行主程序時,都從同一個種子值1開始。能夠經過引入容許重置種子的第二個函數srand1()來解決這個問題。關鍵是使next成爲一個具備內部連接的靜態變量,親只對rand1()和srand1()可見(C程序庫中與srand1()等效的函數被稱爲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> 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("%hd\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 your choice for seed: 3 20067 23475 8955 20841 15324 please enter your choice for seed: q Done
將1做爲seed的值,產生了與前面相同的結果。如今來嘗試將3做爲seed的值:
若是您的C實現容許您訪問系統時鐘這種不斷變化的量,能夠用它們的值(可能須要截斷)來初始化種子值。例如,ANSI C有一個函數time()能夠返回系統時間。時間單位由系統決定,但有用的一點是返回值爲數值類型,而且隨着時間變化。其確切類型與系統有關,名稱爲time_t,但您能夠對它進行類型指派。下面是基本思路:
#include <time.h> /*爲time()函數提供ANSI原型*/ srand1((unsigned int) time(0)) /*初始化種子*/
一般,time()的參數是一個time_t類型對象的地址。那種情形下,時間值也存儲在那個地址中。然而,您也能夠傳送空指針(0)做爲參數。此時,時間值僅經過返回值機制提供。
能夠對標準的ANSI C函數srand()和rand()使用一樣的技術。使用這些函數地,要包括stdlib.h頭文件。實際上,既然已經知道srand1()和rand1()如何使用一個具備內部連接的靜態變量,您一樣也可使用您的編譯器提供的版本。咱們將在下一個例子中這樣作。