当前位置:首页 期刊杂志

“程序设计基础”课程函数设计教学方法探讨

时间:2024-05-04

文章编号:1672-5913(2009)10-0116-04

摘要:本文针对“程序设计基础”课程介绍了以在线实践为导向的教学思路,并对其中的函数设计教学,提出了新颖的教学过程:承上启下、因势利导地引入函数的概念和作用;循序渐进地讲解函数的设计方法;以及通过递归函数设计来提高学生对函数功能的理解和对函数设计技巧的掌握。

关键词:程序设计基础;在线实践导向;函数设计;递归函数;教学

中图分类号:G642

文献标识码:B

1引言

在文献[1]中,作者针对“程序设计基础”课程提出了以在线实践为导向的教学思路,其主要思想为:以学科竞赛为驱动、以在线实践为导向、以课程设计进行强化。这种教学思路以程序设计思想和方法的培养为主,以程序设计语言教学为辅。

我们在教学中以ACM/ICPC程序设计竞赛为驱动,激发学生的学习兴趣和竞争意识,培养学生的主动思维能力。另外,我们在课程的教学中根据教学的需要选取国内外著名OJ(Online Judge,在线评判)网站上的试题作为例题和练习题,以培养学生独立分析问题、解决问题的能力,以及分组讨论、团队协作、文档组织等能力。在课程的最后阶段,我们通过课程设计强化学生的学习效果。

为了适应以在线实践为导向的教学思路,在文献[1]中,我们重新设计了“程序设计基础”课程的理论教学内容和实践教学内容。其中理论教学内容的设置和课时安排如表1所示。

从表1可以看出,理论教学内容安排的思路是:先用尽可能少的时间讲解编写一个C/C++程序所需的最小语法知识集,然后讲解常用的程序设计思想和方法;最后是课程设计。

函数设计的教学是“程序设计基础”课程的一个重点内容。本文针对这部分内容的教学,提出了新颖的教学过程。在以往的教学中,函数设计一般是放在程序控制结构、数组等内容之后,前后教学内容的设置并没有直接联系,使得学生难以理解函数的概念、功能和设计方法。

我们在教学中采取新颖的方法和过程自然地引入数

学函数的使用、循序渐进地介绍函数的设计方法、以及通过递归函数设计来提高学生对函数功能的理解和对函数设计技巧的掌握。

教改课题项目:浙江财经学院2008年教学科研重大课题《以学科竞赛为驱动和以在线实践为导向的程序设计课程教学改革》(课题编号:JK200812)。

作者简介:王桂平(1979-),男,江西省安福县人,讲师,在读博士,浙江财经学院信息学院教师。主要研究方向:算法分析与设计,图像处理与模式识别。

2承上启下、因势利导地引入函数

学生对知识点的理解需要一个过程,而且这个过程越自然越利于学生接受知识点。所以我们在教学过程中尽早地引入函数的概念,自然地过渡到函数设计。

2.1从数学函数的使用入手

我们所设计的第1部分教学内容是以数值型数据的处理为线索,以简单数学计算或数学应用题目为例子来讲解C/C++语言语法知识,如报数游戏、闰年的判断、求三角形面积、素数和完数的判断、Fibonacci数列各项递推、迭代法求平方根等等。以这些数学应用为例讲解语言语法,学生更容易接受,因为这些数学应用问题学生已经在高等数学甚至初等数学中就已经学过了,现在只是用编写程序的方法去求解。

在进行数据处理时,经常要进行一定的运算,才能得到结果,运算是通过运算符和表达式来实现的。所以我们在介绍完C/C++语言中的数据(变量和常量)后,就自然地过渡到运算符和表达式了。

在进行数据处理时,仅有运算符和表达式往往是不够的,经常还需要使用到数学函数。因此,我们在介绍完运算符和表达式后,紧接着就向学生介绍数学函数的使用。

