常数与运行时类型信息编程_第1页
常数与运行时类型信息编程_第2页
常数与运行时类型信息编程_第3页
常数与运行时类型信息编程_第4页
常数与运行时类型信息编程_第5页
已阅读5页,还剩24页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第5章 集合、常数与运行时类型信息编程定义好的抽象,可以解决许多编程方面的障碍。在面向对象语言中,所谓的抽象就是“创建类”。在某种程度上确实如此。许多项目的失败就是因为创建的类太少,结果形成了一些庞大的类,它们做的事情太多,因而难于维护。好的抽象也可以在较低层次上定义。像Delphi这样的强类型化编译器、定义在问题域中有具体意义的类型等,都可能是有帮助的。一般的,进行较为精确的类型定义,通常可以更好地定义类的属性。大多数属性的特征可以通过整数和数组进行表达,但相比而言,由内建类型派生而来的集合、范围、常数、数组以及枚举等更有意义。Object Pascal是一种具有很强的表达能力的面向对象编程

2、语言,它有助于定义在特定问题域的上下文中具有意义的类型。例如,如果只有某个特定范围内的值有意义,就定义范围、集合或枚举类型来命名这些值所代表的数据。本章示范了如何使用Object Pascal中的这些概念,以有助于定义好的抽象。这些技术可以使您的代码具有更强的可读性,而且比使用内建数据类型所需的错误检查代码要少。5.1 不可变常数常数很好用。常数是现存的最为可靠的代码之一。定义常数之后,无论如何其值都是可以依赖的。不用担心,不存在偶然或有意的误用。在Delphi中有许多方法来使用常数,以使得代码更加可靠。注意:Delphi支持类型化常数,它们的值是可变的。关于可赋值的常数,更多的信息请参见5.

3、1.3节“使用const创建静态本地变量”。5.1.1 全局与本地常数当变量定义在本地作用域中时,可以访问该作用域的代码均可使用该变量。临时使用全局或本地变量可能导致有害的问题,特别是对于多线程应用程序,其中一个线程可能依赖于某个值,而另外一个线程正在改变该值。如果一个值需要保持不变,则应该用const来表示。全局常数是定义在单元的接口部分的常数。而本地常数是定义在实现部分的常数。另外,可能会在许多地方重用的值应该定义为常数。假定Pi的值在您的整个程序中都是有意义的,则应在接口部分将名字Pi作为常数引入,并将其值初始化为具有正确的有效数字位数的Pi值,以满足您的需要。注意:新的单元ConvUt

4、ils.pas包含有数以百计的常数和转换单元。尽管它包含了秒差距与米的转换常数值,但并未包含Pi的常数值。System.pas单元中包含了函数Pi,返回Pi值的extended类型的浮点值。我们的目标是在尽可能狭窄的作用域中定义常数。如果一个常数只在过程块中需要,那么该过程就是合适的作用域。使作用域变窄背后的思想在于,要尽量减少使用代码的程序员在理解代码目的时所需要进行思考的事物的数目。常数的语法部分依赖于其所定义的上下文。本地、全局、过程常数的通常形式如下:const name = value;或,const name : type = value;const是关键字,表示其后是常数。对于一

5、个常数列表中的所有常数,仅需要键入const一次。例如,在实现部分定义三个常数,如下所示。implementationconstI : Integer = 3;S = 'Bachman Turner Overdrive'F : Double = 4000000000000.0;有许多途径来使用常数,可以使得程序更加可靠。对于常数的所有变体的语法规则,请察看上下文帮助,在索引中查找grammar。更多的例子见下文。5.1.2 常数参数当过程不应改变某个参数的值时,应把该参数声明为常数参数。如果包括了const限定符,可以保证该值不被改变。保证总是难于得到,因此能够得到保证确实不错

6、。常数参数可以有默认值。下面的代码演示了具有默认值的常数参数。procedure DisplayBandName( const Value : String = 'R.E.O.' );beginShowMessage( Value );end;Procedure SomeProc;constBTO = 'Bachman Turner Overdrive'beginDisplayBandName;end;DisplayBandName过程中定义了一个具有默认值的参数。如果不传递参数,Value参数的值将是R.E.O.。如果把常数BTO传递给DisplayBandN

