delphi第5章集合常数与运行时类型信息.docx
- 文档编号:8230513
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:32
- 大小:73.53KB
delphi第5章集合常数与运行时类型信息.docx
《delphi第5章集合常数与运行时类型信息.docx》由会员分享,可在线阅读,更多相关《delphi第5章集合常数与运行时类型信息.docx(32页珍藏版)》请在冰豆网上搜索。
delphi第5章集合常数与运行时类型信息
第5章集合、常数与运行时类型信息编程
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一次。
例如,在实现部分定义三个常数,如下所示。
implementation
const
I:
Integer=3;
S='BachmanTurnerOverdrive';
F:
Double=4000000000000.0;
有许多途径来使用常数,可以使得程序更加可靠。
对于常数的所有变体的语法规则,请察看上下文帮助,在索引中查找grammar。
更多的例子见下文。
5.1.2常数参数
当过程不应改变某个参数的值时,应把该参数声明为常数参数。
如果包括了const限定符,可以保证该值不被改变。
保证总是难于得到,因此能够得到保证确实不错。
常数参数可以有默认值。
下面的代码演示了具有默认值的常数参数。
procedureDisplayBandName(constValue:
String='R.E.O.');
begin
ShowMessage(Value);
end;
ProcedureSomeProc;
const
BTO='BachmanTurnerOverdrive';
begin
DisplayBandName;
end;
DisplayBandName过程中定义了一个具有默认值的参数。
如果不传递参数,Value参数的值将是‘R.E.O.’。
如果把常数BTO传递给DisplayBandName,那么ShowMessage函数将显示BachmanTurnerOverdrive。
常数参数的存在保证了调用的方法不会在无意中改变传递的参数值。
使用const要远胜于希望和祈祷。
5.1.3使用const创建静态本地变量
定义在过程中的变量在栈上分配内存空间。
常数通过编译嵌入到代码中,只存在于所定义的过程中。
当过程调用或退出时,栈内存空间像手风琴一样来回伸缩。
通常,在过程中引入的名字具有过程作用域。
即,该名字和值只在所定义的作用域中可访问。
有时,您可能需要各种占位符,即只在过程作用域可访问的名字,而在过程返回后依然保持其值。
C和C++称之为静态变量。
Delphi用可赋值常数来产生同样的效果。
使用下面的语法您可以定义一个变量,它看上去是常数,但实际上是可变的静态变量。
ProcedureMutableConst;
const
I:
Integer=0;
begin
Inc(I);
ShowMessage(IntToStr(I));
end;
//...
forI:
=0to3doMutableConst;
在上面的MutableConst过程中定义一个类型化的可赋值常数,常数的值在该过程的各次调用之间仍然可以保持。
最后一行的for语句调用MutableConst四次,最后一次调用在ShowMessage的对话框中显示值为4。
默认情况下,类型化常数是可赋值的。
可以通过$J+编译器指令进行改变;或者在ProjectOptions对话框中的Compiler属性页中改变Assignabletypedconstants复选框,如图5.1所示。
图5.1默认情况下,类型化常数是可赋值的,并且可以在对其所定义的过程的后续调用之间维持其值。
要使其不可赋值,对ProjectOptions对话框中的Compiler属性页的Assignabletypedconstants复选框取消选定即可。
默认情况下,该复选框是选定的
可赋值类型化常数使得可以在过程中定义占位符,每次该过程调用时都可以维护该值。
通过使用可赋值类型化常数,可以模拟静态特性(有关静态特性的更多知识,请阅读第7章)。
5.1.4数组常数
对您的武器库来说,数组常数是另外一项可以添加的工具。
也许您不会每天都用到数组常数,但在日常编程中确实有一些数组常数的例子。
考虑下列例子。
ProcedureArrayExamples;
const
DaysOfWeek:
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';
var
Output:
string;
Day:
Integer;
begin
Day:
=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条件测试来比较激活状态以设置控件的背景颜色,也提供了用数组实现的相同功能的代码。
if(Edit1.Enabled=False)then
begin
Edit1.Enabled:
=True;
Edit1.Color:
=clWhite;
end
else//True
begin
Edit1.Enabled:
=False;
Edit1.Color:
=clBtnFace;
end;
上面的代码计算了TEdit控件(随机选定)的Enabled状态,对该状态取反,并相应地设置颜色。
该代码实用而直接,但使用常数数组可使之更为有效。
使用常数数组的修订版本如下。
const
Colors:
array[Boolean]ofTColor=(clBtnFace,clWhite);
begin
Edit1.Enabled:
=NotEdit1.Enabled;
Edit1.Color:
=Colors[Edit1.Enabled];
end;
常数数组使得我们可以将代码缩减到原来的五分之一。
使用单目not操作符来执行Enabled状态的切换,用Enabled特性的布尔值来索引常数数组Colors。
在使用布尔值作索引时,False的值较小。
上面的代码更加紧凑,既小且快。
下一节的内容是有关记录常数的,阅读时请注意其中一个用到记录数组常数的例子。
5.1.5记录常数
记录常数是类型为记录的常量数据。
一个很普遍的记录是TPoint。
TPoint在笛卡尔坐标系中定义了两个坐标。
TPoint在Windows.pas单元中如下定义:
TPoint=record
x:
Longint;
y:
Longint;
end;
要初始化常量记录,需要以fieldname:
value的形式指定每个字段,每个字段的名字与值用冒号分隔。
这里有一个例子。
constPoint:
TPoint=(X:
100;Y:
100);
常量记录数组需要初始化每个数组元素,对于构成记录值的字段,将名字和值对的集合用括弧括起来。
这里是一个由四个点构成的数组。
const
Points:
array[0..3]ofTPoint=((X:
10;Y:
10),(X:
10;Y:
100),(X:
100;Y:
100),
(X:
100;Y:
10));
ProcedureDrawRect(constPoints:
ArrayofTPoint);
var
I:
Integer;
begin
Canvas.PenPos:
=Points[0];
forI:
=Low(Points)toHigh(Points)do
Canvas.LineTo(Points[I].X,Points[I].Y);
Canvas.LineTo(Points[0].X,Points[0].Y);
end;
由于并未考虑监视器屏幕的高宽比,该数组只是粗略地定义了如图5.2所示的正方形。
数组Points被传递给代码中的DrawRect过程,用以生成如图5.2所示的正方形。
图5.2利用LineTo方法和TPoint记录组成的
数组,在窗体的画布上所画出的正方形
通过将一些基本的ObjectPascal术语联合起来,可以很容易地创建很多种常量数据,用于代表各种各样的信息。
使用记录和数组常数,可以使您的代码更具有表现力。
通过把精确定义的数据映射到问题域的信息,对代码控制得越好,管理数据所需的代码就越少。
5.1.6过程常数
过程常数是用const修饰的名字,其数据类型为过程类型。
一般来说,过程类型只是指向过程的指针,它使得可以把过程和函数赋值给类型与其相匹配的变量和参数。
过程类型的更多知识可以阅读第6章。
一个过程类型的例子就是定义在classes.pas单元中的TNotifyEvent。
下面是classes.pas的摘录,给出了TNotifyEvent的定义。
typeTNotifyEvent=procedure(Sender:
TObject)ofobject;
从列出的代码可以看出,TNotifyEvent类型是由过程组成的,它们有一个TObject类型的参数,名字为Sender。
类型定义结尾的ofobject表示TNotifyEvent类型是被称为方法指针的特定过程类型。
TNotifyEvent看起来很熟悉,因为它就是ObjectInspector中许多事件特性的类型。
双击空白窗体,Delphi将为窗体的OnCreate事件特性创建如下的空方法定义。
procedureTForm1.FormCreate(Sender:
TObject);
begin
end;
注意:
按照惯例,Delphi使用On前缀表示该特性为事件特性。
On暗示着对动作的响应,即事件。
代码中的TForm1.部分表示该方法属于TForm1类。
FormCreate表示它是OnCreate事件的处理程序,将类名和方法名从定义中剥离,余下的就是procedure(Sender:
TObject),恰好可以与TNotifyEvent匹配。
在Delphi中选择OnCreate事件特性,按键F1,将显示CustomForm.OnCreate的上下文帮助。
帮助文档清楚地指出,OnCreate定义为特性OnCreate:
TNotifyEvent;即类型为TNotifyEvent的特性。
5.1.7指针常数
无论使用任何语言,我们的思维都只是受限于用以表达思想的语言以及我们对它掌握的熟练程度。
一些术语可能不像其余的那样常用。
指针常量就是其中的一个。
在日常的Windows应用编程中,指针可能不太常用,不过Delphi并不限制您只能编写典型的Windows风格的程序。
指针常量就是指向特定地址的指针。
下面的代码琐碎而令人困惑,但它演示了与指针常量有关的必要机制。
type
TDateTimeFunc=function:
TDateTime;
const
NowP:
Pointer=@SysUtils.Now;
var
MyNow:
TDateTimeFuncabsoluteNowP;
第2行定义了一个过程类型,它是指向函数的指针,返回TDateTime值。
常量指针被初始化为SysUtils.pas中定义的Now函数地址。
变量MyNow类型为TDateTimeFunc(在类型部分定义),将编译为SysUtils.Now函数的绝对地址。
5.1.8用于初始化常量的过程
在前面关于过程常数的章节中,您已经知道过程类型可用于定义常数并初始化为某一特定的过程。
对于前一节中的例子,无须使用Pointer即可定义对SysUtils.Now函数的常量引用。
type
TDateTimeFunc=function:
TDateTime;
const
ConstNow:
TDateTimeFunc=SysUtils.Now;
调用ConstNow与调用SysUtils.Now具有相同的效果。
由于Delphi支持过程类型,因此用这种形式定义指向过程的变量或常数更为可取。
无论对于哪种类型,均可使用Pointer来指向一个特定的内存地址。
5.2枚举的使用
枚举是代表值的名字列表。
如果使用枚举更有意义,那么它比内建数据类型更为可取。
枚举的一个完美的例子是TFontStyle。
有四种基本的字体风格:
黑体、斜体、加下划线的、加删除线的。
很清楚,可以用整数来存储字体风格的状态,但对特定风格进行列表更有意义。
graphics.pas单元如下定义了TFontStyle类型。
typeTFontStyle=(fsBold,fsItalic,fsUnderline,fsStrikeOut);
定义TFontStyle使得程序员可以声明TFontStyle类型的变量,保证了所有赋予TFontStyle类型变量的值都是有效的,而且不必进行访问检查和错误检查。
由于编译器是强类型的,只有四个可能值之一才能赋予TFontStyle变量。
简言之,所有困难的工作都由编译器来做,因而代码对于程序员更加可读。
5.2.1用枚举定义数组边界
按照经验规则,相对于原始的数组,TList和TCollection更为可取。
回顾使用对象而不是数组来存储数据的原因:
表和集合类可以动态地伸缩,其中包括了范围检查及其他一些功能,如排序和查找等。
使用数组则需要实现所有这些功能。
有些情况下,您可能需要使用数组。
除去索引范围检查的一种方法是:
使用枚举作为索引的类型,从而使数据的范围精确化。
这里有一个例子演示了TNote类型,它使用WindowsAPI函数Beep来播放一个音符(音符的频率和长短是模拟的)。
type
TNote=(doDo,doRe,doMi,doFa,doSo,doLa,doTi,doDo2);
ProcedurePlayNote(Note:
TNote);
const
DoReMi:
array[TNote]ofInteger=(
500,600,700,800,900,1000,1100,1200);
begin
Windows.Beep(DoReMe[Note],750);
Sleep(250);
end;
ProcedurePlayNotes;
var
I:
TNote;
begin
forI:
=Low(TNote)toHigh(TNote)do
PlayNote(I);
end;
第二行定义了一个枚举类型TNote,包含八个元素。
PlayNote过程使用Low和High函数得到值的范围,然后对每个元素进行迭代。
可以注意到索引I定义为TNote类型而不是整数。
每次循环时,都以当前枚举值为参数调用PlayNote过程。
PlayNote过程参数为TNote类型,过程中定义了一个索引为TNote类型的常量数组,并使用WindowsAPI函数Beep(并非Delphi所提供的版本)来播放每个音符对应的频率。
请注意并不需要范围检查,因为对于数组DoReMi,所有可能的TNote值都是可行的,因为它们都受到枚举范围的限制。
定义枚举和其他精确化的类型具有累积效应。
需要调试和测试的代码会逐渐减少,您的代码将更有效地运行。
5.2.2预定义枚举类型
有许多预定义的枚举类型,实际上,枚举类型太多以至于无法全部涵盖。
这种类型的参考手册最好留给在线帮助文档去作。
如果您已经习惯于用以控制组件行为的枚举类型,就可以更加出色地控制VCL。
以TControlStyle为例。
所有的控件都具有ControlStyle特性,该类型定义为枚举值的集合(有关集合操作和定义,更多的信息请参见下一节)。
typeTControlStyle=setof(csAcceptsControls,csCaptureMouse,
csDesignInteractive,csClickEvents,csFramed,csSetCaption,csOpaque,
csDoubleClicks,csFixedWidth,csFixedHeight,csNoDesignVisible,
csReplicatable,csNoStdEvents,csDisplayDragImage,csReflector,
csActionClient,csMenuEvents);
ControlStyle特性可以具有零个、一个或多个定义在上述枚举中的值。
例如,如果ControlStyle具有csAcceptsControls值,则该控件就可以拥有其他的控件。
例如窗体可以拥有控件,但默认情况下,栅格控件是无法拥有其他控件的。
对于已有的控件,其行为是由作者定义的。
但您总是可以子类化一个控件,根据您的需要调整其行为。
例如,下面的类定义了TControlGrid类型,示范了如何在栅格单元中拥有其他控件(如图5.3所示)。
图5.3可以在栅格单元中拥有其他控件的栅格控件
TControlGrid=class(TStringGrid)
private
FButton:
TButton;
protected
ProcedureWMCommand(varMessage:
TWMCommand);MessageWM_COMMAND;
ProcedureOnClick(Sender:
TObject);
ProcedurePaint;override;
public
constructorCreate(AComponent:
TComponent);override;
destructorDestroy;override;
end;
注意:
如果在设计时可以在栅格上绘制出控件,TControlGrid类就可以更有用。
使用上面以及下面列出的代码,即可完成该栅格控件,从而可以在设计时和运行时动态地拥有控件。
关于栅格组件的演示,参见
该类包含一个private字段FButton,用于示范拥有控件的功能。
protectedmessage方法重载了WM_COMMAND的信息处理程序。
这使得被拥有的控件可以接收到发送给它们的消息,但TStringGrid并未如此,因为在原来的设计中TStringGrid是无法拥有父控件的。
OnClick事件处理程序用于演示TButton控件对用户输入的响应。
构造函数和析构函数中重载了ControlStyle特性,并对TButton控件进行分配和释放。
{TControlGrid}
constructorTControlGrid.Create(AComponent:
TComponent);
begin
inheritedCreate(AComponent);
ControlStyle:
=ControlStyle+[csAcceptsControls];
FButton:
=TButton.Create(Self);
FButton.Parent:
=Self;
FButton.BringToFront;
FButton.OnClick:
=OnClick;
Repaint;
end;
ProcedureTControlGrid.Paint;
begin
inheritedPaint;
FButton.Visible:
=(LeftCol=1)and(TopRow=1);
FButton.Enabled:
=FButton.Visible;
FButton.BoundsRect:
=CellRect(1,1);
end;
destructorTControlGrid.Destroy;
begin
FButton.Free;
inherited;
end;
procedureTControlGrid.OnClick(Sender:
TObject);
begin
MessageDlg('GreetingsEarthlings!
',mtInformation,[mbOK],0);
end;
ProcedureTControlGrid.WMCommand(varMessage:
T
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- delphi第5章 集合常数与运行时类型信息 delphi 集合 常数 运行 类型 信息