前文鏈接:劣質代碼評析——猜數字問題(上)
【重構】
這個問題的解決並不復雜,最多只需要三個步驟:
- 生成無重復數字的四位整數;
- 重復猜數字最多10次,猜中則宣布勝利,程序結束;
- 10次沒猜中,宣布失敗,程序結束。
用C語言描述這個過程應該是這樣:
#define TIMES 10 //最多10次 int main( void ) { int count; //1. 生成無重復數字的四位整數; for( count = 0 ; count < TIMES ; count ++ ) //2. 重復猜數字最多10次, { //猜中則宣布勝利,程序結束; } //3. 10次沒猜中,宣布失敗,程序結束。 return 0; }
現在認真審視一下這個結構,思考一下它有無漏洞。這件事情很值得去做,千萬不要錯在起跑線上,否則后面的工作可能都是無用功。
沒有什么漏洞吧?得到這個結論並不困難,因為這本身就是一個簡單的問題。
接下來補充完善這個代碼。
寫代碼可以從前寫到后也可以從后向前寫,沒有一定之規。可以先寫難寫的部分也可以先完成容易的部分。下面的代碼完成了較為簡單的部分。
#include <stdio.h> #define TIMES 10 //最多10次 int main( void ) { int count; //1. 生成無重復數字的四位整數; for( count = 0 ; count < TIMES ; count ++ ) //2. 重復猜數字最多10次 { //猜數字 //if( 猜中 ) { puts("恭喜你猜對了"); //宣布勝利 return 0; //程序結束; } } //3. 10次沒猜中 printf("連續%d次你都沒猜中,抱歉,游戲結束\n",TIMES); //宣布失敗 return 0; //程序結束。 }
這個代碼盡管沒有完成全部功能,但卻已經可以進行測試。測試之后繼續完善
//1. 生成無重復數字的四位整數;
部分。
這部分首先應該到考慮這個四位整數的存儲問題。考慮到后面的算法需要按位進行比較,因此將其數據結構選擇為
#define NUM 4 //正整數位數 int main( void ) { int dig[NUM]; /*……*/ }
即按位分別存儲。
由於需要程序模擬隨機效果,所以需要首先初始化種子數(seed)。否則程序每次運行的結果都將一樣:
#include <stdlib.h> #include <time.h> srand( (unsigned) time( NULL ) );
接下來要做的事情是利用rand()函數生成一個各位不同的四位偽隨機數,並將其分解然后存入dig數組,這事情不可能一蹴而就,所以這里大而化之地把這些任務交給一個函數完成
generate( dig , NUM );
這樣,代碼就演化成為:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define TIMES 10 //最多10次 #define NUM 4 //正整數位數 int main( void ) { int dig[NUM]; //存放四位整數 int count; //1. 生成無重復數字的四位整數; srand( (unsigned)time( NULL ) ); //初始化seed generate( dig , NUM ); //生成無重復數字的四位整數,分解存入dig數組 for( count = 0 ; count < TIMES ; count ++ ) //2. 重復猜數字最多10次, { //猜數字 //if( 猜中 ) { puts("恭喜你猜對了"); //宣布勝利 return 0; //程序結束; } } //3. 10次沒猜中 printf("連續%d次你都沒猜中,抱歉,游戲結束\n",TIMES); //宣布失敗 return 0; //程序結束。 }
generate()函數首要的任務是生成一個四位偽隨機數,這可以通過rand() % ( 10000 - 1000 - 1 ) + 1000 這個表達式實現。之后需要檢查這個四位數是否有重復數字,如果沒有重復則將其分解存入數組。大體上這樣
do { 生成一個四位數 } while(四位數有重復數字); 分解存儲;
但是為了更容易地判斷四位數是否有重復數字,一個技巧性的寫法是
do { 生成一個四位數; 分解存儲; } while(四位數有重復數字);
因此,generate()的這個函數的原型及定義為
#define MIN_5 10000 #define MIN_4 1000 #define TRUE 1 #define FALSE 0 void generate( int [] , int ); void generate( int d[] , int n ) { do { int temp = rand() % ( MIN_5 - MIN_4 - 1 ) + MIN_4 ; //生成一個四位數 resolve( temp , d , n ); //分解存儲; } while( be_reduplicate( d , n ) == TRUE ); //四位數有重復數字 }
其中的resolve()函數的作用是將一個正整數分解並按次序存入數組:
#define TEN 10 void resolve( int , int [] , int ); void resolve( int t , int d[] , int n ) { while( n-- > 0 ) { d[n] = t % TEN ; t /= TEN ; } }
be_reduplicate()函數判斷四位數字中是否有重復,如果有重復數字則再調用rand()重新生成。
int be_reduplicate( int [] , int ); int be_reduplicate( int d[] , int n ) { int i; for( i = 0 ; i < n - 1 ; i ++ ) { int j ; for( j = i + 1 ; j < n ; j ++ ) if( d[i] == d[j] ) return TRUE ; } return FALSE ; }
至此,//1. 生成無重復數字的四位整數;部分功能全部完成。下面繼續完成
//2. 重復猜數字最多10次,
中尚未完成的部分。
“//猜數字”的功能要求用戶輸入所猜的數,因此需要為這個輸入的數據預備存儲空間,所以
int g_dig[NUM];
“//猜數字”這個過程本身可以用一個函數實現
void guess ( int [] , int ) ; int main( void ) { /*……*/ { int g_dig[NUM]; guess ( g_dig , NUM ) ; //猜數字 } /*……*/ } void guess ( int d[] , int n ) { int i ; printf("輸入你猜的數:"); for( i = 0 ; i < n ; i ++ ) scanf("%1d" , d + i ); }
guess ()中的scanf("%1d" , d + i );是一種較為簡潔的實現方法,但是這種方法的健壯性並不夠。一旦用戶輸入了非十進制數字的非空白字符,就會出現問題。這個不足將在后面予以改進。
最后編寫“//if( 猜中 )”部分的代碼。是否“猜中”可由一函數判斷
if( be_same(dig , g_dig , NUM ) == TRUE )
其中be_same()函數為
int be_same ( int [] , int [] , int ); int be_same ( int ori[] , int spe[] , int n ) { int A = 0 , B = 0 ; int i ; for( i = 0 ; i < n ; i ++ ) if( spe[i] == ori[i] ) A++ ; else if( be_inside ( spe[i] , ori , n ) == TRUE ) B++; if( A == n ) return TRUE ; printf("%dA%dB\n",A,B); //輸出幾A幾B return FALSE ; }
其中的be_inside()函數用於判斷第一個參數dig是否在后面的d數組中
int be_inside ( int , int [] , int ); int be_inside ( int dig , int d[] , int n ) { while( n-- > 0 ) if( dig == d[n] ) return TRUE; return FALSE; }
至此,問題解決。下面給出這一問題的完整代碼。這個代碼在前面的基礎上對guess ()的健壯性做了改進,另外添加了在10次沒猜中時輸出被猜的數的代碼。
#include <stdio.h> #include <stdlib.h> #include <time.h> #define TIMES 10 //最多10次 #define NUM 4 //正整數位數 #define MIN_5 10000 #define MIN_4 1000 #define TRUE 1 #define FALSE 0 #define TEN 10 void generate( int [] , int ); void resolve( int , int [] , int ); int be_reduplicate( int [] , int ); void guess ( int [] , int ) ; int be_same ( int [] , int [] , int ); int be_inside ( int , int [] , int ); void out( int [] , int ); int main( void ) { int dig[NUM]; //存放四位整數 int count; //1. 生成無重復數字的四位整數; srand( (unsigned)time( NULL ) ); //初始化seed generate( dig , NUM ); //生成無重復數字的四位整數,分解存入dig數組 for( count = 0 ; count < TIMES ; count ++ ) //2. 重復猜數字最多10次, { int g_dig[NUM]; //預備數據存儲空間 guess ( g_dig , NUM ) ; //猜數字 if( be_same(dig , g_dig , NUM ) == TRUE ) //if( 猜中 ) { puts("恭喜你猜對了"); //宣布勝利 system("PAUSE");return 0; //程序結束; } } //3. 10次沒猜中 printf("那個數是:"); out( dig , NUM ); //輸出被猜的數 printf("連續%d次你都沒猜中,抱歉,游戲結束\n",TIMES); //宣布失敗 return 0; //程序結束。 } void out( int d[] , int n ) { int i ; for( i = 0 ; i < n ; i ++ ) printf("%d " , d[i]); putchar('\n'); } int be_inside ( int dig , int d[] , int n ) { while( n-- > 0 ) if( dig == d[n] ) return TRUE; return FALSE; } int be_same ( int ori[] , int spe[] , int n ) { int A = 0 , B = 0 ; int i ; for( i = 0 ; i < n ; i ++ ) if( spe[i] == ori[i] ) A++ ; else if( be_inside ( spe[i] , ori , n ) == TRUE ) B++; if( A == n ) return TRUE ; printf("%dA%dB\n",A,B); //輸出幾A幾B return FALSE ; } void guess ( int d[] , int n ) { int i ; printf("輸入你猜的數:"); for( i = 0 ; i < n ; i ++ ) { scanf("%*[^0123456789]"); //讀非十進制數字字符 scanf("%1d" , d + i ); } } int be_reduplicate( int d[] , int n ) { int i; for( i = 0 ; i < n - 1 ; i ++ ) { int j ; for( j = i + 1 ; j < n ; j ++ ) if( d[i] == d[j] ) return TRUE ; } return FALSE ; } void resolve( int t , int d[] , int n ) { while( n-- > 0 ) { d[n] = t % TEN ; t /= TEN ; } } void generate( int d[] , int n ) { do { int temp = rand() % ( MIN_5 - MIN_4 - 1 ) + MIN_4 ; //生成一個四位數 resolve( temp , d , n ); //分解存儲; } while( be_reduplicate( d , n ) == TRUE ); //四位數有重復數字 }