7、ame,那么ShowMessage函数将显示Bachman Turner Overdrive。常数参数的存在保证了调用的方法不会在无意中改变传递的参数值。使用const要远胜于希望和祈祷。5.1.3 使用const创建静态本地变量定义在过程中的变量在栈上分配内存空间。常数通过编译嵌入到代码中,只存在于所定义的过程中。当过程调用或退出时,栈内存空间像手风琴一样来回伸缩。通常,在过程中引入的名字具有过程作用域。即,该名字和值只在所定义的作用域中可访问。有时,您可能需要各种占位符,即只在过程作用域可访问的名字,而在过程返回后依然保持其值。C和C+称之为静态变量。Delphi用可赋值常数来产生同样的效

8、果。使用下面的语法您可以定义一个变量,它看上去是常数,但实际上是可变的静态变量。Procedure MutableConst;constI : Integer = 0;beginInc(I);ShowMessage(IntToStr(I);end;/ .for I := 0 to 3 do MutableConst;在上面的MutableConst过程中定义一个类型化的可赋值常数,常数的值在该过程的各次调用之间仍然可以保持。最后一行的for语句调用MutableConst四次,最后一次调用在ShowMessage的对话框中显示值为4。默认情况下,类型化常数是可赋值的。可以通过$J+编译器指令进

9、行改变;或者在Project Options对话框中的Compiler属性页中改变Assignable typed constants复选框,如图5.1所示。图5.1 默认情况下,类型化常数是可赋值的,并且可以在对其所定义的过程的后续调用之间维持其值。要使其不可赋值,对Project Options对话框中的Compiler属性页的Assignable typed constants复选框取消选定即可。默认情况下,该复选框是选定的可赋值类型化常数使得可以在过程中定义占位符,每次该过程调用时都可以维护该值。通过使用可赋值类型化常数,可以模拟静态特性(有关静态特性的更多知识,请阅读第7章)。5.1

10、.4 数组常数对您的武器库来说,数组常数是另外一项可以添加的工具。也许您不会每天都用到数组常数,但在日常编程中确实有一些数组常数的例子。考虑下列例子。Procedure ArrayExamples;constDaysOfWeek : array1.7 of string = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' );MonthsOfYear : array1.12 o

11、f string = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August','September', 'October', 'November', 'December' );EXAMPLE1 = 'February 12, 1966 occurred on a %s'EXAMPLE2 = '

12、;The fourth month is %s'varOutput : string;Day : Integer;beginDay := DayOfWeek( StrToDate('02/12/1966');Output := Format( EXAMPLE1, DaysOfWeekDay );ShowMessage( Output );Output := Format( EXAMPLE2, MonthsOfYear4 );ShowMessage(Output);end;DaysOfWeek数组包含了7个元素,都是字符串。MonthsOfYear数组包含了12个元素,也

13、都是字符串。两个数组都初始化为常数数组。Begin块语句的第2行使用星期中某天对应的数字来索引数组。第1次调用ShowMessage过程的输出为February 12, 1966 occurred on a Saturday。Begin块语句的第4行对该年的月份执行了一个简单的操作。当然您也可以把上面的代码写成由嵌套if条件测试组成的case语句,但常数数组在紧凑的操作中生成了优化而更小的代码。考虑下一个例子,其中用if条件测试来比较激活状态以设置控件的背景颜色,也提供了用数组实现的相同功能的代码。if( Edit1.Enabled = False ) thenbeginEdit1.Enabl

14、ed := True;Edit1.Color := clWhite;endelse / TruebeginEdit1.Enabled := False;Edit1.Color := clBtnFace;end;上面的代码计算了TEdit控件(随机选定)的Enabled状态,对该状态取反,并相应地设置颜色。该代码实用而直接,但使用常数数组可使之更为有效。使用常数数组的修订版本如下。constColors : arrayBoolean of TColor = (clBtnFace, clWhite);beginEdit1.Enabled := Not Edit1.Enabled;Edit1.Col

15、or := ColorsEdit1.Enabled;end;常数数组使得我们可以将代码缩减到原来的五分之一。使用单目not操作符来执行Enabled状态的切换,用Enabled特性的布尔值来索引常数数组Colors。在使用布尔值作索引时,False的值较小。上面的代码更加紧凑,既小且快。下一节的内容是有关记录常数的,阅读时请注意其中一个用到记录数组常数的例子。5.1.5 记录常数记录常数是类型为记录的常量数据。一个很普遍的记录是TPoint。TPoint在笛卡尔坐标系中定义了两个坐标。TPoint在Windows.pas单元中如下定义:TPoint = recordx: Longint;y:

16、Longint;end;要初始化常量记录,需要以fieldname : value的形式指定每个字段,每个字段的名字与值用冒号分隔。这里有一个例子。const Point : TPoint = (X:100; Y:100);常量记录数组需要初始化每个数组元素,对于构成记录值的字段,将名字和值对的集合用括弧括起来。这里是一个由四个点构成的数组。constPoints : array0.3 of TPoint = (X:10;Y:10), (X:10;Y:100), (X:100;Y:100), (X:100; Y:10);Procedure DrawRect( const Points : Ar

17、ray of TPoint );varI : Integer;beginCanvas.PenPos := Points0;for I := Low(Points) to High(Points) doCanvas.LineTo( PointsI.X, PointsI.Y );Canvas.LineTo( Points0.X, Points0.Y );end;由于并未考虑监视器屏幕的高宽比,该数组只是粗略地定义了如图5.2所示的正方形。数组Points被传递给代码中的DrawRect过程,用以生成如图5.2所示的正方形。图5.2 利用LineTo方法和TPoint记录组成的 数组,在窗体的画布上

18、所画出的正方形通过将一些基本的Object Pascal术语联合起来,可以很容易地创建很多种常量数据,用于代表各种各样的信息。使用记录和数组常数,可以使您的代码更具有表现力。通过把精确定义的数据映射到问题域的信息,对代码控制得越好,管理数据所需的代码就越少。5.1.6 过程常数过程常数是用const修饰的名字,其数据类型为过程类型。一般来说,过程类型只是指向过程的指针,它使得可以把过程和函数赋值给类型与其相匹配的变量和参数。过程类型的更多知识可以阅读第6章。一个过程类型的例子就是定义在classes.pas单元中的TNotifyEvent。下面是classes.pas的摘录,给出了TNotif

19、yEvent的定义。type TNotifyEvent = procedure (Sender: TObject) of object;从列出的代码可以看出,TNotifyEvent类型是由过程组成的,它们有一个TObject类型的参数,名字为Sender。类型定义结尾的of object表示TNotifyEvent类型是被称为方法指针的特定过程类型。TNotifyEvent看起来很熟悉,因为它就是Object Inspector中许多事件特性的类型。双击空白窗体,Delphi将为窗体的OnCreate事件特性创建如下的空方法定义。procedure TForm1.FormCreate(Sen

20、der: TObject);beginend;注意:按照惯例,Delphi使用On前缀表示该特性为事件特性。On暗示着对动作的响应,即事件。代码中的TForm1.部分表示该方法属于TForm1类。FormCreate表示它是OnCreate事件的处理程序,将类名和方法名从定义中剥离,余下的就是procedure (Sender:TObject),恰好可以与TNotifyEvent匹配。在Delphi中选择OnCreate事件特性,按键F1,将显示CustomForm.OnCreate的上下文帮助。帮助文档清楚地指出,OnCreate定义为特性OnCreate : TNotifyEvent; 即

21、类型为TNotifyEvent的特性。5.1.7 指针常数无论使用任何语言,我们的思维都只是受限于用以表达思想的语言以及我们对它掌握的熟练程度。一些术语可能不像其余的那样常用。指针常量就是其中的一个。在日常的Windows应用编程中,指针可能不太常用,不过Delphi并不限制您只能编写典型的Windows风格的程序。指针常量就是指向特定地址的指针。下面的代码琐碎而令人困惑,但它演示了与指针常量有关的必要机制。typeTDateTimeFunc = function : TDateTime;constNowP : Pointer = SysUtils.Now;varMyNow : TDateTi

22、meFunc absolute NowP;第2行定义了一个过程类型,它是指向函数的指针,返回TDateTime值。常量指针被初始化为SysUtils.pas中定义的Now函数地址。变量MyNow类型为TDateTimeFunc(在类型部分定义),将编译为SysUtils.Now函数的绝对地址。5.1.8 用于初始化常量的过程在前面关于过程常数的章节中,您已经知道过程类型可用于定义常数并初始化为某一特定的过程。对于前一节中的例子,无须使用Pointer即可定义对SysUtils.Now函数的常量引用。typeTDateTimeFunc = function : TDateTime;constCo

23、nstNow : TDateTimeFunc = SysUtils.Now;调用ConstNow与调用SysUtils.Now具有相同的效果。由于Delphi支持过程类型,因此用这种形式定义指向过程的变量或常数更为可取。无论对于哪种类型,均可使用Pointer来指向一个特定的内存地址。5.2 枚举的使用枚举是代表值的名字列表。如果使用枚举更有意义,那么它比内建数据类型更为可取。枚举的一个完美的例子是TFontStyle。有四种基本的字体风格:黑体、斜体、加下划线的、加删除线的。很清楚,可以用整数来存储字体风格的状态,但对特定风格进行列表更有意义。graphics.pas单元如下定义了TFont

24、Style类型。type TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);定义TFontStyle使得程序员可以声明TFontStyle类型的变量,保证了所有赋予TFontStyle类型变量的值都是有效的,而且不必进行访问检查和错误检查。由于编译器是强类型的,只有四个可能值之一才能赋予TFontStyle变量。简言之,所有困难的工作都由编译器来做,因而代码对于程序员更加可读。5.2.1 用枚举定义数组边界按照经验规则,相对于原始的数组,TList和TCollection更为可取。回顾使用对象而不是数组来存储数据的原因:表和集合

