盧 萍,祝建華
(華中科技大學 計算機科學與技術學院,湖北 武漢 430074)
“C語言程序設計”是高校計算機及相關專業的必修基礎課程,是數據結構、編譯原理、算法分析、操作系統等課程的先導課程。該課程以C語言為工具,以培養學生計算機思維能力和編程解決實際問題的能力為目標。課程涉及的內容多、知識使用靈活、實際問題千變萬化,同一個問題可有多種實現方法,具有很強的實踐性和創造性。
工程教育認證標準對課程的實踐環節提出了更高要求[1-4]。針對C語言功能強且靈活的特點,為了使學生準確掌握每一個教學重難點知識,提出“著眼能力、精準訓練”的實踐模式[5]。在設計實驗任務時,要依據教學內容,梳理知識點,細化目標,明確要求。例如,要求用位運算實現數據壓縮,旨在掌握位運算的應用,為用C語言編寫系統軟件打下基礎。
實驗目標的達成有賴于有效的監督和評價機制,傳統上往往采用加大檢查力度的方法。但由于學生在學習能力、自覺性、主動性等方面存在差異,傳統的人工檢查方式不僅增加了教師的負擔,檢查結果不能及時反饋給學生,而且隨著作業量的增大、學生人數的增多,很難檢查到每一個學生,沒有查到的學生難免出現應付交差現象,影響了教學質量的提高[6-8]。
如何創建有效的督促和激勵機制,推動每個學生一步一個腳印地完成實驗,達到精準訓練、全面掌握知識點的目標,是C語言實踐教學中需著重考慮和解決的問題。EduCoder實踐教學平臺的出現,給程序類課程實踐教學改革帶來了契機[9-11]。平臺提供的在線評測代碼、結果及時反饋、自動統計成績和分析學生能力值等功能,以及類似于游戲闖關挑戰的實驗形式,極大地增強了與學生的互動性,提高了學生的學習興趣和參與度。平臺支持的自主設計評測腳本機制,也給教師提供了很大的自由度和發揮空間。教師可以按需設置實驗任務,實現評測的智能化和精準化,推動學生全面掌握教學重點和難點知識,并使他們變被動學習為主動學習。
從2019年秋季開始,我們借助EduCoder平臺,實施了以學生為中心、以能力培養為導向、以精準訓練為核心的C語言實踐模式,設計了與理論課教學同步的10個實驗作業及其評測腳本,每個實驗包括3~8個題目,取得了很好的效果。
本文將分析 EduCoder平臺的評測機制,并結合典型實例闡述如何根據訓練目標自行設計評測腳本,從而使學生能夠有針對性地進行知識點練習,促進他們對知識點的精準掌握,為綜合應用所學知識解決實際工程問題打下良好的語言基礎。
EduCoder 是一個面向計算機專業方向開展教學、實驗和實訓活動的工程教育平臺。該平臺將知識學習與動手實踐相結合,支持教師按需自主創建實踐課程,并根據課程內容設計并發布實驗任務。學生通過登錄平臺,可以隨時隨地在線編寫代碼完成實驗任務。平臺可自動編譯、執行和評測代碼,并立即反饋評測結果,學生可根據錯誤信息提示修改代碼并繼續測試。在整個實驗過程中,平臺會實時記錄每個學生的活動軌跡,如提交評測的次數、每次評測存在的問題、實驗時間、是否查看參考答案、最近通過的代碼、最終成績等,并形成詳細報告和統計圖表,以便教師充分掌握學生的實驗進度、存在問題和學習效果等,并據此分析教學難點、調整教學策略、改進課堂教學方法等,從而進一步提高教學質量。
EduCoder平臺采用的評測方法是測試用例法,即用數據集測試程序的正確性。測試用數據集應完備,應能全方位檢測算法的正確性,應覆蓋程序執行的各種情況。比如閏年的判斷,有普通的非閏年,如2019;還有100的倍數的非閏年,如1900;有普通閏年,如2008;還有世紀閏年,如2000。如果考慮不全面,則會使錯誤的程序也能夠通過。平臺將會用每組測試用例執行程序,并將程序的實際輸出結果與正確輸出結果進行對比,全部測試用例結果正確的將獲得通關,否則會將失敗的測試用例反饋給學生。
EduCoder平臺用來測試的文件有學員任務文件和評測執行文件兩類。設置任務時,應為每一個題目建立一個文件夾,如 src/step1、src/step2等,每個題目的文件放在對應的文件夾下。學員任務文件是學生編寫代碼用的,里面的內容(可以為空)將直接顯示在代碼區域,需要學生在其中編寫代碼。評測執行文件是需要執行的平臺腳本,根據執行結果判斷程序的正確性,學生對該文件只能查看,不能修改。評測執行文件也可以是學員任務文件,也就是直接運行學生寫的程序,或者是由教師設計的用來測試學生代碼的腳本文件。通過自行設計評測文件可以精準地達到訓練某個知識點的教學目標。
表達式是C語言一個非常重要的程序元素,它貫穿在C語言教學的各個章節,各種流程控制語句的執行條件都要用表達式來描述,表達式還可以單獨構成表達式語句。C語言提供了34種運算符,既能實現其他高級語言的運算,也能實現匯編語言的底層位運算,再加上一些特有的運算符,使C語言的運算能力非常強,表達式類型多樣化。靈活使用各種運算符寫出實現特定功能的表達式是一項重要的技能。
在講授運算符和表達式后,就要訓練學生寫C語言表達式的能力。根據平臺的評測架構,可以設計兩種方式,第一種方式是學員任務文件是一個完整程序框架,學生在其中完成代碼填空(填寫表達式),該文件作為評測執行文件;第二種方式是學員任務文件用.h,文件內容為空或者包含一些注釋(說明相關變量的類型),只需要學生填寫一個表達式,而評測執行文件是針對該實驗目標設計的一個完整程序,可以使用#include把學員任務文件包含進來。相比較而言,后者效果更好,訓練更精準。
【示例1】任務描述:寫一個表達式,求三個整數a、b和c中的最大值。
訓練目標是使學生掌握條件運算符(?:)的使用,這是一個三目運算符,可以用來代替if語句實現某些分支運算。
將學員任務文件命名為 step1_stu.h,在 src/step1文件夾下,里面沒有任何語句,只有一些注釋,學生需填寫一個條件表達式,通過設計評測腳本使得學生只能用條件表達式。將評測執行文件命名為step1_main.c,也在src/step1文件夾下,它是針對該訓練目標而設計的腳本文件,里面包含變量聲明、輸入3個整數、輸出最大數,中間用#include "step1_stu.h"把學生寫的表達式嵌入進來,賦值給變量 x,其后加分號構成一條賦值表達式語句,該文件內容如圖1所示。

