当前位置:首页 > 商业/管理/HR > 管理学资料 > 第5章 集合、常数与运行时类型信息编程
第5章集合、常数与运行时类型信息编程定义好的抽象,可以解决许多编程方面的障碍。在面向对象语言中,所谓的抽象就是“创建类”。在某种程度上确实如此。许多项目的失败就是因为创建的类太少,结果形成了一些庞大的类,它们做的事情太多,因而难于维护。好的抽象也可以在较低层次上定义。像Delphi这样的强类型化编译器、定义在问题域中有具体意义的类型等,都可能是有帮助的。一般的,进行较为精确的类型定义,通常可以更好地定义类的属性。大多数属性的特征可以通过整数和数组进行表达,但相比而言,由内建类型派生而来的集合、范围、常数、数组以及枚举等更有意义。ObjectPascal是一种具有很强的表达能力的面向对象编程语言,它有助于定义在特定问题域的上下文中具有意义的类型。例如,如果只有某个特定范围内的值有意义,就定义范围、集合或枚举类型来命名这些值所代表的数据。本章示范了如何使用ObjectPascal中的这些概念,以有助于定义好的抽象。这些技术可以使您的代码具有更强的可读性,而且比使用内建数据类型所需的错误检查代码要少。5.1不可变常数常数很好用。常数是现存的昀为可靠的代码之一。定义常数之后,无论如何其值都是可以依赖的。不用担心,不存在偶然或有意的误用。在Delphi中有许多方法来使用常数,以使得代码更加可靠。注意:Delphi支持类型化常数,它们的值是可变的。关于可赋值的常数,更多的信息请参见5.1.3节“使用const创建静态本地变量”。5.1.1全局与本地常数当变量定义在本地作用域中时,可以访问该作用域的代码均可使用该变量。临时使用全局或本地变量可能导致有害的问题,特别是对于多线程应用程序,其中一个线程可能依赖于某个值,而另外一个线程正在改变该值。如果一个值需要保持不变,则应该用const来表示。全局常数是定义在单元的接口部分的常数。而本地常数是定义在实现部分的常数。另外,可能会在许多地方重用的值应该定义为常数。假定Pi的值在您的整个程序中都是有意义的,则应在接口部分将名字Pi作为常数引入,并将其值初始化为具有正确的有效数字位数的Pi值,以满足您的需要。注意:新的单元ConvUtils.pas包含有数以百计的常数和转换单元。尽管它包含了秒差距与米的转换常数值,但并未包含Pi的常数值。System.pas单元中包含了函数Pi,返回Pi值的extended类型的浮点值。我们的目标是在尽可能狭窄的作用域中定义常数。如果一个常数只在过程块中需要,那么该过程就是合适的作用域。使作用域变窄背后的思想在于,要尽量减少使用代码的程序员在理解代码目的时所需要进行思考的事物的数目。常数的语法部分依赖于其所定义的上下文。本地、全局、过程常数的通常形式如下:constname=value;或,constname:type=value;const是关键字,表示其后是常数。对于一个常数列表中的所有常数,仅需要键入const一次。例如,在实现部分定义三个常数,如下所示。第5章集合、常数与运行时类型信息编程108implementationconstI:Integer=3;S='BachmanTurnerOverdrive';F:Double=4000000000000.0;有许多途径来使用常数,可以使得程序更加可靠。对于常数的所有变体的语法规则,请察看上下文帮助,在索引中查找grammar。更多的例子见下文。5.1.2常数参数当过程不应改变某个参数的值时,应把该参数声明为常数参数。如果包括了const限定符,可以保证该值不被改变。保证总是难于得到,因此能够得到保证确实不错。常数参数可以有默认值。下面的代码演示了具有默认值的常数参数。procedureDisplayBandName(constValue:String='R.E.O.');beginShowMessage(Value);end;ProcedureSomeProc;constBTO='BachmanTurnerOverdrive';beginDisplayBandName;end;DisplayBandName过程中定义了一个具有默认值的参数。如果不传递参数,Value参数的值将是‘R.E.O.’。如果把常数BTO传递给DisplayBandName,那么ShowMessage函数将显示BachmanTurnerOverdrive。常数参数的存在保证了调用的方法不会在无意中改变传递的参数值。使用const要远胜于希望和祈祷。5.1.3使用const创建静态本地变量定义在过程中的变量在栈上分配内存空间。常数通过编译嵌入到代码中,只存在于所定义的过程中。当过程调用或退出时,栈内存空间像手风琴一样来回伸缩。通常,在过程中引入的名字具有过程作用域。即,该名字和值只在所定义的作用域中可访问。有时,您可能需要各种占位符,即只在过程作用域可访问的名字,而在过程返回后依然保持其值。C和C++称之为静态变量。Delphi用可赋值常数来产生同样的效果。使用下面的语法您可以定义一个变量,它看上去是常数,但实际上是可变的静态变量。ProcedureMutableConst;constI:Integer=0;beginInc(I);ShowMessage(IntToStr(I));end;//...forI:=0to3doMutableConst;在上面的MutableConst过程中定义一个类型化的可赋值常数,常数的值在该过程的各次调用之间仍然可以保持。昀后一行的for语句调用MutableConst四次,昀后一次调用在ShowMessage的对话框中显示值为4。默认情况下,类型化常数是可赋值的。可以通过$J+编译器指令进行改变;或者在ProjectOptions对话框中的Compiler属性页中改变Assignabletypedconstants复选框,如图5.1所示。第5章集合、常数与运行时类型信息编程109图5.1默认情况下,类型化常数是可赋值的,并且可以在对其所定义的过程的后续调用之间维持其值。要使其不可赋值,对ProjectOptions对话框中的Compiler属性页的Assignabletypedconstants复选框取消选定即可。默认情况下,该复选框是选定的可赋值类型化常数使得可以在过程中定义占位符,每次该过程调用时都可以维护该值。通过使用可赋值类型化常数,可以模拟静态特性(有关静态特性的更多知识,请阅读第7章)。5.1.4数组常数对您的武器库来说,数组常数是另外一项可以添加的工具。也许您不会每天都用到数组常数,但在日常编程中确实有一些数组常数的例子。考虑下列例子。ProcedureArrayExamples;constDaysOfWeek:array[1..7]ofstring=('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');MonthsOfYear:array[1..12]ofstring=('January','February','March','April','May','June','July','August','September','October','November','December');EXAMPLE1='February12,1966occurredona%s';EXAMPLE2='Thefourthmonthis%s';varOutput:string;Day:Integer;beginDay:=DayOfWeek(StrToDate('02/12/1966'));Output:=Format(EXAMPLE1,[DaysOfWeek[Day]]);ShowMessage(Output);Output:=Format(EXAMPLE2,[MonthsOfYear[4]]);ShowMessage(Output);end;DaysOfWeek数组包含了7个元素,都是字符串。MonthsOfYear数组包含了12个元素,也都是字符串。两个数组都初始化为常数数组。Begin块语句的第2行使用星期中某天对应的数字来索引数组。第1次调用ShowMessage过程的输出为‘February12,1966occurredonaSaturday’。Begin块语句的第4行对该年的月份执行了一个简单的操作。当然您也可以把上面的代码写成由嵌套if条件测试组成的case语句,但常数数组在紧凑的操作中生成了优化而更小的代码。考虑下一个例子,其中用if条件测试来比较激活状态以设置控件的背景颜色,也提供了用数组实现的相同功能的代码。第5章集合、常数与运行时类型信息编程110if(Edit1.Enabled=False)thenbeginEdit1.Enabled:=True;Edit1.Color:=clWhite;endelse//TruebeginEdit1.Enabled:=False;Edit1.Color:=clBtnFace;end;上面的代码计算了TEdit控件(随机选定)的Enabled状态,对该状态取反,并相应地设置颜色。该代码实用而直接,但使用常数数组可使之更为有效。使用常数数组的修订版本如下。constColors:array[Boolean]ofTColor=(clBtnFace,clWhite);beginEdit1.Enabled:=NotEdit1.Enabled;Edit1.Color:=Colors[Edit1.Enabled];end;常数数组使得我们可以将代码缩减到原来的五分之一。使用单目not操作符来执行Enabled状态的切换,用Enabled特性的布尔值来索引常数数组Colors。在使用布尔值作索引时,False的值较小。上面的代码更加紧凑,既小且快。下一节的内容是有关记录常数的,阅读时请注意其中一个用到记录数组常数的例子。5.1.5记录常数记录常数是类型为记录的常量数据。一个很普遍的记录是TPoint。TPoint在笛卡尔坐标系中定义了两个坐标。TPoint在Windows.pas单元中如下定义:TPoint=recordx:Longint;y:Longint;end;要初始化常量记录,需要以fieldname:value的形式指定每个字段,每个字段的名字与值用冒号分隔。这里有一个例子。constPoint:TPoint=(X:100;Y:100);常量记录数组需要初始化每个数组元素,对于构成记录值的字段,将名字和值对的集合用括弧括起来。这里是一个由四个点构成的数组。constPoints:array[0..3]ofTPoint=((X:10;Y:10),(X:10;Y:100),(X:100;Y:100),(X:100;Y:10));ProcedureDrawRect(constPoints:ArrayofTPoint);varI:Integer;beginCanvas.PenPos:=Points[0];forI:=Low(Points)toHigh(Points)doCanvas.LineTo(Points[I].X,Points[I].Y);Canvas.LineTo(Points[0].X,Points[0].Y);end;第5章集合、常数与运行时类型信息编程111由于并未考虑监视器屏幕的高宽比,该数组只是粗略地定义了如图5.2所示的正方形。数组Points被传递给代码中的DrawRect过程,用以生成如图5.2所示的正方形。图5.2利用LineTo方法和TPoint记录组成的数组,在窗体的画布上所画出的正方形通过将一些基本的ObjectPascal术语联合起来,可以很容易地创建很多种常量数据,用于代表各种各样的信息。使用记录和数组常数,可以使您的代码更具有表现力。通过把精确定义的数据映射到问题域的信息,对代码控制得越好,管理数据所需的代码就越少。5.1.6过程常数过程常数是用const修饰的名字,其数据类型为过程类型。一般来说,过程类型只是指向过
本文标题:第5章 集合、常数与运行时类型信息编程
链接地址:https://www.777doc.com/doc-5317480 .html