題目
分數的加減法
編寫一個C程序,實現兩個分數的加減法
輸入:輸入包含多行數據
每行數據是一個字符串,格式是"a/boc/d"。
其中a, b, c, d是一個0-9的整數。o是運算符"+"或者"-"。
輸出:對於輸入數據的每一行輸出兩個分數的運算結果。
注意結果應符合書寫習慣,沒有多余的符號、分子、分母,並且化簡至最簡分數樣例輸入:
1/8+3/8
1/4-1/2
1/3-1/3
輸出:
1/2
-1/4
0
評析
完全看不懂題目!
本來是一個很好的問題,可惜被出題者給敗壞了。
最大的毛病出在“每行數據是一個字符串,格式是"a/boc/d"”這行文字,驢唇不對馬嘴,令人無法理解。
字符串的定義是:A string is a contiguous sequence of characters terminated by and including the first null character.在文本輸入流中並不存在null character——\0,因此嚴格地說文本流根本不可能存在字符串。
更搞笑的是“格式是"a/boc/d"”。把"a/boc/d"這種東西叫字符串(String Literal)的前提是在C代碼層面而言。若"……"這種形式的東西出現在莎士比亞戲劇中,那就絕對不是字符串。把輸入文本流中的"……"說成是字符串,是“關公戰秦瓊”式的說法。看了后面的輸入樣例,我才弄明白這里的""是根本沒有的。
另一個毛病是“其中a, b, c, d是一個0-9的整數”,這是畫蛇添足式的簡化,並沒有使問題得到任何真正的簡化,只起到了迷惑解題者、束縛解題者的作用。
綜上所述,這是一個極好的問題,但卻是一個敗家的提法。
對這樣的問題,經常出現的情形是,初學者已經開始寫代碼了,高手還在呆呆的思考題目和題目的要求到底是什么。這是初學者和成熟的程序員之間一個非常顯著的區別。
初學者解決問題的時間分配通常是倒三角形
而成熟的程序員則恰恰相反
當然,我並不是反對新手在寫代碼方面進行大量練習,但是新手特別容易忽視理解問題要求和很少在數據結構方面深思熟慮的弱點應該予以充分的重視。
原代碼1:
#include <stdio.h> #include <math.h> int comdiv(int x,int y); int main() { char string[8]; int a[4],i,j,comd,b[2]; while(gets(string)!=NULL) { for(i=0,j=0;i<4;i++,j+=2) a[i]=(int)string[j]-48; if(a[1]==0 || a[3]==0) printf("matherror\n"); else if(string[3]=='-') { b[0]=a[0]*a[3]-a[1]*a[2]; b[1]=a[1]*a[3]; if(b[0]==0) printf("0\n"); else if(b[0]>0) comd=comdiv(b[0],b[1]); else { comd=comdiv(-b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); } } else { b[0]=a[0]*a[3]+a[1]*a[2]; b[1]=a[1]*a[3]; comd=comdiv(b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); } } return 0; } int comdiv(int x,int y) { int i,j; if(x==0||y==0) return 1; else if(x==1||y==1) return 1; else if(x==y) return x; else if(x>y && !(x%y)) return y; else if(!(y%x)) return x; for(i=2,j=1;i<=(x>y?x:y);i++) { if(x%i==0&&y%i==0) j=i; } return j; }
評析:
#include <math.h>
一旦涉及計算,新手就喜歡寫這個,實際上根本不需要。在math.h中列出函數原型的函數都是近似計算函數,整數精確計算通常都用不着math.h中的函數。
char string[8];
這里的毛病是數組定義得太小,很容易出毛病。受到譚浩強流的壞影響,不少初學者在為存儲字符串定義字符數組時,經常有斤斤計較的毛病。
這位小朋友把a , b ,c ,d視為一個int [4],把運算結果視為一個int [2]。應該說有初步的數據結構設計意識,但還處於很幼稚的階段。
while(gets(string)!=NULL)
這個完全是被題目給騙蒙了,真的把輸入流中的 1/8+3/8 當作字符串存儲了,這很傻。實際上,對於輸入流中的 1/8+3/8 可以有很多視角。從scanf()的角度來看,也可以把 1/8+3/8 視為"%d/%d%c%d/%d"。這樣就不難明白為什么前面說原題目中的“其中a, b, c, d是一個0-9的整數”是“畫蛇添足式的簡化,並沒有使問題得到任何真正的簡化,只起到了迷惑解題者、束縛解題者的作用”了。
還有需要說明的是,gets()這個函數事實上已經被C語言開除了,在C語言正式開除它之前,很多職業程序員在更早的時候就已經非正式地把這個函數從C語言中開除了。如果要完成gets()函數的功能可以考慮使用fgets()函數,或自己寫函數實現。
for(i=0,j=0;i<4;i++,j+=2) a[i]=(int)string[j]-48;
- 無論如何都應通過一個函數實現這段代碼的功能。
- 那個“48”屬於譚浩強流,應該寫'0'。
- (int)string[j],似是而非。string[j]作為右值本來就是int類型。所有以右值參與運算的char類型數據事實上都是int類型。
- 這種寫法的容錯性弱爆了。哪怕輸入為 1/8 + 3/8 【注: + 兩側有空格】 都會帶來滅頂之災。
if(a[1]==0 || a[3]==0) printf("matherror\n"); else if(string[3]=='-') { b[0]=a[0]*a[3]-a[1]*a[2]; b[1]=a[1]*a[3]; if(b[0]==0) printf("0\n"); else if(b[0]>0) comd=comdiv(b[0],b[1]); else { comd=comdiv(-b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); } } else { b[0]=a[0]*a[3]+a[1]*a[2]; b[1]=a[1]*a[3]; comd=comdiv(b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); }
結構太差。應該
if(a[1]==0 || a[3]==0) { printf("matherror\n"); continue ; } if(string[3]=='-') { /* …… */ } else { /* …… */
}
if(string[3]=='-') { b[0]=a[0]*a[3]-a[1]*a[2]; b[1]=a[1]*a[3]; if(b[0]==0) printf("0\n"); else if(b[0]>0) comd=comdiv(b[0],b[1]); else { comd=comdiv(-b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); } } else { b[0]=a[0]*a[3]+a[1]*a[2]; b[1]=a[1]*a[3]; comd=comdiv(b[0],b[1]); printf("%d/%d\n",b[0]/comd,b[1]/comd); }
這段代碼若使用switch語句會更自然些。從實現功能的角度來說,作者沒有考慮到1/2+1/2這樣的的情形,沒有考慮到3/4+1/2這樣的情形。所以這段代碼是錯誤的。其他的大小毛病相當多,這里不一一指出了。主要的問題在於,原作者沒有對數據進行很好的抽象,因此也就無法很好地組織代碼;結構化程序設計思想沒有得到徹底的貫徹,迷失於在main()中糾結細節。