25、类可以动态地伸缩,其中包括了范围检查及其他一些功能,如排序和查找等。使用数组则需要实现所有这些功能。有些情况下,您可能需要使用数组。除去索引范围检查的一种方法是:使用枚举作为索引的类型,从而使数据的范围精确化。这里有一个例子演示了TNote类型,它使用Windows API函数Beep来播放一个音符(音符的频率和长短是模拟的)。typeTNote = (doDo, doRe, doMi, doFa, doSo, doLa, doTi, doDo2 );Procedure PlayNote( Note : TNote );constDoReMi : arrayTNote of Integer =

26、 (500, 600, 700, 800, 900, 1000, 1100, 1200 );beginWindows.Beep( DoReMeNote, 750 );Sleep(250);end;Procedure PlayNotes;varI : TNote;beginfor I := Low(TNote) to High(TNote) doPlayNote( I );end;第二行定义了一个枚举类型TNote,包含八个元素。PlayNote过程使用Low和High函数得到值的范围,然后对每个元素进行迭代。可以注意到索引I定义为TNote类型而不是整数。每次循环时,都以当前枚举值为参数调用P

27、layNote过程。PlayNote过程参数为TNote类型,过程中定义了一个索引为TNote类型的常量数组,并使用Windows API函数Beep(并非Delphi所提供的版本)来播放每个音符对应的频率。请注意并不需要范围检查,因为对于数组DoReMi,所有可能的TNote值都是可行的,因为它们都受到枚举范围的限制。定义枚举和其他精确化的类型具有累积效应。需要调试和测试的代码会逐渐减少,您的代码将更有效地运行。5.2.2 预定义枚举类型有许多预定义的枚举类型,实际上,枚举类型太多以至于无法全部涵盖。这种类型的参考手册最好留给在线帮助文档去作。如果您已经习惯于用以控制组件行为的枚举类型,就可

