基于EduCoder平台的C语言实验自动评测设计
2021-03-04祝建华
卢 萍,祝建华
(华中科技大学 计算机科学与技术学院,湖北 武汉 430074)
1 借助 EduCoder平台进行 C语言实验评测的必要性
“C语言程序设计”是高校计算机及相关专业的必修基础课程,是数据结构、编译原理、算法分析、操作系统等课程的先导课程。该课程以C语言为工具,以培养学生计算机思维能力和编程解决实际问题的能力为目标。课程涉及的内容多、知识使用灵活、实际问题千变万化,同一个问题可有多种实现方法,具有很强的实践性和创造性。
工程教育认证标准对课程的实践环节提出了更高要求[1-4]。针对C语言功能强且灵活的特点,为了使学生准确掌握每一个教学重难点知识,提出“着眼能力、精准训练”的实践模式[5]。在设计实验任务时,要依据教学内容,梳理知识点,细化目标,明确要求。例如,要求用位运算实现数据压缩,旨在掌握位运算的应用,为用C语言编写系统软件打下基础。
实验目标的达成有赖于有效的监督和评价机制,传统上往往采用加大检查力度的方法。但由于学生在学习能力、自觉性、主动性等方面存在差异,传统的人工检查方式不仅增加了教师的负担,检查结果不能及时反馈给学生,而且随着作业量的增大、学生人数的增多,很难检查到每一个学生,没有查到的学生难免出现应付交差现象,影响了教学质量的提高[6-8]。
如何创建有效的督促和激励机制,推动每个学生一步一个脚印地完成实验,达到精准训练、全面掌握知识点的目标,是C语言实践教学中需着重考虑和解决的问题。EduCoder实践教学平台的出现,给程序类课程实践教学改革带来了契机[9-11]。平台提供的在线评测代码、结果及时反馈、自动统计成绩和分析学生能力值等功能,以及类似于游戏闯关挑战的实验形式,极大地增强了与学生的互动性,提高了学生的学习兴趣和参与度。平台支持的自主设计评测脚本机制,也给教师提供了很大的自由度和发挥空间。教师可以按需设置实验任务,实现评测的智能化和精准化,推动学生全面掌握教学重点和难点知识,并使他们变被动学习为主动学习。
从2019年秋季开始,我们借助EduCoder平台,实施了以学生为中心、以能力培养为导向、以精准训练为核心的C语言实践模式,设计了与理论课教学同步的10个实验作业及其评测脚本,每个实验包括3~8个题目,取得了很好的效果。
本文将分析 EduCoder平台的评测机制,并结合典型实例阐述如何根据训练目标自行设计评测脚本,从而使学生能够有针对性地进行知识点练习,促进他们对知识点的精准掌握,为综合应用所学知识解决实际工程问题打下良好的语言基础。
2 EduCoder平台的评测机制
EduCoder 是一个面向计算机专业方向开展教学、实验和实训活动的工程教育平台。该平台将知识学习与动手实践相结合,支持教师按需自主创建实践课程,并根据课程内容设计并发布实验任务。学生通过登录平台,可以随时随地在线编写代码完成实验任务。平台可自动编译、执行和评测代码,并立即反馈评测结果,学生可根据错误信息提示修改代码并继续测试。在整个实验过程中,平台会实时记录每个学生的活动轨迹,如提交评测的次数、每次评测存在的问题、实验时间、是否查看参考答案、最近通过的代码、最终成绩等,并形成详细报告和统计图表,以便教师充分掌握学生的实验进度、存在问题和学习效果等,并据此分析教学难点、调整教学策略、改进课堂教学方法等,从而进一步提高教学质量。
EduCoder平台采用的评测方法是测试用例法,即用数据集测试程序的正确性。测试用数据集应完备,应能全方位检测算法的正确性,应覆盖程序执行的各种情况。比如闰年的判断,有普通的非闰年,如2019;还有100的倍数的非闰年,如1900;有普通闰年,如2008;还有世纪闰年,如2000。如果考虑不全面,则会使错误的程序也能够通过。平台将会用每组测试用例执行程序,并将程序的实际输出结果与正确输出结果进行对比,全部测试用例结果正确的将获得通关,否则会将失败的测试用例反馈给学生。
EduCoder平台用来测试的文件有学员任务文件和评测执行文件两类。设置任务时,应为每一个题目建立一个文件夹,如 src/step1、src/step2等,每个题目的文件放在对应的文件夹下。学员任务文件是学生编写代码用的,里面的内容(可以为空)将直接显示在代码区域,需要学生在其中编写代码。评测执行文件是需要执行的平台脚本,根据执行结果判断程序的正确性,学生对该文件只能查看,不能修改。评测执行文件也可以是学员任务文件,也就是直接运行学生写的程序,或者是由教师设计的用来测试学生代码的脚本文件。通过自行设计评测文件可以精准地达到训练某个知识点的教学目标。
3 表达式的自动评测
表达式是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语句,系统则会报语法错,这样就可以非常精准地达到训练写条件表达式的教学目标。
4 自定义函数的自动评测
在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的测评原理是运行评测执行文件,在程序执行过程中读入预设的测试数据,通过将程序输出结果与正确结果进行对比来决定是否通关。
5 带参main函数的自动评测
在针对授课知识点设计实验任务过程中,发现带参main函数比较特殊,平台无法直接测试其功能,学生即使定义了带参main函数,也被当作无参main函数执行。为此,提出了一种能够在 EduCoder平台下间接测试带参main函数的方法,训练学生编写和使用带参数的main函数,并给予自动评测。
5.1 带参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函数的编写。
5.2 评测策略及脚本的设计
根据命令行参数传递原理,结合 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。
6 结语
基于 EduCoder的实践模式经过 2019年秋季和2020年春季两个学期的试行,取得了较好的效果,受到学生的欢迎。考试结果表明,经过知识点的精准训练,学生对一些难点知识,如位运算、二级指针、带参main函数、动态分配等掌握情况良好,成绩得到明显提高,达到了预期目标。该项改革对教师来说,可以使他们从繁重的督促检查中解脱出来,能更好地研究教学教法和答疑解惑,提高教学质量和教学效果;对学生来说,既可促进他们全面掌握教学重点和难点,又可培养独立思考和自主学习的习惯,提高分析和解决问题的能力。