||
[按:数学系出身的很多学生编程能力较弱,主要是因为在数学系,学生本科期间的精力主要放在理论方面,对于编程缺乏适量的实践和深入的认识。下文展示的是今天下午与学生的QQ讨论,关于课题中涉及到的编程环节。文中隐去了涉及课题的敏感信息。希望向有关同行和同学展示本人培养学生的方式,权作某种交流。象这样的讨论基本上每周一次,必要时增加一、两次,目的是加快学生的进展。]
学生 15:04:39
可以开始了吗
Yiwei 15:06:04
今天先讨论例1,就是你上次编写的程序中的问题。
学生 15:06:11
嗯
Yiwei 15:09:21
以前没有给你指出来,就是变量名称应该使用英语,而不要使用汉语拼音。
学生 15:10:29
嗯,知道了
Yiwei 15:10:51
每个子程序,也就是matlab函数,都有一个主要的功能,应该以这个功能为主为子程序或函数取名字。
Yiwei 15:11:49
解决一个问题,往往需要开发一套程序,其中主程序只有一个,其它的都是子程序。
Yiwei 15:13:22
主程序有点象“老板”,它的内部主要是设定各种参数,以及组织和调用子程序。换句话说,主程序里一般不涉及大量的计算。
Yiwei 15:14:20
更准确地讲,主程序本身很少直接含有涉及计算的语句。
学生 15:14:39
嗯,知道了
Yiwei 15:17:36
现在说说子程序或函数。Matlab里面的子程序全部用函数的形式实现。一般而言,一套程序里可能包含个别子程序,它们的作用就是大量的重复操作,这种子程序可以看作“发动机”,一般而言,其中有一个计算量最大的子程序,可以看作“主发动机”,其它的则可能只是这个主发动机的“零件”。
Yiwei 15:20:46
有些子程序会产生一些数据,其中有些数据是中间结果,而有些数据需要反复使用。需要反复使用的数据,应该采取“一次生成 多次使用”的策略。而不要在每次需要的时候,一次一次地反复生成。
学生 15:22:22
嗯
Yiwei 15:23:42
再有,就是要注意程序的通用性。不能说,问题稍微一改变,整个程序都要从头到尾修改,甚至重新编写。这样的话,效率就很低了。
Yiwei 15:24:56
在程序中,有可能变化的量,就不要用具体的数值。
学生 15:25:55
嗯,明白
{博主按:以上主要向学生讲了编写程序的一般原则和注意事项。因为不是讲课,而学生也有一定的编程基础,所以这里讲的是最需要注意的几点。以下则结合学生编写的程序进行具体的讨论。}
Yiwei 15:26:18
现在你打开“chazhi.m”,就是上次讨论的程序。
学生 15:26:28
打开了
Yiwei 15:27:02
看第4行:[R,wucha]=xxx(c,N);
Yiwei 15:27:22
这一行的目的是得到R与wucha
Yiwei 15:27:47
再看第5行:R1=R(1):1e-4:R(25);
Yiwei 15:28:24
我现在问你,第5行有何毛病?
学生 15:29:21
看不出来,就是给R插值去
Yiwei 15:30:40
刚说了通用性,你看看刚才跟你讲的那段话,再看这里有何毛病。
学生 15:30:53
嗯,知道了,还是求有关R的,可以放在一个程序里
学生 15:31:14
不是
学生 15:31:35
还有就是R(1),R(25)
学生 15:31:47
不通用
Yiwei 15:32:05
还有呢?
学生 15:32:23
插值的步长
Yiwei 15:32:43
对了。
Yiwei 15:34:13
再看那个for循环,有何毛病?
学生 15:35:00
800不通用
Yiwei 15:35:17
还有呢?
学生 15:36:29
看不出了
学生 15:37:14
可以当成一个子程序求x
Yiwei 15:40:07
这里的问题就是两件事情,全都放在一起了。而且放在一起是一堆语句,这和上面的程序“不对称”。上面主要是一个调用和几行作为插值准备的语句。
Yiwei 15:40:54
一件事情用一个子程序去实现,这就是模块化思想。
Yiwei 15:41:42
子程序的功能越单一,它的可重用性和通用性就越强。
Yiwei 15:42:30
明白了吗?
学生 15:42:48
明白意思了
学生 15:43:13
但是还不是很会整理
Yiwei 15:44:21
其实你首先要能从中看出一件事情,就是从???中找出???的、???的数据对儿。
学生 15:45:02
嗯
Yiwei 15:45:25
你现在就编写一个子程序,专门用于这一件事。
学生 15:46:33
嗯
Yiwei 15:46:59
做完后发到我的邮箱。
学生 15:55:27
m
学生给您发送了一个窗口抖动。
Yiwei 15:57:12
看到了。等。
Yiwei 16:00:56
这个程序还是存在问题,就是有多余的东西。
Yiwei 16:02:18
提示:去掉num和R1. (输入及子程序内部都去掉这两个变量)
Yiwei 16:02:59
修改完成后发到我的邮箱(以后这句话用“r”表示)
学生 16:04:12
不太明白,子程序中的怎么可以去掉呢,要用到的啊
Yiwei 16:04:45
只用到???就能完成任务。
学生 16:05:21
哦
学生 16:06:48
那要输出什么了输出使误差反号的R,就得有R1啊
Yiwei 16:07:21
输出索引不就行了?
学生 16:08:14
不太会,你教我改下,我学下
Yiwei 16:09:08
好吧。
Yiwei 16:09:33
在输入列表中去除num和R1
学生 16:09:57
嗯
Yiwei 16:10:44
删除第2行。在那里写上语句:???=???(:);
Yiwei 16:11:42
接着在下一行写上:[num,ntemp]=size(???);
Yiwei 16:12:22
第4行:num改为num-1
Yiwei 16:12:54
做完了吗?
学生 16:12:59
完了
学生 16:13:58
6,7行的R1哪来的
Yiwei 16:14:19
不需要。
Yiwei 16:14:39
在第4行写上:idx{[]};
Yiwei 16:15:38
这是一个空的cell变量,用于存储符号要求的索引,就是数组的序号。
学生 16:16:47
嗯
学生 16:16:52
好高级
Yiwei 16:16:57
其中idx是index的缩写记号。在编程时,变量名用英文单词的全拼,或者是“声母”字头的组合。
Yiwei 16:17:52
接着上面的语句,在下一行写上j=0.
Yiwei 16:18:36
现在删除含有R2和j+1的句子。
学生 16:19:09
嗯
Yiwei 16:20:05
然后在if 和 end 之间,写上:
j=j+1;
idx(j)=i;
Yiwei 16:20:39
再回到idx={[]},改为:idx=[]。
Yiwei 16:22:20
换句话说,我刚刚改了主意,就是只保存一对儿索引中的前一个。这就够了。这样的话就不用cell了,所以用idx=[]表示一个空的数组就可以了。
Yiwei 16:22:45
现在输出的地方用idx替换R2.
Yiwei 16:23:22
总共11行程序,完成了吗?
学生 16:23:40
完成了
Yiwei 16:24:15
最后的end和前面的for对齐。
学生 16:24:54
嗯,明白了,好高级,idx都没见过
Yiwei 16:25:49
刚才的做法背后有一个原理:最少输入原理。
Yiwei 16:26:47
就是任何子程序,在编写时一定要排除多余的输入变量,做到最少输入。
学生 16:27:21
嗯,,明白了
{博主按:上文中的讨论,主要是针对学生程序中的“典型情况”进行诘问式启发,尽可能让学生自己发现问题,待学生确实无计可施时,再予以援手;必要时予以示范。我注意到一些导师宁愿化n个小时讲“空话”,也不愿意花1个小时去交给学生实在的技术和经验,这对于欧洲导师而言是不可想象的。上面讲的“典型情况”,就是程序缺乏层次,不懂得尽可能地用子程序实现单一功能的模块,而是一大堆语句放在一起。学生不能从这一堆语句中分辨其中相对独立的过程,从而不能从一堆语句中提取抽象的任务概念,并编制相应的通用子程序。}
Yiwei 16:28:40
好了,刚才完成了主要的事项。现在程序还有做进一步修改。
学生 16:28:52
嗯
Yiwei 16:28:59
将所有的???用???替换。
学生 16:29:36
就刚刚那个子程序吗
Yiwei 16:30:00
是的。在???里做。
学生 16:31:07
Yiwei 16:31:49
很好。为了方便,输出中增加j,就是数据对儿的个数。
学生 16:32:21
Yiwei 16:32:24
j放在idx的后面,两个输出用逗号隔开,放到方括号中。
Yiwei 16:32:43
很好。
学生 16:32:44
Yiwei 16:33:05
好了,现在你可以提问了。
学生 16:33:47
为什么要把???变为???
学生 16:34:56
还有第二行有什么作用,不要不行吗
Yiwei 16:34:57
这只是名称的变化,程序的功能不变。但是这里反映了一种追求,就是变量名称的通用性。
学生 16:35:12
哦
学生 16:35:24
还有第二行有什么作用,不要不行吗
Yiwei 16:35:48
不行。第二行是为第三行做准备的。
Yiwei 16:36:49
第二行,是把向量转换为列向量。无论???原来是行向量还是列向量,出来的结果都是列向量。
学生 16:37:32
哦
学生 16:38:28
明白了
Yiwei 16:38:40
size是给出???的尺寸,它的输出总是假定第一个输出表示行数,第二个输出表示列数。我们只需要行数,但列数也必须用一个临时变量占住位置。
学生 16:39:27
嗯,明白
学生 16:40:03
没了
Yiwei 16:40:27
换句话说,size的输出约定,决定了我们必须保证???为列向量。但子程序里的操作,只在子程序内部起作用,子程序执行完毕后,???还是原来的样子。
Yiwei 16:40:53
好,你没问题了,我问你一个问题。
学生 16:40:59
嗯
Yiwei 16:41:12
idx=[]; 这句有何用处?
学生 16:42:29
idx=[]表示一个空的数组,为9行的存放准备
Yiwei 16:42:59
去掉它会出现何种问题?
学生 16:43:46
第九行会出现没有变量idx的错误
Yiwei 16:44:34
好吧,我们看看是不是这样。在idx=[];前面增加注释符号%
Yiwei 16:44:51
然后保存。
{博主按:以上是子程序完成后的一些细节讨论,这些细节不会影响程序的功能,而是体现编程的风格和程序员素养方面的东西。讨论中通过问答的形式加深学生对这些事宜的印象,顺带地埋了一个伏笔。}
Yiwei 16:45:54
现在我们进入测试环节,就是针对这一个子程序进行测试,顺便检查刚才提出的问题及你的回答。
学生 16:46:08
嗯
学生 16:47:06
Too many output arguments.
Yiwei 16:47:09
>> t=0:1/100:2*pi;
>> y=sin(t);
Yiwei 16:47:26
你不要自己做,跟着我来。
学生 16:47:29
嗯
Yiwei 16:47:48
上面的两句是生成一个测试用的数据。
Yiwei 16:49:05
注意,测试子程序不需要用???这种特定的数据。可以另外生成独立的测试数据,用于独立地测试所考虑的子程序。
学生 16:49:31
嗯
Yiwei 16:51:09
这样做的好处是,你不必专门生成???. 而是独立地进行测试。这一点看起来没什么,但重要的是,你可以不必考虑该子程序的上下文。这样就可以减少很多[子程序]与“外界”的纠缠。
学生 16:51:46
嗯
Yiwei 16:51:53
好了,现在我们画图看一下数据y。
Yiwei 16:52:16
>> figure;plot(y)
Yiwei 16:53:07
刚才的画图语句不要了。
Yiwei 16:53:10
>> figure;plot(y,'r.')
Yiwei 16:53:15
用这个。
学生 16:53:55
Yiwei 16:54:43
很好。现在用放大镜找出???的、???的数据的索引。
Yiwei 16:55:01
就是用手工观察的办法找出答案。
Yiwei 16:56:39
?
学生 16:57:34
300左右
Yiwei 16:57:53
要准确答案。
Yiwei 16:58:31
这也是检查一下你对放大镜的掌握情况。
学生 16:59:32
315与316反号
Yiwei 17:00:07
好的。
Yiwei 17:00:23
现在用刚才的子程序去算。
学生 17:01:32
学生 17:02:33
这就是刚刚在idx=[];前面增加注释符号%造成的吧
Yiwei 17:02:52
你把注释符号去掉看看。
学生 17:03:10
也不对
Yiwei 17:04:16
把你的程序文本贴到屏幕上给我看。
学生 17:04:49
(略)
Yiwei 17:06:03
把运行情况的给我看看。
学生 17:06:28
(略)
Yiwei 17:06:50
你修改程序后要保存,然后才起作用。
学生 17:06:59
等会。我知道哪错了
学生 17:07:42
(略)
Yiwei 17:07:57
刚才是什么问题?
学生 17:08:33
弄错了,在另一个命名的程序里改的
Yiwei 17:08:59
改的什么?
学生 17:09:54
我把这个程序写在???里了
学生 17:10:35
???里就没改
学生 17:11:09
还是我原来自己弄得那个程序
Yiwei 17:11:16
说明你没有严格跟着我走。以后注意不要自作主张。
学生 17:11:26
嗯
Yiwei 17:12:20
你原来的程序邮箱里有,不需要另外备份。每次让你将程序发到邮箱就是这个道理。
学生 17:12:39
嗯,加%运行的结果一样
Yiwei 17:12:49
好的。
Yiwei 17:13:11
现在保持那一行的%。
Yiwei 17:13:34
我们考虑另一个测试。
Yiwei 17:13:51
>> clear;
学生 17:14:56
嗯
Yiwei 17:15:20
>> t=0:0.01:1;
>> y=exp(t);
Yiwei 17:17:25
>> figure;plot(y,'r.')
Yiwei 17:17:35
把你的图贴出来。
学生 17:18:01
(略)
Yiwei 17:18:14
>> [???,???]=???(y);
Yiwei 17:18:23
贴出你看到的。
学生 17:19:15
(略)
Yiwei 17:19:50
知道为什么出现错误吗?
学生 17:20:34
???没定义
Yiwei 17:21:19
也可以说没有赋值,而被要求输出。
Yiwei 17:21:58
再问你一个问题:这里???为什么没定义?
学生 17:23:41
???没有起到索引的作用?
Yiwei 17:23:58
回答错误。
学生 17:24:20
刚这两个例子有什么区别了
Yiwei 17:24:41
想想看有何区别?
学生 17:25:30
哦,知道了,下一个图就没有???点
学生 17:26:58
就因为没有,所以找不出来???就附不了值
Yiwei 17:28:05
对了。所以,???; 是为这种情况而准备的。
学生 17:28:27
如果去了%,就是空的,等着重新赋值
学生 17:28:35
嗯,明白了
Yiwei 17:29:02
去掉那个%, 然后再运行。贴出你看到的结果。
学生 17:29:21
(略)
Yiwei 17:29:49
去掉末尾的分号运行。
学生 17:30:08
(略)
学生 17:30:22
哦,彻底理解了
Yiwei 17:30:29
很好。
Yiwei 17:31:19
这里的j=0也很有用。你可以通过j的值判断是否有解,而不用去读取空的数组。
学生 17:32:03
嗯,明白
Yiwei 17:33:41
今天就讨论了这么一个小的子程序,但背后的道理都在里面了。最主要的,就是:最少输入原理,变量名通用性,还有独立测试。这三点是最重要的。
学生 17:34:28
嗯,大彻大悟
Yiwei 17:34:59
现在休息吧。吃完晚饭接着讨论后面的内容。
学生 17:35:24
嗯,好的,几点大概
Yiwei 17:35:34
老时间。
学生 17:35:42
嗯,好的
{博主按:上面的讨论主要是围绕测试子程序进行的,其中有个插曲,就是有时学生做了一些不必要的事情,但讨论时并未告知我,后来学生自己也忘记了,从而在某个阶段就产生了问题,我就无法知道是什么问题。学生想起出现问题的原因后,我就一定要问清楚究竟是什么情况(尽管可能是无谓的问题)。等弄清楚原因以后,嘱咐学生如何避免出现此类问题。在这个阶段,也通过引导的方式让学生领悟了前一个阶段所埋的伏笔问题。当然,伏笔和测试例子都是作为引导者即兴的设计,目的是加深学生的印象。}
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2024-11-17 03:22
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社