28、以更加出色地控制VCL。以TControlStyle为例。所有的控件都具有ControlStyle特性,该类型定义为枚举值的集合(有关集合操作和定义,更多的信息请参见下一节)。type TControlStyle = set of (csAcceptsControls, csCaptureMouse, csDesignInteractive, csClickEvents, csFramed, csSetCaption, csOpaque, csDoubleClicks, csFixedWidth, csFixedHeight, csNoDesignVisible, csReplicatable

29、, csNoStdEvents, csDisplayDragImage, csReflector, csActionClient, csMenuEvents);ControlStyle特性可以具有零个、一个或多个定义在上述枚举中的值。例如,如果ControlStyle具有csAcceptsControls值,则该控件就可以拥有其他的控件。例如窗体可以拥有控件,但默认情况下,栅格控件是无法拥有其他控件的。对于已有的控件,其行为是由作者定义的。但您总是可以子类化一个控件,根据您的需要调整其行为。例如,下面的类定义了TControlGrid类型,示范了如何在栅格单元中拥有其他控件(如图5.3所示)。

30、图5.3 可以在栅格单元中拥有其他控件的栅格控件TControlGrid = class(TStringGrid)privateFButton : TButton;protectedProcedure WMCommand( var Message : TWMCommand ); Message WM_COMMAND;Procedure OnClick( Sender : TObject );Procedure Paint; override;publicconstructor Create( AComponent : TComponent); override;destructor Destr