圖1 示例1的評測執行文件內容
學生是不能修改評測腳本的,該設計使得學生在學員任務文件中只能寫一個表達式,因而不能使用 if語句,如果使用if語句,系統則會報語法錯,這樣就可以非常精準地達到訓練寫條件表達式的教學目標。
在C語言中實現模塊化程序設計的手段是編寫函數,即把每一個模塊設計成一個函數,完成總任務的程序是由一個主函數和若干其他函數組成的。主函數比較簡單,起著任務調度的總控作用,其他函數將最終直接或間接被主函數調用,以解決總任務。這種模塊化的程序結構增強了程序的可讀性、可維護性和可擴充性。學會設計和編寫函數非常重要,在講授完函數后,需要訓練學生用函數去實現特定的功能。
【示例 2】任務描述:定義函數 digit(n,k),求n中從右端開始的第k個數字的值(k從1開始),如果k超過了n的位數,則函數返回–1;否則返回n中第k個數字。例如:digit(345876,4)=5,digit(345,4)= –1。
訓練目標是掌握函數的定義,根據要求編寫自定義函數,包括return語句的使用。
將學員任務文件命名為 step2_stu.h,在 src/step2文件夾下,里面只有函數的頭部注釋,學生需按照規定的接口編碼實現函數功能。該文件內容如圖2所示。

圖2 示例2的學員任務文件內容
評測執行文件命名為step2_main.c,也在src/step2文件夾下,里面包含變量聲明、函數調用和調用前后的數據輸入輸出,在 mian函數的前面用#include"step2_stu.h"嵌入學生定義的函數,該文件內容如圖3所示。

圖3 示例2的評測執行文件內容
這樣,學生在任務文件中就只能按要求編寫函數,從而達到訓練目標。
EduCoder的測評原理是運行評測執行文件,在程序執行過程中讀入預設的測試數據,通過將程序輸出結果與正確結果進行對比來決定是否通關。
在針對授課知識點設計實驗任務過程中,發現帶參main函數比較特殊,平臺無法直接測試其功能,學生即使定義了帶參main函數,也被當作無參main函數執行。為此,提出了一種能夠在 EduCoder平臺下間接測試帶參main函數的方法,訓練學生編寫和使用帶參數的main函數,并給予自動評測。
表示和處理 main函數的參數是指針數組和二級指針的一個重要應用。有些操作系統,包括UNIX和MS-DOS,需要用戶在命令行界面輸入參數來啟動一個程序的執行,這些參數被傳遞給程序,供程序分析處理。命令行界面需要用戶記憶操作命令,不如圖形界面使用方便,但能節省計算機系統資源,在熟記命令的前提下,使用命令行界面的操作速度更快。所以,圖形界面的操作系統不僅保留了命令行界面,而且還加強了操作命令的功能[12]。帶參 main函數的定義形式為:

