突出案例教学和宏观概念的函数教学方法探讨
2019-12-16陈圣磊刘林源王红霞
陈圣磊 刘林源 王红霞
摘要:函数是c语言程序设计中重要的教学内容,在实际教学中发现学生存在囿于细节知识、缺乏宏观视野的问题。提出了突出案例教学和宏观概念的函数教学方法,以手机案例引入函数概念并讲解函数定义与调用的区别,强调建立函数定义与调用关系、参数传递方向这样一些宏观概念,提出了理解函数接口和局部变量概念的新视角,在教学实践中取得了良好的效果。
关键词:程序设计;c语言;函数;案例教学;模块化设计
中图分类号:G642
文章编号:1009-3044(2019)29-0163-04
函数是C语言程序设计中非常重要的概念,是进行系统功能抽象从而实现模块化程序设计的基础。要求学生掌握函数的定义与调用方法、函数的参数与返回值、局部变量与全局变量等基础概念,但是在实际教学中发现有些同学在学完函数后,仍然无法区分函数定义与函数调用,存在对函数的基本概念理解不够透彻的问题,也就导致在实际编程中无法灵活运用函数。
已有不少学者探讨了函数的教学方法,王桂平等从数学函数的使用人手,因势利导地引人函数的概念,方红琴等总结了函数教学的三个难点,并且从引入环节下功夫,起到事半功倍的效果,周百顺讨论了C语言中函数分解与定义的原则。这些研究大都强调了在函数引入中案例的重要性,但是仍然未能解决对函数基本概念理解不清的问题。本文提出突出案例教学和宏观概念的函数教学方法,在引入函数概念和讲解函数定义与调用区别两个环节采用适当的案例辅助学生理解,并且强调建立函数的宏观概念,包括函数要先定义后调用、参数从主调函数传递到被调函数等。这里的宏观概念是相对于具体的语法细节来说的。另外,提出了理解函数基本的概念的两个新视角,即从黑箱角度看待函数的输入与输出,从函数的独立性角度理解局部变量。在教学实践中,突出案例教学,强调宏观概念,采用新颖的视角理解函数中的基本概念,起到了良好的效果。
1為什么需要函数
C语言课程在函数之前已经讲解了变量与数据类型、流程控制语句以及数组的概念,同学已经能够完成计算闰年、迭代求方程的根、对成绩排序之类的任务,那么为什么还要讲解函数这个概念呢?
这个问题至关重要,如果学生能理解为什么需要使用函数,那就很容易理解后面讲解的具体语法格式,否则,学生会陷入具体的语法问题中,不知道自己在干什么,为什么这么做。在实际教学中,可以通过具体的案例来讲解使用函数的必要性。函数是进行功能抽象从而实现模块化设计的工具,同时也可以用来减少程序中重复的代码,因此可以从模块化程序设计和减少重复代码需要两个角度来讲解。
1.1模块化程序设计的需要
在实际教学中,可以结合2018年中兴公司的案例来引入模块化程序设计。
“2018年上半年美国政府宣布了对中兴的禁令,禁止美国公司向中兴销售零部件和相关技术,引发了中兴股价大跌。我们知道中兴是全世界第四大手机生产商,市值达一千两百亿人民币,怎么会轻易被美国控制呢?这是因为中兴手机使用了美国高通公司的骁龙处理器,一旦高通公司不再提供处理器,中兴就无法继续生产手机。
“从这个事件我们可以看到,像中兴这样的厂商无法自己生产手机中的所有零部件,而是需要使用其他公司提供的部件,而且还是核心部件。因此手机的生产是需要全球很多公司的分工与合作才能完成的。当然,分工合作的前提是产品被划分成很多相对独立的模块,只要模块之间的接口定义好了,每个厂家可以专注于改进模块内部的结构,提高模块的性能,而不会对合作的其他公司造成影响。
“那么在程序设计中如何实现分工合作呢?答案就是函数,一个函数就是一个模块,使用函数就可以实现模块化程序设计,从而实现程序设计的分工合作。”
这一案例结合当下热点新闻,容易激发学生的兴趣。通过类比手机的模块化生产,引导学生思考如何实现程序的模块化设计,从而引入函数的概念。
1.2减少程序中重复代码的需要
在程序设计中,有时需要多次使用某个功能,就需要多次重复编写实现该功能的代码,这使得程序存在冗长、不便于修改的问题。那么如何减少重复代码呢?
有的同学可能想到是循环。的确,循环有减少重复代码的功能,但这只是针对重复代码连续出现的情况。如果代码是在程序中不同的位置重复出现,则难以用循环消除重复。因此,有必要引入另一种减少代码重复的机制,也即是函数。
在教学实践中,使用下面的例子来讲解如何使用函数消除重复。在程序Prol中,两个if语句完成的功能是一样的,都是从两个整数中求出最大值,只不过针对的具体整数不一样。由于两个if语句中间有一个输入语句,因此无法使用循环来消除重复。如果把这部分代码抽象成一个函数max,功能就是求两个整数的最大值,然后在主函数中两次调用max,这就消除了重复代码,如程序Pr02所示。
通过该实例中两个程序的对比,学生可以理解函数是如何减少重复代码的。同学们对函数有了这样一个基本概念后,就很容易理解后续的语法格式了。
2函数的定义与调用
关于函数的使用,有两个最基本的操作,定义与调用。要求学生一定要弄清定义和调用的区别与联系,如表1所示。从时间顺序来看,这两个操作是先定义,后调用。从概念来看,函数定义就是指明功能是如何一步一步实现的,函数调用就是这个功能的一次使用。但是这两者又需要联合起来完成功能的使用,如果只有定义没有调用,则该功能永远都不会被执行,如果只有调用没有定义,则功能没有实现,会在连接阶段产生错误。
为加深对定义与调用之间关系的理解,仍然采用手机的案例。“举个例子,手机是可以实现通信功能,用户可以使用手机进行通信。当然在用户使用之前,要有生产商先把手机生产出来。厂商生产手机需要考虑通信功能如何实现,采用什么协议,电路如何设计,但是用户使用手机通信时不需要考虑这些问题,只需要输入号码,按拨号按钮就可以。手机对用户来说是一个黑箱,即用户看不到里面的结构,只能通过界面操作手机。
“那么厂商生产手机相当于我们定义一个函数,用户使用手机相当于调用一个函数。所以函数要先定义,后使用,这应该就容易理解了。
“在编写函数时,我们既是函数的生产者,又是函数的使用者,所以要及时在这两个身份之间转换。作为生产者,就是要关注函数的功能如何实现。作为使用者,只需要给定参数,不需要了解函数中的功能是如何一步一步实现的。”
当然,这里对区别与联系的讲解仅针对函数最基本的概念,并没有涉及参数、返回值等具体语法格式。这主要是为了让学生建立关于函数的宏观概念,明白函数先定义后调用的使用流程。在明确这一宏观概念的基础上,后续教学再逐步细化,这样才能避免学生一开始就陷人细节问题中。
3从黑箱角度理解函数接口
在上述函数定义与调用概念的讲解中,提到在调用函数时要给定参数。那么,这个参数又是什么概念呢?
我们知道一个函数代表一个特定的功能,而一个功能一般都会有输入和输出。可以把参数理解为函数的输入,而返回值理解为函数的输出。在调用函数时,只需知道参数和返回值的格式即可,无需了解函数内部是怎么实现的,因此函数就是一个黑箱。同时,调用函数时需给定合适的参数,处理其返回值,因此参数和返回值就构成了函数的接口。图2展示了函数与黑箱的类比,通过这种类比,学生就容易理解参数的概念。当然,定义函数时程序员要实现函数的功能,这时就要关注函数内部的结构了。
调用函数时要给定参数,那么在定义函数时就应该预留空间来保存这些参数。因此参数又分为实际参数和形式参数。调用函数时给定的参数称为实际参数,而定义函数时预留的存储空间称为形式参数。在调用函数时,实际参数会传递给形式参数,使得被调用函数运行时形参具有实际的数据。
我们仍然通过图1中的程序Pro2来讲解函数的参数。首先看max函数的定义。从第一行可以看到,max函数有两个参数x和v,都是整数类型,一个返回值,也是整数类型。x和v是在函数定义中使用的参数,因此是形式参数。
那么max函数实现了什么功能呢?我们可以看到在花括号中,定义了整型变量m,如果x大于v,把x赋值给m。否则,把v赋值给m。这样,m就是x和v中最大的一个。因此,max函数的功能就是求两个整数的最大值。最后通过return语句返回了这个最大值。
然后我们看主函数中对max函数的调用。首先,第4行由输入函数scanf给变量a和b输入值。第5行使用变量a和b调用max函数,并且把返回值赋给变量m1,即a和b的最大值,这里a和b是实际参数。
从这个例子我们可以看到,首先使用形式参数x和v定义max函数,来实现求最大值的功能。然后在主函数中使用实际参数a和b调用max,从而求出了a和b的最大值。
4函数调用过程演示
在之前的教学中,同学已了解到C语言的程序总是从主函数开始执行。现在有了自定义函数后,程序中有了多个函数,牢记这一点尤为重要。以图3中的程序为例,程序从主函数开始执行,在主函数中调用max函数。
这里需要分清主调函数和被调函数的概念。由于是在主函数中调用max函数,因此主函数是主调函数(callingfunction),max函数是被调函数(called function)。当然,主调函数并非总是主函数,比如,如果在max函数中又调用了prinff函数,那么针对这次调用,max是主调函数,printf是被调函数。
在调用函数max时,程序的流程控制从主函数转移到了max函数中,如图3中虚线①所示,这时实参传递给了形参,如图3中实线①和②所示。被调函数执行结束时,控制又从max函数转移回主函数中,如图3中虚线②所示,并且由return语句把最大值返回到调用的位置,如图3中实线③所示,赋值给变量m1。
在讲解了上述程序调用过程的细节后,应当再次强调,从总体上看,参数是从主调函数传到被调函数,返回值是从被调函数返回到主调函数。这个参数传递是单向的,仅能从主调函数到被调函数,而不能反向传递。这是由值传递决定的,明确这样一个概念有助于后续理解指针类型参数如何实现反向传递。
该调用过程可以通过单步执行演示,以观察控制的转移以及参数的变化。单步执行能够非常直观地演示函数调用过程,加深同学们的理解。并且,单步执行是发现程序逻辑错误的重要手段,有助于培養同学调试程序的能力。因此,不但要给学生演示,也要求学生在上机中进行实际操作。
之前在单步执行时一直在使用step over按钮,现在可以跟同学们说明stepinto和step out的用法,如图4所示。如果某行有函数调用,那么在这行上点击step into按钮会使得控制转移到被调用函数中,在被调函数中继续单步执行,而step over按钮会使得这一行当做一个整体执行,然后执行下一行。在被调函数中执行时,可以点击step over单步执行,如果点击step out,则会结束当前函数的单步执行,返回到函数被调用的位置继续执行。
5从函数独立性角度看待局部变量
函数是完成特定功能的模块,提供了参数和返回值作为与主调函数传递数据的渠道。除此之外,函数应该是相对独立的,也就是内部的数据不应该受外界干扰。因此,函数内部定义的变量默认都是局部的,也就是只能在函数内部访问,而不能在函数之外访问。这就是局部变量。
从这个角度理解局部变量的概念是比较容易的。在实际教学中发现有些同学在学习了函数后,会出现在一个函数中试图使用另一个函数中变量的问题。现在,应该强调参数和返回值是函数与外界传递数据的渠道,一个函数中定义的变量是不能在其他函数中使用的,否则参数和返回值就失去了意义。
与局部变量相对应的是全局变量。全局变量是在所有函数之外定义的变量,在变量定义之后的所有函数中都可以使用。由于全局变量可以在多个函数中使用,因此也是这些函数之间共享数据(或者传递数据)的一个渠道。但是由于全局变量破坏了函数的独立性,所以不鼓励过多使用全局变量。
6结束语
C语言课程有很多的细节知识,同时又借鉴了很多人类社会组织如何运行的道理,比如函数是实现模块化以及分工合作的基础。本文认为在教学中应该强调宏观概念,同时突出案例教学,这样才能建立整体概念,有助于理解细节知识。另外,提出了从黑箱角度看待函数的参数与返回值,从函数独立性角度看待局部变量,这两个视角能够加深同学的理解。从实践来看,这些教学方法起到了良好的效果。