劣质代码评析——猜数字问题(下)
前文链接:劣质代码评析——猜数字问题(上)
【重构】
这个问题的解决并不复杂,最多只需要三个步骤:
- 生成无重复数字的四位整数;
- 重复猜数字最多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 ); //四位数有重复数字 }