問題:
n-1位數字
已知w是一個大於10但不大於1000000的無符號整數,若w是n(n≥2)位的整數,則求出w的后n-1位的數。
輸入:
第一行為M,表示測試數據組數。
接下來M行,每行包含一個測試數據。
輸出:
輸出M行,每行為對應行的n-1位數(忽略前綴0)。如果除了最高位外,其余位都為0,則輸出0。
樣例:4 1023 5923 923 1000
輸出 23 923 23 0
原代碼:
#include <stdio.h> #include <math.h> int digit(int x) //定義函數取位數 { int i,temp; for(i=1;;i++) { temp =(int) pow(10,i); if(x/temp==0) return i-1; } } int main() { unsigned int w[100],i,m,number,a,temp; int digit(int x); printf("想輸入幾組數據:\n"); scanf("%d",&m); printf("請輸入相應數字\n"); for (i = 0;i<m;i++) //輸入相應的數值 { printf("%d、",i+1); scanf("%d",&w[i]); } printf("您要的n-1位數字如下:\n"); for(i=0; i <m; i++) //輸出想要的n-1位數 { a=digit(w[i]); temp=(int)pow(10,a); number=w[i]/temp; number=w[i]-number*temp; printf("%d\n",number); } return 0; }
評析:
先說一下題目。這個題目比較好,程序的要求和問題的范圍非常明確,而且給出了樣例。美中不足的是“無符號整數”這個詞,unsigned是C代碼層面的概念,一般不宜用於描述問題。在問題層面上,應該說“非負整數”而不是“無符號整數”。
源代碼的總體結構欠佳,作者把函數定義寫在了函數調用前,整個源代碼結構頭重腳輕。
main():
unsigned int w[100],i,m,number,a,temp;
變量定義太多,並不必要地使用了數組這種復雜的數據結構。
實際上,這里只需要定義一個變量,就是測試數據組數——m。
int digit(int x);
函數類型聲明位置不當,應該放在main()之外,main()的前面。函數的類型也不正確,最初作者想到了用unsigned,但后來又隨手用了int。
printf("想輸入幾組數據:\n"); scanf("%d",&m); printf("請輸入相應數字\n");
這個寫得很不錯。作者沒有像很多ACMer那樣省去printf()輸出的那兩行提示信息。
for (i = 0;i<m;i++) //輸入相應的數值 { printf("%d、",i+1); scanf("%d",&w[i]); }
完全沒必要用數組,用一個int變量記錄輸入數據就可以了。只要在輸入數據后直接求解就可以就可以保證這個int變量可以反復使用。即
for (i = 0;i<m;i++) { int w ; printf("%d、",i+1); scanf("%d" , &w ); //輸入相應的數值 //求后n-1位的數 }
另外這里用for語句顯然不如使用while語句。
while ( m -- > 0 ) { int w ; printf("%d、",i+1); scanf("%d" , &w ); //輸入相應的數值 //求后n-1位的數 }
使用while語句根本用不着那個沒什么意義的i。初學者往往迷信for語言功能最強(這也往往是受到了老譚的壞影響。老譚書中濫用for語句的例子比比皆是,且不惜篇幅,大講特講)。
for(i=0; i <m; i++) //輸出想要的n-1位數 { a=digit(w[i]); temp=(int)pow(10,a); number=w[i]/temp; number=w[i]-number*temp; printf("%d\n",number); }
這段毛病較多。首先調用了digit()函數,
a=digit(w[i]);
這個函數的定義是:
int digit(int x) //定義函數取位數 { int i,temp; for(i=1;;i++) { temp =(int) pow(10,i); if(x/temp==0) return i-1; } }
功能是求x的位數。
從算法的角度來說,求x的位數毫無必要。
這里求的方法也是錯誤的,因為作者調用了pow()庫函數。pow()函數的原型是
double pow(double x, double y);
參數和返回值都是浮點類型,這表明這個函數只是求一個近似值。把求近似值的函數用於整數領域的這種精確問題明顯有問題。譬如當a為4時,(int)pow(10,a)的值可能是9999而不是10000。不過很多職業程序員也不懂得這一點,被指出后還惱羞成怒(參見 算法:求比指定數大且最小的“不重復數”問題的高效實現 、似是而非的k=sqrt(n) )。
求一個整數的位數其實很簡單。
unsigned digit( unsigned x ) { int pl = 1u ; while ( ( x /= 10u ) != 0u ) { pl ++ ; } return pl ; }
由於題目認為根本就不需要調用庫函數,因此也沒有預備庫函數給考題者。所以,盡管作者寫上了
#include <math.h>
但還是被無情地被回絕了:
In function `digit':
undefined reference to `pow'
In function `main':
undefined reference to `pow'
回到main()中第二個for語句循環體中被打斷的地方
temp=(int)pow(10,a);
作者再次錯誤地調用了pow()。原來就是想求出把高位留下,后面各位都置0的數而已。我只能說這位初學者想法是正確的,但是辦法是錯誤的。求這個數連求原數的位數都不用,更不用說不用蹩腳地調用pow()了:
unsigned digit( unsigned x ) { int temp = 1u ; while ( ( x / temp ) > 10u ) { temp *= 10u ; } return x % temp ; }
由此,不難發現main()里第二條for語句中的
number=w[i]/temp; number=w[i]-number*temp;
也是完全多余的。順便說一句,即使要完成這兩條語句的功能,也不應該在for的循環體內完成,而應該通過調用函數完成。
重構:
#include <stdio.h> unsigned get_low_num( unsigned ) ; int main( void ) { unsigned m ; printf( "想輸入幾組數據:\n" ); scanf( "%u" , &m ); while ( m -- > 0u ) { unsigned w ; printf( "請輸入相應數字\n" ); scanf( "%u" , &w ); printf( "您要的n-1位數字如下:\n" ); printf( "%u\n" , get_low_num( w ) ); } return 0; } unsigned get_low_num( unsigned x ) { unsigned temp = 1u ; while ( ( x / temp ) > 10u ) { temp *= 10u ; } return x % temp ; }