31、oy; override;end;注意:如果在设计时可以在栅格上绘制出控件,TControlGrid类就可以更有用。使用上面以及下面列出的代码,即可完成该栅格控件,从而可以在设计时和运行时动态地拥有控件。关于栅格组件的演示,参见该类包含一个private字段FButton,用于示范拥有控件的功能。protected message方法重载了WM_COMMAND的信息处理程序。这使得被拥有的控件可以接收到发送给它们的消息,但TStringGrid并未如此,因为在原来的设计中TStringGrid是无法拥有父控件的。OnClick事件处理程序用于演示TButton控件对用户输入的响应。构造函数和析

32、构函数中重载了ControlStyle特性,并对TButton控件进行分配和释放。 TControlGrid constructor TControlGrid.Create(AComponent: TComponent);begininherited Create(AComponent);ControlStyle := ControlStyle + csAcceptsControls;FButton := TButton.Create(Self);FButton.Parent := Self;FButton.BringToFront;FButton.OnClick := OnClick;Rep

33、aint;end;Procedure TControlGrid.Paint;begininherited Paint;FButton.Visible := (LeftCol = 1) and (TopRow = 1);FButton.Enabled := FButton.Visible;FButton.BoundsRect := CellRect( 1, 1 );end;destructor TControlGrid.Destroy;beginFButton.Free;inherited;end;procedure TControlGrid.OnClick(Sender: TObject);b

34、eginMessageDlg('Greetings Earthlings!', mtInformation, mbOK, 0);end;Procedure TControlGrid.WMCommand( var Message : TWMCommand );begininherited;if( ControlCount > 0 ) thenFindControl( Message.Ctl ).Dispatch(Message);end;构造函数ControlGrid调用TStringGrid的构造函数,创建按钮,并为按钮的OnClick事件设置处理程序。Paint方法被重

35、载,用于在单元1中绘制该单元的控件(1单元可见时)。对于动态的控件,跟踪该控件属于哪一个单元是必要的。析构函数释放了按钮,并调用TStringGrid的析构函数。当单击按钮时,OnClick方法将显示友好的问候。最后,WMCommand方法响应所有的消息,首先调用继承的消息处理程序,然后对拥有的控件分发消息(消息处理程序的更多知识请阅读第6章)。在逐渐掌握Delphi的体系结构之后,无论编写简单或还是复杂的控件,都可以利用已有类的属性进行简化,这样可以使程序更加灵活、实用。5.2.3 用于枚举类型的过程有一些函数是专门设计用于枚举类型的。表5.1列出用于枚举类型的过程,并描述了每个过程所执行的