尽管这时学生对函数的概念和功能还一知半解,一开始也不能正确地使用数学函数,但我们认为从数学函数的使用入手,能较早地让学生接触到函数的使用,也能让学生更自然地接受函数的概念。特别是学生在学初等数学时,已经具备了数学上函数、函数的自变量、函数值等概念,这些概念有助于学生初步理解程序设计语言中的函数、函数参数、函数返回值等概念。

例如,要对2.5开3次方根,即要求2.51/3。老师向学生介绍了数学函数pow的原型后,学生一开始可能将pow函数的调用错误地写成如下的形式:

double x = 2.5, y = 1.0/3, z;

z = double pow( x, y );

z = double pow( double x, double y );

等等。导致这些错误的原因是学生还没有函数原型的概念,老师只要将这些错误的调用形式纠正过一两次,学生就能举一反三,正确地使用数学函数。

2.2从二重循环过渡到函数设计

我们在“算法及控制结构”这一节内容的教学中,是以二重循环的使用作为结尾的,如输出100~200之内的所有素数、输出6~10000之间的完数等等。这些程序的main函数代码比较长,这样,当我们在讲解函数时,就很自然地引入函数对main函数的功能进行分解。

例如,要输出100~200之内的素数,可以用一个2重循环实现。但如果有一个函数prime,能够实现判断一个正整数m是否为素数。其调用形式是:prime(m)。调用该函数后返回值如果为1,则m为素数;如果为0,则m为合数。因此我们只需要用如下的代码就可以输出100~200之内的所有素数:

for( int m =100; m<=200; m++ )

{

if( prime(m) )

printf( "%d ", m );

}

在这个例子中,我们把“输出100~200之内所有素数”的功能需求进行分解,把“判断一个整数是否为素数”的功能用prime函数去实现。这就是函数的功能所在。

通过这样的讲解,学生能较自然地理解函数的功能,也更容易接受函数的概念。

3循序渐进地讲解函数设计(基础篇)

从二重循环过渡到函数的功能和概念后,我们采取以下教学方法,使得学生能在较短的时间里掌握函数的设计方法。

3.1循序渐进,步步深入

在讲解函数的定义和调用时,对其中的知识点,我们采取以下顺序进行讲解:函数的定义、函数的参数、函数的返回值、函数的调用。

我们认为,按照这样的顺序进行讲解是合理的,因为学生已经掌握了数学函数的使用,已经能初步理解函数的相关概念了,我们按照这样的顺序讲解可以循序渐进地进入到自定义函数的定义和调用上。在这个过程中,我们将教学的重点放在函数形参、实参和函数返回值上。

3.2切中要害,见招拆招

很多初学者对函数比较头疼,不知道该如何设计函数。具体体现在:

(1) 不知道函数是否有参数,有几个参数,是否有返回值,随意地设置函数的参数和返回值。

(2) 不明确函数要处理的数据是哪些,不明白函数形参的作用是什么,形参的值是在什么时候被“赋予”的。初学者经常在函数里通过输入语句给形参输入数据。例如,初学者可能在定义上述prime函数时输入数据到形参x中:

int prime( int x )

{

printf( "%d", &x );

}

对于第1个问题,我们的解释是:程序设计者希望采用怎样的形式去调用函数,这种函数调用形式里有几个参数,分别是什么类型,是以此来确定函数的形参个数和类型;程序设计者希望函数执行以后是否得到一个结果,这个结果是什么类型的,是什么含义,是否需要返回到主调函数中,以此来确定函数的返回值及其类型、含义等。

对于第2个问题,我们的解释是:函数形参是在函数调用时,通过实参与形参之间的数据传递,从而“被赋予”了值。只要没有函数调用发生,就不会给形参分配存储空间;当函数调用发生时,为形参分配存储空间,并把实参的值赋值给形参。

对于上述解释,我们以前面讲过的二重循环例子来进一步阐述。即输出100~200之内所有素数,要求:1)定义一个函数prime,用于判断x是否为素数,如果为素数,返回1,否则返回0;2)在主函数中调用prime函数,用于判断100~200之间的每个数是否为素数。

根据题目的意思,主调函数中调用prime函数的形式是prime(199),即判断199是否为素数,如果为素数则返回1,否则返回0。因此,prime函数的原型为:

int prime( int x );

另外,在prime函数里,是要判断形参x是否为素数,这个x的值不是在prime函数里通过输入语句输进去的,也不是采用赋值的方式“赋予”给它的,而是在主调函数中调用prime函数时,如prime(199),把实参199的值传递给形参x的,因此这时执行prime函数,形参x的值就是199,调用prime函数就是要判断199是否为素数。

讲解并演示这些过程后,我们在课堂上可以通过一些练习题进一步考查学生对利用函数进行功能分解、函数设计、函数调用的理解。

3.3精选例题,事半功倍

我们所设计的第1部分教学内容是以数值型数据的处理为线索,以简单数学计算或数学应用题为例子来讲解的,函数的设计也不例外。例如,我们通过以下例子来讲解函数的嵌套调用。

抛物线y = x2/(2*p)绕它的对称轴x = 0旋转所成的曲面就是旋转抛物面。放在焦点F(0, p/2)处的光源所发出的光,经过抛物面各点反射之后就成为平行光束,如图1。可以利用这一性质制造需要发射平行光的灯具,例如:探照灯,汽车的车前灯等。请编写程序验证这个性质。

题目的意思是,如图1所示,从焦点F发射的任意光线,比如图中的两条光线L和L',经过抛物面反射后,反射光线R和R'都平行y轴。

要证明反射光线R平行y轴,只要证明∠1 = ∠3,而∠1和∠2是相等的,所以只要证明∠2=∠3即可,即只要证明FC = FT,这里点C是光线L与抛物线的交点,点T是抛物线在C点的切线与y轴的交点。

以下编写程序,实现:任意给定抛物线参数p和发射光线斜率k,输出线段FC和FT的长度。

在本题中,我们设计以下3个函数来实现程序的全部功能:

(1) main函数:在main函数中输入抛物线参数p和直线参数k,接下来所有工作都是通过调用solve函数实现的。

(2) solve函数:求交点C和交点F的坐标,并调用length函数求线段FC和FT的长度并输出。solve函数有两个形参,即抛物线参数p和直线参数k,没有返回值。solve函数的原型为:

void solve(double p, double k);

(3) length函数:求平面上两点(x1,y1)和(x2,y2)的距离,即连接这两点的线段的长度。该函数有4个形参,为这两个点的坐标;返回值为求的线段长度。length函数的原型为:

double length( double x1, double y1,

double x2, double y2 );

通过这道题目的讲解,学生在求解比较复杂的数学应用题时,能根据需要对程序的功能进行分解并用不同的函数实现。

4递归函数设计(提高篇)

在以前的教学中,函数设计通常需要2~3周才能讲完。而在目前的教学中,我们将函数重载、有默认参数的函数等内容剔除掉后,学生能够在1周(3个理论课时+2个实验课时)的教学中初步掌握函数的设计方法。在后续章节的教学中,我们列举的很多例题也需要通过设计函数来实现,所以在后续的教学中一直在进一步加强学生对函数设计方法的掌握。另外,我们将递归函数的设计放在“递归与搜索”这一章当中来讲解,作为函数设计的提高阶段。

递归是很多算法的基础,如搜索、分治等,也是课程的一个难点。学生在掌握了一般函数的设计方法后,在利用递归思想进行搜索求解时需要注意以下两个问题:

(1) 如何设计递归函数递归函数的设计主要面临以下几个问题:

① 需要将什么信息传递给下一层递归调用?——由此确定递归函数有几个参数,各参数含义是什么。

② 每一层递归函数调用后会得到一个怎样的结果?这个结果是否需要返回到上一层?——由此确定递归函数的返回值,及返回值的含义。

③ 在每一层递归函数的执行过程中,在什么情形下需要递归调用下一层?以及递归前该做什么准备工作?递归返回后该做什么恢复工作?——由此确定递归函数中递归调用的细节。

④ 递归函数执行到什么程度就可以不再需要递归调用下去了?——应该在适当的时候终止递归函数的继续递归调用,也就是要确定递归的终止条件。

(2) 如何调用递归函数进行求解