main函數具有兩個形參,第1個參數argc代表命令行中參數(即字符串)的個數,第2個參數是字符指針數組 argv,argv[i]指向命令行中第 i個字符串。假定有一個名為copy的程序,在Windows下運行該程序的命令行如下:

該命令行有3個參數,第1個參數copy是可執行程序名,其后的abc.txt和def.txt是程序執行所需的參數。該命令行啟動copy程序后,會將3傳給argc,argv[0]指向串“copy”,argv[1]指向串“abc.txt”,argv[2]指向串“def.txt”,然后執行程序,實現將文件 abc.txt的內容復制到文件def.txt中。
可見,帶參main函數被系統調用時,需要在命令行中輸入數據來啟動程序的執行,而這些數據被傳遞給main函數。命令行中輸入不同的參數,程序將執行不同的功能。在 EduCoder下設置實驗任務時,不能設置命令行的參數,只能設置測試集數據,測試數據是main函數執行后由輸入函數(getchar、scanf等)讀取的,而命令行的參數是操作系統讀取并傳給main函數的,兩種獲取數據的方式截然不同。
根據帶參main函數的特點,提出的這種通過設計評測腳本的間接測試方法,有助于學生精準掌握命令行參數的作用、參數的傳遞機制以及帶參main函數的編寫。
根據命令行參數傳遞原理,結合 EduCoder評測機制,提出如下在EduCoder下對帶參main函數功能的測試策略:①將命令行參數作為測試數據;②學生編寫等效于本地帶參main函數的main0函數,即在本地環境調試時是main,提交平臺測試時將 main改為main0即可;③設計一個main函數作為測試腳本,模擬操作系統讀取和存儲命令行參數,對main0進行測試,命令行參數就是設置的測試數據。
main函數需要模擬命令行參數的整個處理過程,包括從測試數據(即命令行參數)中讀取字符、識別出參數字符串、統計字符串個數、動態分配內存存儲字符串、記錄字符串的首地址等,再用獲取的參數個數和字符串數組作參數,調用main0函數。main中聲明變量n保存字符串個數,字符指針數組inputStr保存各字符串的首地址,將n傳給main0的形參argc,inputStr傳給argv,調用main0函數(即帶參main函數),從而啟動main0的執行(相當于main函數被系統調用)。
main0函數等價于帶參main函數,學生要在自己的編程環境中寫帶參main函數,即將main函數代碼粘貼到 EduCoder編輯器,當測試腳本對其進行自動測試時,把main函數的名字改為main0即可。因此,學生也可以先在本地環境實現帶參 main函數的功能并調試通過后,再將main修改為main0,提交平臺進行自動測試。
【示例3】任務描述:編寫一個程序,名為strcat,用命令行參數實現至少兩個字符串的連接,命令行為:
strcat str1 str2 str3 ...
其中,strcat是命令名,即可執行程序名,str1、str2、str3、 ...是被連接的字符串,每一個字符串的長度不超過50,規定連接順序為右邊的串依次連接到左邊串的末尾。例如,
命令行輸入:strcat abc def gh
連接之后形成新串并輸出:abcdefgh
訓練目標是掌握帶參函數的定義和命令行參數的傳遞,并初步掌握動態存儲分配的概念及其應用,為后續結構指針的應用打下基礎。
編程要求:學生編寫帶參main0函數(若在本地調試則為main函數)來連接命令行中的多個字符串,連接之后的新串無冗余地存儲到用 malloc動態分配的空間,并將該空間首地址賦值給外部指針p,指針p在評測腳本中定義,連接后的新串也在腳本文件中輸出。
評測執行文件命名為 step3_main.c,文件內容見圖 4和圖5。

圖4 示例3的評測執行文件內容之一
圖5為Begin ...End之間的代碼模擬命令行參數的讀入和參數串的存儲。

圖5 示例3的評測執行文件內容之二
因此,學生只有深入理解帶參main函數這一教學重點和難點知識,并學會使用動態存儲分配函數,代碼才能通過測試。測試數據若為:strcat abc def gh,則輸出結果應為:abcdefgh。
基于 EduCoder的實踐模式經過 2019年秋季和2020年春季兩個學期的試行,取得了較好的效果,受到學生的歡迎。考試結果表明,經過知識點的精準訓練,學生對一些難點知識,如位運算、二級指針、帶參main函數、動態分配等掌握情況良好,成績得到明顯提高,達到了預期目標。該項改革對教師來說,可以使他們從繁重的督促檢查中解脫出來,能更好地研究教學教法和答疑解惑,提高教學質量和教學效果;對學生來說,既可促進他們全面掌握教學重點和難點,又可培養獨立思考和自主學習的習慣,提高分析和解決問題的能力。