程序设计和调试中的几点总结。
在调试过程中 ,遇到了许多困难。
1. 不知道如何在函数中传递数组做实参,一开始连如何定义函数中的形参都不知道,还是用了帮助才了解,正式发现F1的帮助作的十分贴心,很好用,只是不太会用。我想,学好如何使用帮助能够大大缩短学习的周期。
数组实参的传递最后解决了,要在type 中对数组进行定义 ,如下操作:
Type
arr1=array[1..100] of integer ;
这样就定义了一个数组类型---arr1,以后声明数组只要 x :arr1 ; 而不用 x :array[1..100] of integer ;在数组的实参传递中只要使用数组名,如下:
//函数定义
function f1( x,y :arr1 ) :real ;
begin
.....
end;
//函数使用
p := f1(x,y) ; //x,y 分别为两个数组。
2. 在插值调试过程中,算法是以知的,具体的代码调试要求比较细心。对于变量的定义尤其要注意,应当遵守一定的规则,否则在以后的维护和修改中会带来许多不必要的麻烦,更有可能带来许多意想不到的错误,这一点在以后说明。对于书上已经写好的算法,可能由于你取数组下标不同(0或1),循环变量起点不同而有所差异,不能照抄。有时错误往往在一个符号(如:>写成< 等)。
3 . 对于delphi提供的函数、方法,他们在使用时都有一定的提示。比如 edit1. 当你.下去的时候就会列出此时可以使用的方法,如果没有找到你要用的方法只有2种情况:①没有该方法。②你在某地方有错误,使该方法不能显现出来。比如:inttostr(edit1.Text) ,在写好edit1. 后,找不到Text方法。因为用的函数错误,inttostr()中用的是整数值,但是 edit1.Text 是字符串类型,所以不会显示。用这种方法可以自我检测。
4. 对于变量的规范命名是十分必要和有用的,这在编写一些小程序中察觉不出来。因为在小程序中使用不了许多变量,容易管理,用脑子就可以记住,不会产生许多麻烦。而在大程序中,尤其是多人合力完成的程序,你自己定义的变量别人也许不知道,所以规范的命名有助于别人和自己管理和维护程序。如果定义不得当的话,有可能出现意想不到的错误。我在写Unit1的代码时就遇到了这样的情况。在写一次线性插值的时候,在处理边缘点的时候出现了错误。
错误是这样的:当输入点后,第一次按插值按钮,没有错误。但是在按一次按钮就出错了,这是为什么呢?为什么其他的插值没有出现类似问题呢?我进行了单步调试,发现了出错的地方是循环变量出错。当按第二次的时候,循环变量改变了,从而使结果出错。一看循环变量,原来是i ,而我又用i做了全局变量,由于全局变量中储存的是循环的次数,因而就没有在函数中定义i变量,而直接用全局变量代替局部变量。这样局部的改变影响到全局,就是错误的根源。所以,通过这件事,我深刻的了解到规范命名和注释的重要性。
5. 在对form进行退出过程的执行是一定要注意,如果你对任何一个form进行了操作,如果对其有影响的话,那么就要在调用form的退出过程是进行复原操作,这是必须的工作,也是很重要的工作之一。
在对form的退出过程的编写的时候要注意几个方面:首先,如果你要关闭的form中有一些可视的组件,如:edit,listbox,label,memo等等一些组件,那么就要将这些组件的内容恢复form刚创建时的样子,这是十分必须的一步工作。别小看了这一步工作,这有可能给你带来捆饶。大多数我们调用form使用的语句是formx.show 。而该语句仅仅把你所要调用的form显示出来而不做其他任何事情,更别说是初始化工作,所以我们要对该form在上次退出的时候就进行初始化工作,以便为我们本次的操作打好基础。否则,上次操作留下的数据会继续在form中显示,从而影响我们本次的操作。
其次,在form的退出过程的编写也要注意某些有漏洞的地方,初学者往往容易犯这样的错误。比如:大家往往习惯与用form.close来关闭窗口,这是对的,有时我们会单独做一个退出按钮,里面调用了form.close,并且对本form进行了还原,这一切都很正常。但是在他使用form自带的叉型图标来关闭form时就会遇到麻烦。再次进入时会发现上次退出的时候没有进行还原操作,但是还原操作的代码明明写了,这是为什么呢?因为form自带的关闭只调用了form.close,而不进行其他操作,解决的方法就是在form的Onclose事件中加入还原代码就可以了。而在自己添加的退出按钮的过程代码段中也只需要用一句show.close,还原的是就交给了OnClose Event来完成。
再者,在form关闭的时候,如果对其他的form有过改动,必须要还原,否则会出现上面讲过的错误。有时两个form互相影响,就要互相还原。这样难免就要用到对方form的unit,用语句uses unitx ;而这也有规定。在interface 里,是不能互相uses 的。如果在interface 里互相uses,系统会报错 Circle use ;所以只能够一个在interface中,另一个在implementation 里uses,或者两个都在implementation里uses。
6. 文件的共享,这是一个减少代码量和减少重复劳动,减少代码冗余的好方法。具体来说就是写一个公共的unit,把多个form都要用到的函数、过程、数据、类型等等都放在一起,这样便于管理和修改。在每个要用到该unit中的函数的unit中uses一下就可以了。不过有一点很重要,那就是要为每一个函数,过程写一个声明,这样的话别的unit才能够看见这些函数和过程。否则,仅仅在implementation中写了函数、过程的实现而不写声明的话,他们只在本unit可见,着一点很重要。切记,切记!
7. 对于命名规则我再多说一点,在给变量命名的时候要考虑到他的逻辑性。也就是说给这个变量起的名字是有意义的,能够让人一看见就能够知道他是干什么用的。这样不仅仅方便维护人员管理与维护,也方便自己理清思路,可以很快的判断出逻辑表达试。在这方面,逻辑变量的体现最为明显,比如: 定义一个逻辑变量 flag 这样我们就可以看出 flag 是标记的意思。那么当有标记也就是 flag=true 时,我们可以写成
if flag then ……
而不是写成 if flag=true then …… 对于前一种写法他的逻辑性更强,而且易于我们的逻辑思维。但是初学者往往会用后一种写法,这是对 Boolean 类型不太熟悉造成的,我们要养成习惯。尽量使自己的程序的逻辑性强,而且遵守编程规范,这样会给我们和他人节省很多宝贵的时间和精力。在给函数的形参和实参起名字的时候也要有意义,这样才不至于搞混淆。我就遇到了这样的情况,在该设计的 unit2 中,做的是有关积分的东西,积分分为上限和下限,这两个限定一旦搞错就会出现错误,如果是全部都搞错还比较容易纠正,上限和下限颠倒过来只是值变成相反数。如果一部分搞错,一部分是对的,那么就错的有够离谱了,这样的错误往往不能从错误的结果加以判断,只能分段查找程序中的问题。看是否在那里出错,如此而已,费时费力,所以要进行合理的命名是十分重要的。
8. 调试工作中很重要的一步就是确定错误的位置,这个工作已经由编译器代替我们完成了。有时后编译器完成的工作只是初步,具体的位置还是要求我们自己去找出来。有一些错误的定位由于某些原因会和实际出错的位置差别比较大,这些位置的查找是要靠编程的经验和仔细的观察才能够找出来的,在实际中,良好的书写习惯会帮助我们克服这些实际出错位置和报错位置差别比较大的错误。这些错误中,比较典型的就是:多写或漏写 begin 或 end ,使之没有配对,从而造成程序的出错。有时候对 ;,(),[ ] 的使用也会造成同样的效果。克服这些错误就是要养成良好的书写习惯,比如: 在写程序的时候,一般在同一个循环内的语句都要向左对齐,一个二重循环的内循环要向内缩进一些,用来区别与外循环。在写 begin 和end 的时候就要一起写,对于括号(),[ ] 也是一样的处理,这样一起写的好处是以后不会漏掉另一个这样我们在写程序的时候就不需要记着前面我还在哪儿用了一个 begin,这里要写一个 end 与之对应。这样的好处在写函数表达式的时候显得尤其好用。在一个比较复杂的表达式中如果括号的层数超过3层就十分难以看清楚,这时良好的书写习惯就会帮了你的大忙了。你不会为了找另一个对应的括号而瞅花了眼,只要一步一步的从里到外的写出来,不会有太大困难。当然,别人要看懂是要花一定时间的,但是你写的却不会有错。
除了定位错误之外还要改正错误,当然我前面讲的方法大都是改正错误,但是最有效的就是看编译器给你的提示信息。这是我们查找错误的出发点,其中的一些小错误很容易就能看出来,比如:变量没有声明,在 else 前加了 ; ,类型不对应,函数参数传递错误等。而有一些错误是从信息中看不出来的,语法的错误不太难纠正,但是算法的错误就不好纠正了。这就要求我们用调试工具来一步步找出错误。其中最常用到的工具就是断点、单步进入、单步跳出,以及 观察变量。断点可以帮助我们将程序分段,看一看到底是在那一部分出了错,以便于查找。不过在调试完毕以后一定要记着把断点都清除掉,不然你的程序会在运行时突然中断,而你也查不出错。我个人比较爱用的是单步进入+单步跳出+Add Watch 这是调试程序的一个必由之路。写程序并不难,调试程序的高手才是很有本事的人,我一向是这么认为。单步进入 就是一步一步的调试,边解释边运行,这样便于我们找到错误。单步跳出 是为了帮助单步进入的,单步跳出对于一个过程或者是一个函数是将其看成一条语句,一下子跳过去,这样节省了那些正确的部分的检查。也可以用 运行到光标处这一项,两者都是缩短正确的程序段的检查时间。 Add Watch 是一个非常好用的东东,你可以在里面加入你想要监视的变量名称。则该变量的值会在表中显示出来,他配合单步操作,可以看见你想要知道的变量在每一步的变化情况是否和预料的一样变化。如果有出入则可以对该语句进行分析,从而找出出错的原因。可见单步操作+Add Watch 是调试程序必不可少的步骤,也是最有效的方法。但是,在程序比较复杂,调用函数比较多的时候,单步进入就不是那么好用了。因为往往在一个按钮事件中有用到好几个procedure 或者 function ,在每一个function或者procedure中往往又要调用别的function和 procedure。所以用单步进入很容易让人搞的晕头转向,不知所云。假如用到了第三方的控件,那就更不知道程序运行到什么地方了。所以,在程序量比较大,而且调用函数比较多的地方,单步操作就不太好用了。这时我们最好使用断点工具,他使用的意义和单步操作的意义是一样的,就是为了看清楚程序是否按我们所设计的思路在运行。断点的好处就是在于他可以使我们在希望停止的地方暂停,而其余不需要停止的地方则按原来的速率进行运行。结合Add Watch,我们就可以看见整个程序运行的过程了。断点的增加是比较有讲究的,一般情况下都要在以下几个地方加上断点: ①循环语句的入口处,在此处加上断点可以监视进入循环的数据,看看是否在循环以前就有错误。在每个循环前放一个就象作一个备份一样,看看会出什么样的结果。 ②在循环语句的最后一句设置一个断点,这样就可以监视每一次的循环过程,看看是在那一次循环出错。还有一个好处就是这样在循环内部设置一个断点可以防止无限循环。万一你编写的程序出错,出现了无限循环,这样会造成系统资源急剧下降,从而容易造成死机。如果你没有备份这次所做的工作的话,那真是欲哭无泪了。 ③在循环完成处设置一个断点,这样可以把入口处和出口处进行对照,看一看该循环是否按照你的要求正常工作,可以很快的判断出循环语句的正确性。 ④在判断语句前设置断点,这个断点主要的目的是观察此时的Add Watch 中的值。此时的各个判断参数的值可以看出程序是否按照正常步骤进行,或者在判断语句中出现了逻辑错误,这些都是很常见的断点设置处。
9. 设置procedure 和function 的各种用法。不要小看了var 这三个字,我们通常在定义变量中使用,而他的一个很重要的用途就是在 procedure 或者function中设置变参。变参在普通的procedure 和function中不是很常用,但是却有着十分重要的用途。如果你想编写一个函数,要返回两个值,因为这两个值是在同一个函数中得出的。所以想要有两个返回值,但是函数只能有一个返回值,这时该如何解决呢?变参就派上了用场,在函数和过程中使用的变参会改变实参的值就象传址函数一样。这样的话,在一个过程或者函数中定义多个变参就可以返回多个参数了。