调用递归函数进行求解:在main函数(或其他函数)中应该采取怎样的形式调用递归函数?也就是从怎样的初始状态出发进行搜索,通常也就是确定实参的值。

我们在教学中以一些经典的竞赛题目为例来阐述上述方法,如有这样一道例题:有17种硬币,硬币的面值是平方数12, 22, 32, …, 172,即1, 4, 9, …, 289。问要支付一定额的货币,有多少种支付方法。

例如,若要支付总额为10的货币,则有四种方法:10个面值为1的货币;1个面值为4的货币和6个面值为1的货币;2个面值为4的货币和2个面值为1的货币;1个面值为9的货币和1个面值为1的货币。

在本题中,为避免求得重复的支付方案,我们需要按硬币面值从小到大的顺序依次选用合适的硬币,如果当前选用的硬币面值总额小于需要支付的货币总额n,则继续选用;如果等于,则我们找到一种方案,不再考虑其他货币,而是继续下一个方案的选择;如果大于,则放弃该方案,继续下一个方案的选择。

我们设计一个递归函数build来求货币总额n的支付方案数,build函数的设计思路是:

●确定build函数的参数:需要支付的货币金额、现已求得的支付方案数、当前选用的硬币面值总额、当前最后选用的硬币是第几种硬币这些信息需要传递到下一层递归调用。因此确定build函数有4个参数:n、count、sum、j,分别对应上述4种信息。

●确定build函数的返回值:每次build递归调用结束后,求得的是当前找到的方案数,最上层的build函数执行完后,得到的结果是最终找到的方案数,因此build有返回值,为int型。

●确定在什么情况下要递归调用下一层build函数:分别考虑第i种货币(i取值为j、j+1、…、17),如果选用该种货币(sum的值增加i*i)后,sum仍小于n,则递归调用build函数:build( n, count, sum, i);从该递归调用返回后,sum的值要减去i*i,

表示弃用第i种货币,继续考察下一种货币。

●确定build函数的终止条件:如果当前选用硬币面值总额sum等于或大于n时,不再递归调用下去,其中前一种情形还需将count的值加1,表示找到一种支付方案。

根据上述分析,设计的build函数如下:

int build(int n, int count, int sum,int j)

{

int i; //循环变量

for( i=1; i<=17; i++ )//搜索所有面值的硬币

{

if( i

sum += i*i; //选用面值为i*i的硬币

//找到一种支付方案

if( sum==n )return ++count;

//超出了支付总额,不再搜索

if( sum>n )return count;

//没超出则递归调用build函数继续搜索

count = build( n, count, sum, i );

sum -= i*i; //弃用面值为i*i的硬币

}

return count;

}

build函数设计好以后,在主函数中,只要采取以下语句调用build函数就可以求得货币总额n的支付方案数count:

count = build( n, 0, 0, 0 );

4个实参的值代表问题的初始状态:需支付的货币总额为n、现已求得的支付方案数为0、当前选用的硬币面值总额为0、当前最后选用的硬币是第0种硬币。

通过这些搜索题目的讲解,学生能在理解搜索思想的同时掌握递归函数的设计技巧,从而进一步提高函数设计能力。

5结束语

函数设计是“程序设计基础”课程的一个重点,也是学生普遍感到难以掌握的一个知识点。本文在以在线实践为导向的教学思路基础上,详细的介绍了我们在函数设计教学中采取的教学方法和过程,这些方法和过程都是以在线实践为导向的教学思路的具体体现。

参考文献:

[1] 王桂平,冯睿. 以在线实践为导向的程序设计课程教学新思路[J]. 计算机教育,2008(22):100-102.

[2] 方红琴. 点面结合突破C程序设计函数教学中的重难点[J]. 计算机教育,2008(22):130-131.

[3] 谢伟增,李瑾. C语言程序设计中的重点:函数与指针[J]. 河南广播电视大学学报,2003,16(4):65-66.

[4] 罗碧波. C/C++语言程序中函数调用解决办法[J]. 计算机时代,2007(5):66-67.

免责声明

我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!