江 南,何炎祥(.湖北工業(yè)大學 計算機學院,湖北 武漢 430068;2. 武漢大學 計算機學院,湖北 武漢 430072;3. 軟件工程國家重點實驗室(武漢大學),湖北 武漢 430072)
?
計算機專業(yè)編程語言類課程教學的思考
江 南1,何炎祥2,3
(1.湖北工業(yè)大學 計算機學院,湖北 武漢 430068;
2. 武漢大學 計算機學院,湖北 武漢 430072;
3. 軟件工程國家重點實驗室(武漢大學),湖北 武漢 430072)
摘 要:從計算機專業(yè)編程語言類課程教學實踐中總結3方面的教學思考,首先討論如何在教學過程中提高學生準確分析程序運行結果的能力,即結合圖示分析運行時數(shù)據(jù)區(qū)以及區(qū)分編譯時和運行時錯誤,其次提出對比不同編程語言關鍵特性的教學手段,最后闡述培養(yǎng)抽象描述和表達問題能力的重要性,并以相應教學案例和取得的效果加以說明。
關鍵詞:編程語言;運行時數(shù)據(jù)區(qū);語言特性對比;抽象描述
程序完成一定的功能,功能的正確性是我們對程序的首要關注面,運行的結果就是設計和編碼所要實現(xiàn)的預期效果。程序的運行結果是確定的,其中,“確定”這個詞表達的意思是:運行結果一定是可以分析出來的,如果運行結果和預期不符,那么一定要找出問題所在。在教學過程中,常常聽到學生說“這個程序的運行結果好象是……似乎是……”,有時候即使他們給出了一個正確的答案,也很難進一步說明答案的由來。因此,教師要在教學過程中對重要語法點設計相關聯(lián)的程序,注重分析程序的運行結果并輔以圖示,而不是一遍遍地羅列和解釋每個語法點,讓學生去記憶。一般來講,計算機總是規(guī)規(guī)矩矩地按照程序編譯后的指令去執(zhí)行,因此,我們提倡的做法是按照語言的語法功能,用人腦代替機器運行程序,用筆和紙模擬機器執(zhí)行的過程。這種方法在學生初學語言時對迅速掌握語法要點非常實用,并且在掌握一門語言后用這種方法分析復雜程序也是很有必要的。
1.1分析運行時數(shù)據(jù)區(qū)
講授編程語言時,強調“一個程序不管有多復雜,它終歸是該程序對所操縱數(shù)據(jù)產(chǎn)生的效果”。程序運行時環(huán)境,特別是數(shù)據(jù)區(qū)是非常值得關注的。作為教學案例,我們先給出一段Java參數(shù)傳遞的代碼,其中程序的輸出以注釋給出。
假定類Person的定義包括一個整型(int)成員變量age、一個構造方法對成員變量進行初始化,兩個成員方法getAge()和setAge(int)分別獲得age的值和對age進行賦值。按照語法解釋,該段代碼的執(zhí)行情況是:運行方法g()時,首先創(chuàng)建了一個Person對象,其成員變量age的值是10,本地變量p指向該對象,第二行輸出10;使用p調用方法f時,按照Java按值傳遞參數(shù)機制,p的值傳遞給f方法的形參person,由于p是一個引用類型變量,它的值是對象的地址,這個地址值拷貝給person,因此person的值也是剛剛創(chuàng)建對象的地址,即person也指向這個Person對象;參數(shù)傳遞完成后,在f方法體中,首先創(chuàng)建了一個Person對象,其成員變量的值是20,person指向這個新創(chuàng)建的對象,因此輸出20;方法f執(zhí)行完畢后,返回到方法g,由于方法g在調用方法f時,沒有對本地變量p產(chǎn)生任何改變,因此,輸出10。
教師僅僅這樣解釋顯得非常沉悶,如果給出它的運行時數(shù)據(jù)區(qū)圖示,會直觀和形象得多。圖1中的(a)~(d)給出了調用方法g前后運行時數(shù)據(jù)區(qū)的變化。
圖1 方法調用的運行時數(shù)據(jù)區(qū)示意圖
在分析實現(xiàn)功能的層面上,模擬Java程序的運行需要關心的是棧楨中各本地變量的取值和堆中對象各成員變量的取值。因此,在分析上述代碼時,按照程序的執(zhí)行依次畫出圖1中的(a)~(d)。從圖1可以直接看出,參數(shù)傳遞完成時,g棧楨中的person變量指向Person對象1;方法f執(zhí)行完成后,person變量指向Person對象2;返回方法g后,f棧楨中的p變量仍然指向Person對象1。
上述運行時數(shù)據(jù)區(qū)的圖示方法貫穿在整個授課過程對程序的分析之中。這種方法適用于常用的命令式語言,如解釋C語言內存泄漏問題時,用圖示法模擬運行時數(shù)據(jù)區(qū)的變化,可以直觀描述被泄漏的內存空間。
實踐表明,這種方法的使用形象化了知識點,加深了學生對語言理解,在授課過程中,我們鼓勵學生使用這種方法來分析程序。從學生的反饋來看,這種方法對準確分析程序的運行結果有較大幫助。
1.2區(qū)分編譯時錯誤和運行時錯誤
編譯器不報錯并不保證運行時沒有錯誤發(fā)生。在教學過程中,為了培養(yǎng)學生準確分析程序運行結果的能力,我們還重視幫助學生建立編譯時錯誤和運行時錯誤的概念,區(qū)分編譯時錯誤和運行時錯誤。為了做到這一點,我們針對不同語言的特點,設計不同的代碼段,解釋它們的編譯結果和運行結果。先以Java語言中的“造型”這一語法點為例。在Java中,造型分向上造型和向下造型,造型機制的語法點總結為:向上造型總是允許的,可以隱式進行;向下造型必須顯式進行。我們可能寫出看似符合這個總結的程序,但是,程序運行可能會報類造型異常的錯誤。假定類B是類A的子類,給出如下代碼段:
在授課過程中,先給出代碼段(1),分別創(chuàng)建了父類A的對象a和子類B的對象b,將子類型引用變量b強制轉換為父類型(由造型操作(A)完成),即向上造型,然后賦給父類型引用變量a,這段代碼既沒有編譯時錯誤也沒有運行時錯誤;由于向上造型總是被允許的,即一個子類對象總是一個父類型,它可以隱式進行,去掉造型操作符(A)后,程序是不受影響的。然后給出代碼段(2),它將一個父類型引用變量a賦給子類型引用變量b,即向下造型,因為無法知道一個父類型引用變量在運行時是否引用到一個子類型對象,向下造型必須顯式進行,因此代碼段(2)報編譯時錯誤。接著給出代碼段(3),顯式造型父類型引用變量a為一個子類型(由造型操作(B)完成),這樣,在編譯時,編譯器認為類型正確,不報錯誤;但是,在運行時進行類型檢查時,由于引用變量a指向的對象是父類型的,將一個子類型變量引用到這個父類型對象時,Java運行時機制報類造型異常的錯誤。為了修正這個錯誤,最后給出代碼段(4),即在向下造型前,將b賦給a,即將父類型的引用變量a指向子類對象,這樣,程序運行時,引用變量a指向的對象是子類型的,將它賦給子類型變量沒有任何問題。
對于C++編程語言,在VC++環(huán)境下,一個典型的編譯時不報錯,但運行時報錯的例子如下:
int *p = (int *) malloc(2*sizeof(int));
free(p);
free(p);
它表示的是,指針變量p在運行時指向了一個整型數(shù)據(jù)空間,然后釋放這個數(shù)據(jù)空間,最后它又再次釋放這個已經(jīng)釋放的空間。許多大型C/ C++程序運行時錯誤是因為這種同一內存空間多次釋放而造成的。需要說明的是,VC++和GCC環(huán)境對這類運行時問題的處理是不同的,給出這個例子是為了說明區(qū)分編譯時錯誤和運行時錯誤對理解程序的必要性。
從“分析運行時數(shù)據(jù)區(qū)”和“區(qū)分編譯時錯誤和運行時錯誤”兩個方面闡述如何在教學過程中提高準確分析程序運行結果的能力。實踐表明,這樣能夠加強學生對知識點的把握,使得這些知識點在學生的大腦有更清晰地認識,并加深學生對編程語言的理解。
不同編程語言在設計和實現(xiàn)上具有差異。C/ C++和Java是我們主要講授的兩種命令式編程語言,從程序形式上看,這兩門語言較為接近,但它們在設計和實現(xiàn)上又具有明顯的不同。在學習第二門編程語言時,如果適當結合已講授的另外一門編程語言,會得到很好的觸類旁通的效果,深化學生對編程語言的理解。
我們以面向對象語言的一個重要特性,多態(tài)為例。首先,設計具有對比性的兩段代碼,分別在VC++和javac下進行編譯:
然后給出程序的分析:C/C++中,虛函數(shù)才會被覆蓋,即子類函數(shù)覆蓋了父類中聲明為virtual的相同函數(shù)。當調用這樣的函數(shù)時,運行時才會確定到底執(zhí)行哪個類中的函數(shù)體,如果調用該函數(shù)的指針變量指向一個父類型,則執(zhí)行父類中對應的函數(shù);如果指向一個子類型,則執(zhí)行子類中對應的函數(shù)。因此,當指針變量p指向a時,由于a的類型是父類型A,所以第1行和第2行分別輸出A-f和A-g;當指針變量p指向b時,對于第3行輸出,雖然b的類型是子類型B,但是函數(shù)f未聲明為虛函數(shù),f函數(shù)的調用在編譯時已經(jīng)由p的類型所確定,為父類型A,因此第3行輸出A-f,對于最后一行輸出,由于g函數(shù)聲明為虛函數(shù),而此時p指向的是一個子類型b,因此輸出B-g。
Java代碼中,Java語言沒有virtual關鍵字,可以視為所有的方法都是虛方法,因此,B類中的方法f和方法g分別覆蓋了A中的方法f和方法g。方法覆蓋時,在運行時由調用方法的引用變量所引用對象的類型來確定到底執(zhí)行哪個類中的方法體。對于第1行和第2行的輸出,父類型的引用變量p指向的是一個父類對象a,則分別調用父類的方法f和方法g,因此輸出A-f和A-g;在將父類型的引用變量指向一個子類對象b后,第3行和第4行的輸出分別是調用類B中相應的方法體,因此輸出B-f和B-g。
最后,引導學生對以上2個程序進行小修改,查看運行結果,思考為什么會是這樣的運行結果,按照課堂上的分析,能否解釋得通?按照學生下次課堂教學的反饋,引導有興趣的學生閱讀相關的編譯器源碼,對編程語言的實現(xiàn)進行粗略地介紹,減少編程語言在學生眼里的神秘感。
實踐表明,語言的類似特性在不同語言中的對比教學是很有效果的。學生會給出一些類似“原來如此”的反饋,可見,這種教學手段能夠澄清學生看來似是而非或者覺得百思不得其解的運行結果,也極大提高了學生對編程語言的學習興趣,并能引導學生對后續(xù)課程,如編譯原理等課程有基本認識。
編程語言類課程的教學不僅要教會學生使用一到兩門編程語言,更要在講授知識的同時,讓學生具有良好的抽象描述和表達問題的能力。
從功用來看,程序是用來解決問題的,為了解決問題首先需要描述和表達問題。它要求設計者站在計算機求解問題的角度,將問題抽象化。因此,在授課過程中教師如果過于關注語言本身的細節(jié),可能學生學到的只是語言結構,這對解決問題是遠遠不夠的,因為對程序設計沒有宏觀把握,不能寫出好的軟件。從這個意義上講,編程語言類的教學應該在教學過程中,逐步引導學生建立一種實用的軟件工程思想,這是非常重要的。
為了培養(yǎng)抽象表達問題的能力,教師在講授程序的主要語法點之后,可以適當設計一些小案例作為問題提出,學生從已學的知識點中,將問題抽象化,然后開始編碼實現(xiàn)、測試、調試并最終解決問題。其結果是既提高了學生對語法點的掌握程度,又培養(yǎng)了學生抽象描述和表達問題、解決問題的能力。
最后要強調的是,為了培養(yǎng)學生抽象表達問題的能力,掌握一門語言并具有扎實的編程技能是非常必要的。任何一門語言的熟練掌握和精通都需要花費努力和時間,不是短期可以實現(xiàn)的,因此在授課過程中我們一直強調并鼓勵踏實的學習作風,盡可能激發(fā)學生的學習興趣,使得他們在課程結束之后還愿意在編程語言應用能力的學習提高上投入精力。
具備一定的編程技能和具有抽象描述問題的能力是編程語言類課程需要達到的教學目標,本文基于計算機專業(yè)編程語言類課程的教學實踐,對教學方法和思路提出思考。未來將繼續(xù)結合教學實踐,提高編程語言類課程的教學質量,并努力縮小與國外計算機編程類課程教學的差距。
參考文獻:
[1] 曹西征, 孫志勇. VC++教學中實例驅動法和模糊評價研究[J]. 計算機教育, 2015(21): 82-84.
[2] Shewchun J . Data structures and Java programming [EB/OL]. [2015-11-30]. http://www.cs.berkeley.edu/~jrs/61b/.
[3] Sierra K , Bates B. Head fi rst Java[M]. 2 ed . Sebastopol: O'Reilly Media Inc, 2005.
[4] Sahami M. Programming methodology [EB/OL]. [2015-11-30]. http://web.stanford.edu/class/cs106a/.
(見習編輯:趙盼;編輯:宋文婷)
第一作者簡介:江南,女,講師,研究方向為編程語言、可信編譯,nanjiang@whu.edu.cn。
基金項目:湖北工業(yè)大學教研項目“ RESTFUL Web服務環(huán)境下自主學習云系統(tǒng)的研究與實現(xiàn)”(2012015)。
中圖分類號:G642
文章編號:1672-5913(2016)04-0156-04