36、操作。表5.1 用于枚举类型的过程过程描述Ord返回整数,表示与其位置相关的枚举值Pred返回在传给函数的值之前的枚举值Succ返回传给函数的值的下一个枚举值High返回最大的枚举值Low返回最小的枚举值枚举是有序类型,基于在类型定义中的出现次序,枚举元素自动地分配连续值,从第一个位置的0开始到最后一个位置的n-1结束。例如,可以使用High和Low函数得到枚举的上界和下界。如果包含了运行时类型信息(RTTI),则枚举的符号名也可以得到。下面列出的程序演示所有的五个枚举函数,以及如何包含RTTI信息。uses typinfo;$M+typeTEnums = ( Enum0, Enum1, En

37、um2, Enum3, Enum4);$M-procedure ShowEnum( Enum : TEnums );constMASK = '%s=%d'varName : String;Value : Integer;beginName := GetEnumName( TypeInfo(TEnums), Ord(Enum) );Value := GetEnumValue( TypeInfo(TEnums), Name );ShowMessage( Format( MASK, Name, Value );end;procedure TestEnumerated;beginSho

38、wEnum( Enum3 );ShowEnum( Pred( Enum3 );ShowEnum( Succ( Enum3 );ShowMessage( IntToStr(Ord( Enum4 ) );ShowEnum( Low(TEnums);ShowEnum( High( TEnums );end;注意:尽管Delphi本身也使用了GetEnumName和GetEnumValue函数,但由于某些原因,它们并未包括在上下文相关帮助中。RTTI用于特性的读写,以及OpenTools API(关于OpenTools API,请参考附录A)。第一行的uses语句表示应包括typinfo单元。该单元包

39、括了与运行时类型信息相关的过程,其中就有在本例中用到的GetEnumName和GetEnumValue。第四行定义了枚举类型TEnums,该定义包含在$M+和$M-编译器指令之间。$M指令指示编译器对TEnums类型加入运行时类型信息。第六行开始的ShowEnum过程示范了如何使用typinfo.pas中所定义的GetEnumName和GetEnumValue过程。这两个过程的第一个参数是指向TTypeInfo记录的指针。如代码所示,将枚举类型的名字传递给TypeInfo函数将返回指向类型信息记录的指针。GetEnumName中的第二个参数是枚举类型中某个特定元素所对应的有序数值。GetEnu

40、mValue则根据类型信息记录和枚举元素的名字返回对应的有序数值。TestEnumerated过程的输出如下:ShowEnum( Enum3 ); / outputs Enum3=3ShowEnum( Pred( Enum3 ); / outputs Enum2=2ShowEnum( Succ( Enum3 ); / outputs Enum4=4ShowMessage( IntToStr(Ord( Enum4 ) ); / outputs 4ShowEnum( Low(TEnums); / outputs Enum0=0ShowEnum( High( TEnums ); / outputs

41、Enum4=4某些低层的VCL过程使用了运行时类型信息。在创建组件和枚举时,运行时类型信息特别有用。枚举可以使代码更加健壮、富于表现力、可读性好。5.3 集 合 操 作集合通常表示一组相关的事物,如一组塔珀家用塑料制品或一组高尔夫球棍。集合操作是人类最早了解的数学知识之一(至少在美国和西欧是这样)。至少有三十年了,Sesame Street一直教着这首歌“Which one of these things is not like the other? Which one of these things just doesnt belong?”即,哪些是集合的成员而哪些不是?集合在实际的世界中是

42、很常见的,因此,很自然的,在抽象世界中应存在这样的术语,使得开发者可以表达集合的概念并对其进行算术操作。在Object Pascal中确实如此,我们只需将自己的理解映射到Delphi中的集合实现即可。5.3.1 理解集合以及set of语句集合是同一有序类型的值的聚集。集合的例子有:所有整数的集合、某个枚举类型中所有元素的集合或彩虹中所有元素的集合。在Object Pascal中的集合的大小限定到一个字节,这意味着一个特定集合的基类型必须限制到少于256个元素,而其有序值必须在0到255之间。集合的值的范围是其基类型的幂集,幂集就是包括空集在内一个集合的所有可能的子集的集合。定义集合的语法是:

43、SetType -> SET OF OrdinalType,其中OrdinalType定义为:OrdinalType -> (SubrangeType | EnumeratedType | OrdIdent)。就是说,集合定义在单元的类型部分,将一个名字与子界类型、枚举类型或有序类型的集合联系起来即可。OrdinalType所涉及的各种类型示范如下。TRangeSet = set of 0.255;TCharSet = set of char;TPrimaryColors = set of (pcRed, pcBlue, pcGreen);TRangeSet示范了一个由整数构成的子

44、界集合,其值由0到255。TCharSet定义了由三原色构成的枚举值的集合。定义集合类型后,则集合实例都是一些子集,其中包含了一些该类型的元素。5.3.2 使用集合构造器set of语句定义了一个类型,其中包括从某个有序类型而来的一个有限范围内的值。集合类型的变量可以是受类型定义约束的幂集的任一元素。要初始化集合类型的实例,可以使用集合构造器。集合构造器由 标识,其中包含一些由逗号或.分隔的值。考虑上一节的TCharSet集合。要构造一个包含大写字母的TCharSet变量,使用如下代码即可。varUpperCaseChars : TCharSet;beginUpperCaseChars :=

45、'A'.'Z'/ .end;变量UpperCaseChars是TCharSet的一个子集,初始化时包含了所有的大写字母。由于UpperCaseChars定义为变量,因此可以向集合添加成员。要使UpperCaseChars只包含大写字母字符,可以将其定义为常数并使用$J-编译器指令使该常量不可赋值,或者定义一个只包含大写字母的集合类型。const$J-UpperCaseChars : TCharSet = 'A'.'Z'$J+或者typeTUpperCaseChars = set of 'A'.'Z'

46、提示:默认情况下,Delphi中的类型化常数是可写的。要限制UpperCaseChars只包含从A到Z的字母,可以重新定义集合类型或者把可写类型化常数用编译器指令包裹起来,如下所示:$J- const UpperCaseChars : TCharSet = 'A'.'Z' $J+。现在,UpperCaseChars是个只包含字符A到Z的不可赋值的常数,而TUpperCaseChars类型的范围则限制为字符A到Z。可定义TUpperCaseChars类型的变量,例如:varUpperCaseChars : TUpperCaseChars;这样UpperCaseCh

47、ars的值就隐含地限制到了从A到Z。回想一下,默认情况下Delphi中的类型化常数是可写的(请参考上面的提示,其中简短地讨论了对代码的可能修改,从而使其目的性更强)。为包括大写字母和小写字母,扩展上面的集合构造器以包含小写字母。constAlphabeticChars : TCharSet = 'A'.'Z', 'a'.'z'现在TCharSet变量AlphabeticChars包含了所有大写和小写的字母字符。为简化集合代数,有许多操作符可以执行集合算术运算。5.3.3 集合操作符表5.2包含了集合的算术操作符的完整列表,并描述了

48、对集合进行的操作。所有的集合运算,其结果或者为布尔值,或者为新的子集。表5.2 集合操作符与结果类型,除了in以外,所有的集合操作符的两个操作数都是集合操作符操作结果例子+并集合Set1 + Set2-差集合Set1 Set2*交集合Set1 * Set2<=是的子集布尔值Set1 <= Set2>=是的超集布尔值Set1 >= Set2=相等布尔值Set1 = Set2<>不等布尔值Set1 <> Set2in是的成员布尔值Ordinal in Set1下面列出的代码示范了对四个集合执行的集合操作,它们都定义为字符集合的子集。typeTChar

49、Set = set of char;constA : TCharSet = 'A'.'M', 'R', 'S', 'U'B : TCharSet = 'B', 'G', 'H', 'L'.'Z'SubsetA : TCharSet = 'A'.'G'SupersetA : TCharSet = 'A'.'M', 'R', 'S', '

50、U', 'V'Procedure TForm1.DisplayResultset( OperationName : String; const CharSet : TCharSet );varI : Char;Count : Integer;beginMemo1.Lines.Add( '*' + OperationName + '*' );Count := 0;for I := Low(Char) to High(Char) doif( I in CharSet ) thenbeginMemo1.Lines.Add(I);Inc(Coun

51、t);end;Memo1.Lines.Add( '* Elem Count: ' + IntToStr(Count) + ' *' );end;Procedure TForm1.SetTests;constBOOLS : arrayBoolean of String = (' is False', ' is True' );beginMemo1.Clear;DisplayResultSet( 'union', A + B );DisplayResultSet( 'difference', A - B

52、 );DisplayResultSet( 'intersection', A * B );Memo1.Lines.Add( 'A < SubSetA (Not A >= SubSetA)' + BOOLS Not (A >= SubSetA) );Memo1.Lines.Add( 'A > SuperSetA (Not A <= SuperSetA)' + BOOLS Not (A <= SuperSetA) );Memo1.Lines.Add( 'A <= SupersetA' + BO

53、OLS A <= SuperSetA );Memo1.Lines.Add( 'A >= SubsetA' + BOOLSA >= SubSetA );Memo1.Lines.Add( 'A = B' + BOOLSA = B );Memo1.Lines.Add( 'A <> B' + BOOLSA <> B );Memo1.Lines.Add( '''A'' in B' + BOOLS 'A' in B );end;类型声明部分定义了一个由字

54、符组成的集合类型。常数声明部分包含了四个集合的定义,它们是字符集合的子集。集合A和B是不同的集合,SubsetA初始化为集合A的子集,而SuperSetA初始化为集合A的超集。DisplayResultSet方法使用in操作符来测试某个特定的字符是否是结果集合的成员。例子的输出如下。· A与B的并集A+B是所有大写字母字符的集合,因为所有的大写字母字符或者在A中或者在B中。· A与B的差集A-B包含字母A、C、D、E、F、I、J、K,因为A的这些元素不是B的成员。B-A则得到一个不同的结果集合。· A与B的交集A*B包含字母B、G、H、L、M、R、S、U,因为A和

55、B中均包含这些元素。· 如果SubSetA是A的子集,则用Not (A >= SubSetA)实现的A < SubSetA结果为False。· 如果SuperSetA是A的超集,则用Not (A <= SuperSetA)实现的A > SuperSetA结果为False。· A <= SuperSetA的结果为True,因为SuperSetA中包含所有A中的元素以及V。· A >= SubSetA的结果也是True,因为SubSetA中只包含A的部分元素。· A = B结果为False,因为A中的所有元素都不

56、在B中。· A <> B结果为True(见前一项条件测试A = B)。· 字符A不是集合B的成员,因此Ain B结果为False。对谓词与命题的演算,定义了用于集合的逻辑操作和谓词语句的代数规则。但不要假定用户也上过离散数学的课程,应该考虑把繁复的集合操作分解为对中间结果进行运算的单一的集合操作。集合代数演算基本的四个代数定律对集合逻辑也是适用的。这意味着集合具有传递性,即如果A = B而且 B = C则A = C;集合具有对称性,即A = A;集合具有交换性(对集合的差不适用),即A + B = B + A;集合具有分配性,A * B + A * C = A

57、* ( B + C )。如果需要,可以用这四个基本的数学定律简化复杂的集合等式。下列代码示范了集合的分配律、对称律以及交换律,这些定律都是对基类型为byte的数集验证的。typeTSet = set of byte;constSet1 : TSet = 1, 2, 3, 4;Set2 : TSet = 3, 4, 5, 6;Set3 : TSet = 5, 6, 7, 8;procedure DisplayResult( ASet : TSet );varI : Byte;beginfor I := Low(Byte) to High(Byte) doif( I In ASet ) thenF

58、orm1.Memo1.Lines.Add( IntToStr(I);end;procedure SetTest;constBOOLS : arrayBoolean of string = ('False', 'True');varResultSet : TSet;begin/ Distributive Law/ ResultSet := (Set1 * Set3) + (Set2 * Set3);ShowMessage( BOOLS Set3 * (Set1 + Set2) = (Set3 * Set1) + (Set3 * Set2) );ResultSet := Set3 * (Set1 + Set2);DisplayResult( ResultSet );/ reflexive A + B = B + AShowMessage( BOOLSSet1 + Set2 = Set2 + Set1 )

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论