“TC過(guò)時(shí)了”,我說(shuō)。接著就一堆人問(wèn),“為什么過(guò)時(shí)。俊,“TC不是很好用嗎”,
“教材上也是用這個(gè)啊”,“二級(jí)考試不也是規(guī)定用的TC嗎?”。
于是乎我已經(jīng)無(wú)語(yǔ)了。
/----------------------------------------------------------------------/
一。頭文件的問(wèn)題
好了,來(lái)看個(gè)經(jīng)典的TC2下在老潭的書(shū)的《C語(yǔ)言程序設(shè)計(jì)(第二版)》,
第4頁(yè)的一段代碼:
main( )
{
printf("This is a C program.\n");
}
好了,當(dāng)時(shí)的TC2的確允許使用printf和scanf可以不加頭文件,
也的確允許main()這樣的聲明。允許是允許了,可是你寫成這樣對(duì)你有好處嗎?
打個(gè)比方,Basic語(yǔ)言知道嗎?這種語(yǔ)言可以不聲明變量就使用。
的確同樣也是允許了,可是給我的話,我會(huì)要求編譯器強(qiáng)制檢查變量是不是聲明了再使用。
為什么?如果你需要用名字是nlen這個(gè)變量,當(dāng)中有一個(gè)不小心寫成n1en,
不強(qiáng)制檢查的話,編譯器不告訴你錯(cuò),但運(yùn)行結(jié)果是錯(cuò)誤的。但這種錯(cuò)誤要是在很多行
代碼里你怎么找?特別是'l'和'1'如此相似。
編譯器雖然允許,但你別以為那樣寫就是好事。說(shuō)回剛剛的C代碼,TC2允許不加頭文件,
于是不少初學(xué)的人習(xí)慣了從不寫頭文件,以為C語(yǔ)言不需要頭文件似的。
好了,等到看到那本書(shū)的138頁(yè),那個(gè)字符輸入輸出,用了gets,
就突然多了一個(gè)#include <stdio.h>,仔細(xì)一看,那一頁(yè)中間有100來(lái)字強(qiáng)調(diào)
了要用庫(kù)函數(shù),可是沒(méi)有講怎么用,下面多了的那一行初學(xué)者也可能會(huì)覺(jué)得奇怪,
要是不理解的人就跳過(guò)了,要是沒(méi)留意的更不當(dāng)一回事了,誰(shuí)讓之前書(shū)里一直都
沒(méi)有那句呢。然后,當(dāng)有的初學(xué)者用TC3.0打代碼的時(shí)候,編譯提示說(shuō)scanf沒(méi)有
聲明(或者是用TC2的打了gets函數(shù)說(shuō)gets沒(méi)有聲明),他們就郁悶了,
都是按照書(shū)的代碼啊,都一模一樣還會(huì)錯(cuò)?于是跑來(lái)論壇問(wèn)。有跑來(lái)問(wèn)的還好,
最怕的就是用TC2的,看了數(shù)組覺(jué)得難還跳了過(guò)去的,于是一直都不會(huì)寫頭文件,
成了習(xí)慣。要是隨便換個(gè)環(huán)境,結(jié)果沒(méi)寫頭文件的,錯(cuò)了,結(jié)果還說(shuō)自己寫C代碼
寫那么多,這里怎么就錯(cuò)了,然后就懷疑編譯器是不是壞的。然后就借口說(shuō)這個(gè)
用不習(xí)慣要換TC2。結(jié)果就是惡性循環(huán),跳不出TC2,學(xué)不到新領(lǐng)域的東西。
論壇上這種代碼絕不少見(jiàn),隨便翻一些提問(wèn)帖子肯定找得到。其實(shí)早在TC3.0已經(jīng)
不再允許scanf和printf不加頭文件了,隨后的所有C編譯器也不再支持TC2的
這個(gè)特性了。
/----------------------------------------------------------------------/
二。main的聲明
其實(shí)如果只是算潭書(shū)的第二版,這個(gè)無(wú)類型的main聲明沒(méi)有問(wèn)題,只是直到C99標(biāo)準(zhǔn)
的出現(xiàn),這個(gè)聲明才徹底廢除。其實(shí)也是同樣一個(gè)問(wèn)題,允許你不寫,但這不是說(shuō)你
這樣用就是好事。不過(guò),這個(gè)的后果有點(diǎn)不一樣的就是,你忽略不寫,很可能以為是
不需要返回的,或者是以為返回一個(gè)無(wú)類型的(void),接著就出現(xiàn)了main函數(shù)里不
寫return 0;的問(wèn)題。那本書(shū)就是從頭到尾所有main函數(shù)一律沒(méi)有return 0; 。好了,
等到學(xué)了函數(shù)那章,書(shū)上說(shuō),沒(méi)有返回值的函數(shù)應(yīng)該要用void來(lái)聲明,好了,
void main就這樣誕生了。不信就看看譚書(shū)的第三版,變成從頭到尾的void main()。
不過(guò)話說(shuō)回頭,這樣的問(wèn)題對(duì)于初學(xué)者學(xué)習(xí)基本語(yǔ)法和結(jié)構(gòu)方面和寫寫小程序的都不會(huì)
出現(xiàn)任何影響。在編譯器上來(lái)說(shuō),這只是一個(gè)標(biāo)準(zhǔn)的問(wèn)題,但對(duì)于你來(lái)說(shuō)別小看了這么
一點(diǎn)點(diǎn)的變化。因?yàn)槟懔?xí)慣了main這種寫法的話,必然會(huì)把這個(gè)習(xí)慣帶到編寫其它函數(shù)里
(這里暫且不講main本身返回值的問(wèn)題)。
舉個(gè)簡(jiǎn)單例子,寫以下代碼:
#include <stdio.h>
factorial(int n)
{
if(n==1)return 1;
else if(n>1)return n * factorial(n-1);
}
int main(void)
{
int n;
while(scanf("%d", &n),n>=0)
{
printf("%d\n", factorial(n));
}
return 0;
}
看得出來(lái)是計(jì)算階乘的(主要看那個(gè)子函數(shù),main函數(shù)用回標(biāo)準(zhǔn)寫法),
只不過(guò)少了int聲明和最后一個(gè)必然的return而已,
看看輸入一個(gè)0進(jìn)去會(huì)發(fā)生什么事吧:
TC2: 1 (結(jié)果居然是碰對(duì)了)
VC6: -858993460 (不確定的隨機(jī)結(jié)果)
GCC: 0 (C99標(biāo)準(zhǔn))
如果你用TC,那你還可能以為這份代碼是正確的,
于是你就以為遞歸算階乘就是這么寫的。
于是還反過(guò)來(lái)問(wèn)我:“喂,雨中飛燕,這個(gè)結(jié)果不就是正確的嗎,還錯(cuò)什么。俊
于是我無(wú)語(yǔ)了。。。然后你也就留下了一個(gè)你可能以后都不打算去檢查的Bug。。。
現(xiàn)在我們?cè)賮?lái)看一個(gè):
#include <stdio.h>
long Factorial(int n)
{
if(n==1)return 1;
else if(n>1)return n * Factorial(n-1);
return 1;
}
FactorialSum(int n)
{
float f = 0;
for(;n>0;--n)
{
f += 1.0/Factorial(n);
}
return f;
}
int main(void)
{
int n;
while(scanf("%d", &n),n>=0)
{
printf("%f\n", FactorialSum(n));
}
return 0;
}
這個(gè)是計(jì)算1/1! + 1/2! + 1/3! +...+ 1/n!的代碼,F(xiàn)actorial的改好了,
然后加一個(gè)FactorialSum函數(shù)計(jì)算和。里面的變量f就是用來(lái)累加(喜歡用float來(lái)
保存浮點(diǎn)也是書(shū)上的一大不良特色),1寫成1.0保證結(jié)果不是整數(shù)。似乎沒(méi)錯(cuò)吧?
運(yùn)行一下不就知道了嘛,輸入1,輸出0;輸入2,輸出0;輸入5,輸出0,
結(jié)果是輸出全部是0。原因就是你省略類型埋的禍根,只需要在那個(gè)函數(shù)前面補(bǔ)上
一個(gè)float那結(jié)果就正確了。
這個(gè)危害有多嚴(yán)重嗎?在這里不算嚴(yán)重,因?yàn)樵谶@里代碼很短,相當(dāng)容易看得出來(lái)。
要是換成大程序呢?上千代碼甚至上萬(wàn)的代碼,要是最后都懶得寫一下return,
或者一個(gè)返回類型,那么錯(cuò)誤還怎么找?你要是這個(gè)也省略那個(gè)也省略,
寫了N多行代碼的時(shí)候,一運(yùn)行,這個(gè)結(jié)果也錯(cuò)那個(gè)輸入也不行的時(shí)候,
你再看看你是不是被你自己的“習(xí)慣”給難住你自己吧。
其實(shí)說(shuō)實(shí)在的,WIN-TC自帶的一個(gè)tcsearch.exe文件,那個(gè)可以查函數(shù)用法和示例,
上面的示例代碼全部都是一樣的風(fēng)格: int main(void)
非常規(guī)范標(biāo)準(zhǔn)的寫法,可是有多少人看了這個(gè)了呢?
/----------------------------------------------------------------------/
三。函數(shù)聲明與返回值
經(jīng)典的老代碼:
int max(x,y)
int x,y;
{
return x>y ? x : y;
}
現(xiàn)在早已不是pascal時(shí)代了,這樣的函數(shù)聲明不但難讀,并且現(xiàn)在C99標(biāo)準(zhǔn)已經(jīng)不再
支持這種寫法了。在老潭的書(shū)也僅僅用了半版不到的篇幅提到了一下,
其它地方并沒(méi)有使用這種聲明,這點(diǎn)做得還不錯(cuò)。不過(guò)很奇怪的是論壇上還時(shí)不時(shí)
能看見(jiàn)這種聲明,這個(gè)到底是拜誰(shuí)所賜呢?這我就不知道了,這里也不展開(kāi)來(lái)講了。
/----------------------------------------------------------------------/
四。代碼風(fēng)格
這個(gè)問(wèn)題就非常嚴(yán)重了,F(xiàn)在的情況是,從代碼風(fēng)格,就可以知道你的大概水平了,
至少能知道你是不是菜鳥(niǎo)。那些代碼縮進(jìn)弄得亂七八糟的,不用看詳細(xì)代碼都知道
水平肯定高不到哪里去(當(dāng)然不排除你可以故意弄亂)。因?yàn)橐亲鳛橐粋(gè)新手,
要是寫的代碼亂,調(diào)試的時(shí)候或者自己看自己的代碼的時(shí)候,要是你自己都覺(jué)得亂,
當(dāng)代碼有Bug,你要找出來(lái)的話,看你還頭痛不頭痛了。本來(lái)代碼就有問(wèn)題,再加上
格式亂,要是你自己看著都不舒服,那你還怎么去調(diào)試代碼呢?自己不會(huì)看著頭暈?
代碼少的時(shí)候你可能不覺(jué)得,等你寫了上百行代碼的那再嘗嘗這種滋味吧。
更嚴(yán)重的問(wèn)題是,DOS原版TC對(duì)縮進(jìn)支持不好,
格式的控制相對(duì)其它的編輯器來(lái)說(shuō)都要弱。
對(duì)于初學(xué)者,很容易弄出參差不齊的代碼,
對(duì)于學(xué)習(xí)方面來(lái)說(shuō)這是一個(gè)很不利的因素。
再者,大括號(hào)的位置及變量聲明位置的問(wèn)題。看以下幾種風(fēng)格:
1.
int main(void) //潭氏風(fēng)格
{int n,s;
while(scanf("%d", &n),n>=0)
{
s = factorial(n);
printf("%d\n", s);
}
return 0;
}
2.
int main(void) //視頻教學(xué)風(fēng)格?
{ int n,s;
while(scanf("%d", &n),n>=0)
{ s = factorial(n);
printf("%d\n", s);
}
return 0;
}
3.
int main(void) //不知道這風(fēng)格的來(lái)源
{
int n,s;
while(scanf("%d", &n),n>=0)
{
s = factorial(n);
printf("%d\n", s);
}
return 0;
}
4.
int main(void){ //這種風(fēng)格也有不少高手使用的
int n,s;
while(scanf("%d", &n),n>=0){
s = factorial(n);
printf("%d\n", s);
}
return 0;
}
5.
int main(void) //C Primer Plus 上的風(fēng)格
{
int n;
while(scanf("%d", &n),n>=0)
{
int s = factorial(n);
printf("%d\n", s);
}
return 0;
}
我推薦的寫法是第5種。第三種寫法貌似較少見(jiàn),雨中飛燕本人不好作出評(píng)論。
但對(duì)于1,2,4三種寫法,都是有原因的。原因也很簡(jiǎn)單,減少占用的行數(shù)。
特別是紙版書(shū),節(jié)省這點(diǎn)行數(shù)累積起來(lái)可以節(jié)約不少紙張,降低書(shū)的成本。
對(duì)于powerpoint演示,如果不節(jié)約行數(shù),代碼根本沒(méi)辦法顯示完。
但你要注意,它這樣寫可能不是為了告訴你要這樣寫出這樣緊密的代碼。
但同樣的問(wèn)題出現(xiàn)在TC上。DOS窗口標(biāo)準(zhǔn)大小80*25,高度只能顯示25行,
再加上TC菜單和最下面的輸出窗口,你能同時(shí)看到最多20行,要是按第5種風(fēng)格
來(lái)寫代碼,會(huì)看得很辛苦(因?yàn)門C2用不了鼠標(biāo),TC3能用不過(guò)也麻煩),
于是造成N多緊縮型代碼。如果你換成現(xiàn)在新的編輯軟件,根本用不著這樣。
Windows上的集成編輯軟件一頁(yè)下來(lái)就是三四十行,用鼠標(biāo)滾輪滾一下就能上下拉,
一個(gè)代碼塊要是不想看還可以折疊起來(lái),這些特性都是TC所無(wú)法比擬的,
都比TC方便得多。你還有什么理由抱著TC不放呢?還何必寫緊縮型代碼來(lái)為難
自己的那雙眼睛呢?
/----------------------------------------------------------------------/
五。TC圖形庫(kù)
TC圖形庫(kù)這個(gè)可以說(shuō)是TC一大特色,用TC2寫出來(lái)的代碼一般可以一眼看出,因?yàn)門C2
編譯運(yùn)行的時(shí)候,屏幕上原有的東西并不會(huì)清除,所以用TC的人一般會(huì)習(xí)慣在程序開(kāi)頭
寫上clrscr()。當(dāng)然,要是用Win-TC就不會(huì)發(fā)生這種情況。如果使用者本人知道這個(gè)
庫(kù)是TC專有(好比是VC的MFC)的話,這倒問(wèn)題不大。問(wèn)題是如果使用者不知道的話,
屏幕輸出用習(xí)慣了gotoxy,變得理所當(dāng)然地認(rèn)為C就應(yīng)該支持這類函數(shù)的話,
那就糟糕了。論壇上跑來(lái)問(wèn)VC6可不可以輸出圖形,或者問(wèn)有沒(méi)有g(shù)otoxy函數(shù)的人
不是一個(gè)兩個(gè)人的問(wèn)題了。
/----------------------------------------------------------------------/
六。越界檢查
由于在DOS下,DOS系統(tǒng)根本不會(huì)去檢查程序的訪問(wèn)越界問(wèn)題,無(wú)論你要對(duì)內(nèi)存的哪里
進(jìn)行讀寫,都是允許的(只要不把自己程序的代碼區(qū)改寫了就沒(méi)事)。但在Windows下,
尤其是WinXP,內(nèi)存是分塊的,對(duì)只讀塊寫數(shù)據(jù)或者對(duì)不可讀寫塊進(jìn)行讀數(shù)據(jù)都會(huì)引發(fā)
異常,如果程序不能夠處理這個(gè)異常,那么這個(gè)程序就會(huì)被強(qiáng)制關(guān)閉。有了這個(gè)異常
機(jī)制,當(dāng)然會(huì)使你更容易查出程序的錯(cuò)誤。特別地,在VC6的Debug模式下,
堆里未被初始化的內(nèi)存被0xCD字節(jié)模式填充,堆里釋放的內(nèi)存被0xDD字節(jié)模式填充。
于是一但發(fā)生越界的時(shí)候,很容易通過(guò)程序運(yùn)行結(jié)果得知當(dāng)中有錯(cuò),為調(diào)試帶來(lái)了方便。
然而TC則不然,不管你越界了多少,你都收不到任何的警告或者錯(cuò)誤。
而且在多數(shù)情況下,TC下運(yùn)行正常的越界代碼換VC6上就會(huì)結(jié)果出錯(cuò)。
有初學(xué)者以為這是VC6編譯器有問(wèn)題,其實(shí)不然,而是TC給你掩蓋了這個(gè)錯(cuò)誤。
#include <stdio.h>
int main(void)
{
int n;
int num[8];
int sum[8] = {0};
for(n=1;n<=8;++n)num[n] = 1;
for(n=1;n<8;++n)sum[n] = num[n]+num[n+1];
for(n=1;n<8;++n)
printf("%d\n", sum[n]);
getchar();
return 0;
}
這個(gè)程序的目的是給num數(shù)組全部給1,然后sum數(shù)組計(jì)算num相鄰兩個(gè)數(shù)的和。
有的初學(xué)者可能會(huì)說(shuō),這個(gè)程序沒(méi)問(wèn)題,如果他以為int num[8];的下標(biāo)是從1至8的話。
然后,照樣運(yùn)行一下看看。TC的結(jié)果是7個(gè)2,結(jié)果正常。
VC6的結(jié)果是,什么都沒(méi)有顯示,然后你打開(kāi)資源管理器查看一下進(jìn)程,
你就會(huì)發(fā)現(xiàn)這個(gè)程序占用CPU高達(dá)90%以上。你不要以為這是編譯器發(fā)生了問(wèn)題,
原因正是因?yàn)檫@里的越界導(dǎo)致了一個(gè)死循環(huán)!如果是GCC或更高版本的VC編譯器,
把for里的8改成11也會(huì)發(fā)生死循環(huán)。但即使改成11,在TC上運(yùn)行的結(jié)果也非常正常,
TC里完全沒(méi)有任何錯(cuò)誤的征兆。這里不分析這個(gè)死循環(huán)產(chǎn)生的原因,這里只是想告訴你,
你在TC上運(yùn)行結(jié)果無(wú)誤的程序不一定就邏輯上正確了(包括VC和GCC)。特別是TC幾乎
沒(méi)有任何的越界檢查,所以更要求程序員在TC上編寫代碼時(shí)候要十分謹(jǐn)慎和細(xì)心。
但現(xiàn)在問(wèn)題是現(xiàn)在使用TC的大多是初學(xué)者,在沒(méi)有什么經(jīng)驗(yàn)的情況下,很大可能
會(huì)寫出類似這樣的越界訪問(wèn)代碼。這種代碼也許在TC上沒(méi)有問(wèn)題,可是只要換一個(gè)
編譯環(huán)境,問(wèn)題就馬上暴露出來(lái)了。不信就看看下面的經(jīng)典TC錯(cuò)誤代碼吧:
#include <stdio.h>
int main(void)
{
char *pstr;
gets(pstr);
puts(pstr);
getchar();
return 0;
}
(別告訴我說(shuō)你不知道這代碼錯(cuò)在哪里,你不知道錯(cuò)哪里的話那你平時(shí)一定是使用TC的)
/----------------------------------------------------------------------/
七。結(jié)束語(yǔ)
現(xiàn)在再來(lái)回答文章一開(kāi)頭的內(nèi)容:“為什么說(shuō)TC過(guò)時(shí)”?
主要原因其實(shí)不是過(guò)時(shí)不過(guò)時(shí)的問(wèn)題,主要是不適合現(xiàn)在初學(xué)C語(yǔ)言的學(xué)生們使用。
很容易因?yàn)門C過(guò)于寬松的一些語(yǔ)法,或者一些與C99標(biāo)準(zhǔn)不一樣的語(yǔ)法,
讓初學(xué)者養(yǎng)成不良習(xí)慣或者產(chǎn)生特定環(huán)境下的依賴性。這些都是對(duì)學(xué)習(xí)上不利的因素。
所以我一般給別人推薦DevC++或者VC2005就是這個(gè)原因,借助編譯器的強(qiáng)制能力,
迫使你使用較為規(guī)范的方式去寫代碼。新加坡的環(huán)境為什么好?不是因?yàn)楣袼刭|(zhì)高,
是因?yàn)橛蟹梢?guī)定,隨便扔垃圾或者破壞環(huán)境的都會(huì)有嚴(yán)重的法律后果。
歸根究底,不讓你用TC,就是不希望你有不良習(xí)慣,更何況有很多比TC優(yōu)秀的編譯器呢?
何必因?yàn)榻滩幕蛘呖荚囀荰C,你就一定用TC呢?
知識(shí)是屬于自己的,用來(lái)真正充實(shí)你自己的
考試是給別人看的,用來(lái)得到一時(shí)之虛榮的
你希望得到充實(shí),還是一時(shí)之虛榮?