楊存?zhèn)?,汪維清
(西南大學(xué) 信息管理系,重慶 402460)
計(jì)算機(jī)相關(guān)專(zhuān)業(yè)學(xué)生的第一門(mén)編程語(yǔ)言往往是C語(yǔ)言,作為一門(mén)高級(jí)語(yǔ)言,有一定的抽象性。未接觸過(guò)計(jì)算機(jī)科學(xué)的初學(xué)者,往往會(huì)因?yàn)楦鞣N原因形成一些理念誤區(qū),學(xué)習(xí)理念出錯(cuò)會(huì)導(dǎo)致事倍功半,使初學(xué)者喪失學(xué)習(xí)興趣。
C語(yǔ)言具有一定的抽象性,初學(xué)者在學(xué)習(xí)C語(yǔ)言大多無(wú)編程經(jīng)驗(yàn),C語(yǔ)言教材往往會(huì)較為抽象地介紹C語(yǔ)言,而不具體講解涉及的底層原理,因?yàn)檫@更有助于初學(xué)者編程入門(mén)。
例一:以一個(gè)C語(yǔ)言版本的以遞歸方法尋找兩個(gè)正整數(shù)最大公約數(shù)的程序?yàn)槔齕1]:
數(shù)組,函數(shù),判斷語(yǔ)句等基本語(yǔ)法在常見(jiàn)的編程語(yǔ)言中都有,相對(duì)不那么“底層”的Python,C#,Java等都可實(shí)現(xiàn)這個(gè)算法,與編程語(yǔ)言的種類(lèi)關(guān)系較小。
很多C語(yǔ)言的資料講到,arr是一個(gè)指針,它保存內(nèi)存的一個(gè)地址,通過(guò)此地址可訪(fǎng)問(wèn)內(nèi)存中一個(gè)int型數(shù)據(jù),誤操作指針會(huì)導(dǎo)致程序錯(cuò)誤甚至系統(tǒng)崩潰[2]。初學(xué)者往往理解為arr保存的是物理地址,雖不影響程序?qū)崿F(xiàn),但并不準(zhǔn)確。
以32位的使用NT內(nèi)核的Windows為例,在Windows中,每個(gè)進(jìn)程都有自己的虛擬地址空間(0x00000000~0xFFFFFFFF),指針arr保存了虛擬地址空間的地址,而非物理內(nèi)存地址。指針arr所指向的數(shù)據(jù)可能存儲(chǔ)在物理內(nèi)存中,可能在硬盤(pán)上(由于Windows虛擬內(nèi)存技術(shù)),也可能在其他地方[3]。
誤操作指針可以導(dǎo)致系統(tǒng)崩潰或硬件損壞,但是初學(xué)者編寫(xiě)的此類(lèi)簡(jiǎn)單控制臺(tái)程序很難導(dǎo)致系統(tǒng)崩潰或硬件損壞,相對(duì)來(lái)說(shuō),編寫(xiě)Windows內(nèi)核模式驅(qū)動(dòng)程序,單片機(jī)控制程序這樣的偏底層的程序才易因指針問(wèn)題出現(xiàn)嚴(yán)重錯(cuò)誤。操作系統(tǒng)中進(jìn)程之間相對(duì)獨(dú)立,在Windows操作系統(tǒng)中,一個(gè)進(jìn)程若需要訪(fǎng)問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù),可以調(diào)用Write Process Memory函數(shù)、使用DLL注入技術(shù)、創(chuàng)建內(nèi)存文件映射對(duì)象共享數(shù)據(jù)等,而一個(gè)簡(jiǎn)單的指針問(wèn)題很難辦到。
以上涉及到計(jì)算機(jī)組成原理,操作系統(tǒng)原理中的知識(shí),只學(xué)習(xí)C語(yǔ)言本身,即使屏蔽掉這些知識(shí),也可以做到寫(xiě)出有一定復(fù)雜度的程序。初學(xué)者較難理解的概念,如函數(shù)指針,共用體,可以用抽象的方式理解,學(xué)習(xí)計(jì)算機(jī)科學(xué)可以是從頂層向底層和從底層向頂層相結(jié)合的。
例二:直接調(diào)用Windows API編寫(xiě)窗體程序的代碼片段
對(duì)于初學(xué)者來(lái)說(shuō),宏定義的用法,函數(shù)的編寫(xiě)方法,switch語(yǔ)句的使用等并不困難,上述代碼沒(méi)有涉及不常見(jiàn)的語(yǔ)法,但初學(xué)者并不容易理解,Windows API編寫(xiě)窗體程序涉及到很多Windows操作系統(tǒng)的知識(shí)。
由以上兩例可以看出,僅憑C語(yǔ)言的學(xué)習(xí),不能理解計(jì)算機(jī)的底層原理。作為一種高級(jí)語(yǔ)言,能夠提供的抽象程度可以允許一定范圍內(nèi)忽略硬件及操作系統(tǒng)之類(lèi)的細(xì)節(jié)而編寫(xiě)程序,所以對(duì)于初學(xué)者來(lái)說(shuō)不管學(xué)習(xí)Python,Java,C#還是C語(yǔ)言,都不會(huì)直接了解底層的原理,C語(yǔ)言只是相對(duì)來(lái)說(shuō)能更好的引申出這些知識(shí)而已。
源代碼應(yīng)是方便程序員閱讀和修改的,越復(fù)雜,通常越難以維護(hù),更容易出錯(cuò)。在不影響程序的最終實(shí)現(xiàn)效果(比如不用位運(yùn)算就會(huì)嚴(yán)重拖慢運(yùn)行速度)的前提下,應(yīng)該使代碼更通俗易懂。但是,初學(xué)者往往會(huì)“炫耀”技巧,使得代碼過(guò)于復(fù)雜、艱澀。
例三:交換兩個(gè)變量的值
不考慮int的表示范圍以上代碼是沒(méi)有問(wèn)題的,但若*x+*y?[INT_MIN,INT_MAX]會(huì)溢出,這個(gè)函數(shù)便不能交換兩變量的值。而更易懂的寫(xiě)法應(yīng)該引入第三個(gè)變量進(jìn)行交換。
例四:“HelloWorld!”轉(zhuǎn)化成大寫(xiě)輸出
這個(gè)程序的目的是將一串字符轉(zhuǎn)化成大寫(xiě)輸出,在禁用編譯器優(yōu)化的情況下,可能會(huì)調(diào)用很多次strlen函數(shù)。而且并非需要每次都調(diào)用printf函數(shù),可將其存入一個(gè)字符數(shù)組后一次性輸出。較合理的寫(xiě)法如下:
以下將C語(yǔ)言的庫(kù)函數(shù),語(yǔ)法等,統(tǒng)一稱(chēng)作C語(yǔ)言的特性。C語(yǔ)言教材的第一示例程序通常都是一個(gè)簡(jiǎn)單的”Hello World”,然后逐漸介紹新的特性,并要求讀者在程序中使用,會(huì)讓部分讀者有“學(xué)到新特性就要盡可能在程序中使用”的習(xí)慣,使用常用的,被大多數(shù)編譯器支持的特性是正常的,而不常用的,只被很少編譯器支持的特性,如_Generic關(guān)鍵字,_Noreturn函數(shù)標(biāo)記,_Atomic類(lèi)型修飾符和頭文件
如在判斷正整數(shù)的奇偶性,判斷整數(shù)的符號(hào)是否相同時(shí),不應(yīng)總是使用位運(yùn)算[5];有兩個(gè)有關(guān)聯(lián)的數(shù)據(jù)應(yīng)該使用結(jié)構(gòu)體而不是利用C99標(biāo)準(zhǔn)中定義的復(fù)數(shù)的實(shí)部和虛部進(jìn)行表示;在不需要特別注意內(nèi)存占用或沒(méi)有其他特殊需求時(shí)不使用“位域”等。
例五:C語(yǔ)言源代碼
頭文件
例六:求出a,b的值:
使用Visual C++6.0編譯,a,b分別為30和37,在更老的Turbo C 3.0中,a,b分別為30和39。這一歧義與序列點(diǎn)(sequence point)有關(guān)?!盿=(i++)+(i++)+(i++);”這樣的表達(dá)式?jīng)]有任何實(shí)際意義,是錯(cuò)誤的[7]。
除此之外,不確定行為(unspecified behavior),實(shí)現(xiàn)定義行為 (implementation-defined behavior)等也可能給程序造成意外的結(jié)果。
在PC、智能手機(jī)等設(shè)備上,接觸最多的是圖形化的界面,普通用戶(hù)很少使用命令行界面,初學(xué)者總想早一些能編寫(xiě)出來(lái)他們所熟悉的GUI程序,但編寫(xiě)程序,學(xué)習(xí)計(jì)算機(jī)科學(xué),不等于編寫(xiě)GUI程序。一些初學(xué)者學(xué)習(xí)了C語(yǔ)言以及計(jì)算機(jī)其他課程之后依然只能寫(xiě)出“黑框框”程序,十分焦躁。并非編寫(xiě)GUI程序就一定有技術(shù)含量,而編寫(xiě)算法程序,學(xué)習(xí)計(jì)算機(jī)網(wǎng)絡(luò)原理,理解操作系統(tǒng)原理這些與GUI關(guān)系不大的就沒(méi)有技術(shù)含量。編寫(xiě)GUI程序有各種成熟的解決方案,如Windows平臺(tái)上的WPF技術(shù)、Java的JavaFX技術(shù)、Python的Tkinter模塊等,使用這些技術(shù)編寫(xiě)簡(jiǎn)單的GUI程序是十分容易的,而計(jì)算機(jī)相關(guān)專(zhuān)業(yè)的學(xué)生應(yīng)該將精力放在計(jì)算機(jī)組成原理、計(jì)算機(jī)網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)、操作系統(tǒng)、編譯原理等核心內(nèi)容上,浮于表面的學(xué)習(xí)很難對(duì)整個(gè)計(jì)算機(jī)科學(xué)有清晰的認(rèn)知,更難在計(jì)算機(jī)行業(yè)有所發(fā)展。
(1)對(duì)于C語(yǔ)言初學(xué)者來(lái)說(shuō),寫(xiě)出各種不良或者錯(cuò)誤的代碼是很正常的,但是如果學(xué)習(xí)理念有誤區(qū),就應(yīng)當(dāng)及時(shí)糾正。有些初學(xué)者在反復(fù)糾結(jié)“a+=a-=a*a;”這種錯(cuò)誤的,意義不明的代碼,有些初學(xué)者迷戀各種似是而非的技巧,有些初學(xué)者想“學(xué)完”C語(yǔ)言(指的是,掌握所有特性)……這些都是不正確的理念,無(wú)助于培養(yǎng)計(jì)算機(jī)科學(xué)的思維習(xí)慣。
(2)C語(yǔ)言可以認(rèn)為簡(jiǎn)單,因?yàn)檎Z(yǔ)法簡(jiǎn)單,沒(méi)有像C++那么多的關(guān)鍵字和語(yǔ)法;但C語(yǔ)言也很難,語(yǔ)法簡(jiǎn)單,一些語(yǔ)義很難理解,而理解這些語(yǔ)義,不是靠背語(yǔ)法、程序代碼可以解決的。
(3)學(xué)習(xí)計(jì)算機(jī)科學(xué)需要正反饋,可以自頂層向底層和從底層向頂層相結(jié)合的方式學(xué)習(xí),而不應(yīng)該總是線(xiàn)性的,從“Hello World”到學(xué)習(xí)聯(lián)合體,函數(shù)指針這種高級(jí)特性,應(yīng)逐漸的加入操作系統(tǒng)、計(jì)算機(jī)組成原理等其他知識(shí),一步步加深一些語(yǔ)言特性的理解,同時(shí)也促進(jìn)其他理論的學(xué)習(xí)。