注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

yeye55的博客

编程就象品一杯清茶,清淡却又深厚。

 
 
 

日志

 
 
关于我

宅男一只,喜欢编程。没事的时候就喜欢抱着电脑看看电影、看看动漫、听听音乐、打打网游、逛逛论坛。清静的时候喜欢专研编程技术,但是发现自己至今仍然只是一只三脚猫。

网易考拉推荐

Delphi XE4 语言指南 - 2 程序组织  

2014-11-25 19:14:28|  分类: XE4参考指南 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

  本指南名称:Delphi XE4 语言指南,作者:叶叶,于2014年11月25日在网易博客上发表。页面地址:http://yeye55blog.blog.163.com/blog/static/197241021201410252936156/ 。本指南全文可以在上述页面中下载。请尊重他人劳动成果,转载或引用时请注明出处。

2 程序组织

  一般情况下,一个Delphi程序由一个项目文件和多个单元文件组成。单元文件通常用来保存程序的具体实现代码,然后通过项目文件将这些单元文件组织在一起。另外,项目文件还提供了程序的入口点和相关初始化。如果你是在IDE中开发程序的,那么IDE会自动生成项目文件,并且提供对相关单元文件的管理。在IDE中开发程序可能会遇到的文件类型如下表所示。

文件扩展名 说明
.dpr 应用程序或动态链接库的项目文件
.dpk 包的项目文件
.pas 单元文件(源文件)
.dcu 经过编译的单元文件(编译单元文件)
.dfm VCL窗体文件
.res 资源文件
.dproj 项目设置文件

表2.1 文件类型

  其中,VCL窗体文件(.dfm)保存了一个窗体的相关设置数据。包含了窗体以及窗体中所有组件的属性设置情况。通常一个VCL窗体文件对应于应用程序中的一个窗口或对话框。在IDE中可以打开这个文件进行查看或编辑。虽然VCL窗体文件是纯文本格式的文件,但是很少使用文本编辑器对其进行编辑。一般情况下,都是在IDE中对其进行可视化编辑。一个VCL窗体文件通常都与一个单元文件(.pas)相关联,它们通常都有一个相同的主文件名。在相关联的单元文件中保存着对应窗体中的处理代码。

  另外,IDE还会为每个项目生成一个资源文件(.res)和一个项目设置文件(.dproj)。其中,资源文件保存了应用程序的图标以及其他资源,如字符串等。而在项目设置文件中保存了当前项目的设置信息,如编译器、链接器的设置,搜索路径信息,版本信息,等等。一般情况下,资源文件和项目设置文件的主文件名都与项目文件的主文件名相同。

2.1 项目文件

  项目文件是一种纯文本格式的文件,通常以“.dpr”作为其文件扩展名。在Delphi中有两种格式的项目文件,一种用于应用程序,另一种用于动态链接库。在本小节中介绍的是应用程序所使用的项目文件,关于动态链接库所使用的项目文件的详细介绍请参考第15.1.2节。在IDE中新建一个窗体应用程序(File > New > VCL Forms Application),那么IDE就会自动生成如下的一个项目文件。

01 program Project1;
02
03 uses
04   Vcl.Forms,
05   Unit1 in 'Unit1.pas' {Form1};
06
07 {$R *.res}
08
09 begin
10   Application.Initialize;
11   Application.MainFormOnTaskbar := True;
12   Application.CreateForm(TForm1, Form1);
13   Application.Run;
14 end.

代码2.1 一个应用程序的项目文件

  代码2.1的第1行是项目文件的标题。标题必须以保留字program开始,其后紧跟一个有效的标识符,作为其应用程序的名称,然后以分号“;”结尾。保留字program与应用程序名称之间使用空格隔开。在IDE下开发应用程序,其应用程序名称必须同项目文件的主文件名相一致。在上面的例子中,应用程序的名称为“Project1”,对应的项目文件名为“Project1.dpr”。在项目文件中必须要包含标题,并且标题必须是第一条有效代码。即,你可以在标题之前插入注释,但是不能插入其他代码。

  代码2.1的第3行到第5行是一个uses子句。通过uses子句可以引用其他单元文件。这些被引用的单元文件将会被包含到应用程序中。uses子句在项目文件中是可选的。关于uses子句的详细介绍请参考第2.3节。

  代码2.1的第7行是一个编译指令。它要求编译器绑定一个资源文件,这个资源文件的主文件名同项目文件的主文件名相一致。通常在这个资源文件中保存了应用程序的图标。这行编译指令在项目文件中也是可选的。

  代码2.1的第9行到第14行是一个块(block)。该块以保留字begin开始,并以保留字end结束。在项目文件中必须要包含该块,该块中的内容就是应用程序运行时需要执行的代码。一般情况下,在该块中的代码不会很复杂。因为,主要实现代码通常都分布在单元文件中。在该块中通常完成的是一些初始化和启动操作。例如,在代码2.1的块中,首先完成了对应用程序的初始化,然后新建了一个主窗口,最后启动了Windows的消息循环。在该块中可以包含类型、常量、变量和例程的声明,但是这些声明必须位于保留字begin之前。还有一点需要注意,在保留字end之后使用点号“.”进行结束,而不是分号“;”。

2.2 单元文件

  在Delphi中通常将一个源代码模块称为单元(unit),单元由类型、常量、变量和例程的声明组成。单元以单元文件的方式存在。单元文件是一种纯文本格式的文件,并且以“.pas”作为其文件扩展名。一个单元文件由标题、接口部分、实现部分、初始化部分和终止化部分构成。这五个部分的先后顺序是固定不变的,不能随意修改。一个单元文件的简单框架如下所示。

01 unit Unit1; //标题
02
03 interface //接口部分
04
05 uses Unit2; //uses子句
06
07 {对外公开的类型、常量、变量和例程的声明}
08
09 implementation //实现部分
10
11 uses Unit3; //uses子句
12
13 {仅内部使用的类型、常量、变量和例程的声明
14  以及类方法、过程和函数的实现代码}
15
16 initialization //初始化部分
17
18 {单元初始化时需要执行的代码}
19
20 finalization //终止化部分
21
22 {单元终止化时需要执行的代码}
23
24 end.

代码2.2 单元文件框架

  单元文件的标题必须以保留字unit开始,其后紧跟一个有效的标识符,作为其单元的名称,然后以分号“;”结尾。保留字unit与单元名称之间使用空格隔开。在IDE下开发应用程序,其单元名称必须同单元文件的主文件名相一致。在上面的例子中,单元的名称为“Unit1”,对应的单元文件名为“Unit1.pas”。该单元文件在编译后会生成已编译的单元文件“Unit1.dcu”。在单元文件中必须要包含标题,并且标题必须是第一条有效代码。即,你可以在标题之前插入注释,但是不能插入其他代码。另外,在同一个项目中的单元名称必须是唯一的,不能重名。即使两个单元文件在不同的文件夹中,在同一个项目中也不能使用相同的名称。

  单元文件的接口部分从保留字interface开始,直到实现部分之前结束。在接口部分中可以声明类型、常量、变量和例程。这些声明的内容是公开的,可以被其他单元访问到。其他单元只要引用了该单元,就可以访问到这些声明的内容。就好象这些声明是在本身单元中发生的一样。在接口部分中声明的例程,只包含了前向声明部分,没有具体的实现代码。在接口部分中声明的类,必须包含该类中所有成员的声明。另外,接口部分可以拥有自己的uses子句。uses子句必须紧跟在保留字interface之后,并且位于其他所有代码之前。接口部分的uses子句是可选的。

  单元文件的实现部分从保留字implementation开始,直到初始化部分之前结束。如果初始化部分被省略,那么实现部分将在单元文件的末尾结束。在实现部分中可以声明类型、常量、变量和例程。这些声明的内容是私有的,只能被本单元内的代码访问。其他单元无法访问到这些声明的内容。在实现部分中可以包含方法和例程的具体实现代码。如果例程在接口部分进行了前向声明,那么对应的例程在实现部分中可以省略参数列表。但是,如果保留了参数列表,那么这个参数列表必须同前向声明中的参数列表相一致。另外,实现部分可以拥有自己的uses子句。uses子句必须紧跟在保留字implementation之后,并且位于其他所有代码之前。实现部分的uses子句是可选的。

  单元文件的初始化部分是可选的,初始化部分从保留字initialization开始,直到终止化部分之前结束。如果终止化部分被省略,那么初始化部分将在单元文件的末尾结束。初始化部分的内容由零到多条语句组成。这些语句将在单元被加载到内存中时被执行,这通常发生在应用程序刚刚开始运行时。这些语句的功能通常都是进行一些初始化操作,例如对单元中的全局变量进行赋值,等等。多个单元文件的初始化部分的执行顺序,按照该单元文件被引用时在uses子句中的先后顺序来确定。在应用程序的整个运行过程中,单元文件的初始化部分仅被执行一次。即使该单元文件被引用了很多次,也只有在第一次被引用时初始化部分才会被执行。

  单元文件的终止化部分也是可选的。终止化部分从保留字finalization开始,直到单元文件的末尾结束。在一个单元文件中,必须要有一个初始化部分才可以出现终止化部分。即,如果出现终止化部分,那么初始化部分就不允许被省略。终止化部分的内容由零到多条语句组成。这些语句将在单元即将从内存中卸载时被执行,这通常发生在应用程序即将被关闭时。这些语句的功能通常都是进行一些释放操作,例如对占用的资源进行释放,等等。多个单元文件的终止化部分的执行顺序,按照对应初始化部分的执行顺序反向进行。例如,假设在应用程序启动时,按照先后顺序执行了单元A、单元B和单元C的初始化部分。那么在应用程序即将被关闭时,会按照先后顺序执行单元C、单元B和单元A的终止化部分。与初始化部分相同,在应用程序的整个运行过程中,单元文件的终止化部分仅被执行一次。注意一点,终止化部分中的语句必须可以处理未完全初始化的数据。因为,如果初始化部分在执行过程中出现了运行时错误,那么初始化部分将会被停止执行,从而造成部分数据没有进行初始化。终止化部分必须可以处理这样的数据。

  单元文件以保留字end作为结束,其后紧跟一个点号“.”。在“end.”之后仍然可以编写代码,但是这些代码将会被编译器忽略。

  在构成单元文件的五个部分中,标题、接口部分和实现部分必须出现,不能被忽略。但是,接口部分和实现部分可以留空。即,只包含保留字interface或保留字implementation,其他的一行代码都没有。另外,初始化部分和终止化部分是可选的。它们通常成对出现,负责资源的分配和释放。

  在单元文件第一次被编译时,编译器会生成一个经过编译的单元文件。这个文件是一个二进制文件,包含了编译生成的二进制代码。通常使用“.dcu”作为其文件扩展名,同时主文件名与对应的单元文件相同。一旦编译单元文件(.dcu)被生成,编译器就不会重新编译源文件(.pas)。除非遇到下面几种情况:源文件(.pas)被修改;编译单元文件(.dcu)丢失;强制要求编译器重新编译;或者是本单元所依赖的另一个单元的接口部分的相关内容被修改。遇到上面这几种情况时,编译器就会对源文件(.pas)进行重新编译。在对一个项目进行链接时,链接器会直接从编译单元文件(.dcu)中取出二进制代码进行链接,此时并不需要源文件(.pas)的参与。

2.3 uses子句

  在项目文件中,以及单元文件的接口部分和实现部分,都可以包含uses子句。uses子句用于引用单元文件。当引用了一个单元文件以后,就可以使用该单元文件的接口部分声明的标识符。uses子句以保留字uses开始,其后紧跟一到多个单元名称;每个单元名称之间使用逗号“,”分隔;最后使用分号“;”结尾。在项目文件中的uses子句,每个单元名称都可以指定一个源文件名。源文件名以保留字in开始,其后紧跟一个字符串,用以指示文件名。这个文件名可以包含绝对路径或相对路径,也可以省略路径。用以指示文件名的字符串必须放置在两个单引号“'”之间。一个uses子句的使用示例如下所示。

01 uses
02   Vcl.Forms,
03   Unit1 in 'Unit1.pas',
04   Unit2 in 'Folder\Unit2.pas';

代码2.3 uses子句使用示例

  一般情况下,编译器会自动搜索单元文件。但是,如果一个单元文件与项目文件不在同一个文件夹中,或者这个单元文件不在编译器的搜索目录中。那么在引用这个单元文件时就需要指定它的源文件名。另外,当一个单元名称被指定源文件名时,IDE就会将对应的单元文件视为当前项目中的一部分,从而在项目管理器(Project Manager)中显示出来。然后可以通过项目管理器来管理这些单元文件。在IDE中新建单元文件或修改窗体文件时,IDE会自动添加或删除对应单元文件的引用。IDE也会自动维护项目文件中的单元引用。

  在单元文件的uses子句中不能使用保留字in来指定源文件名。从一个单元文件中引用的其他单元文件必须与项目文件在同一个文件夹中,或者在编译器的搜索目录中,或者被对应的项目文件所引用。

  System单元和SysInit单元是默认自动引用的,不允许手动添加引用。所有的项目文件和单元文件都默认引用了这两个单元文件,可以直接使用其中声明的类型、常量、变量和例程。这两个单元文件实现了Delphi最基本的一些操作,如文件输入输出、字符串处理、浮点运算、动态内存管理、等等。

  在uses子句中引用单元文件的顺序将会影响到这些单元文件初始化部分的执行顺序。另外,在uses子句中引用的多个单元文件中出现了相同名称的标识符时,编译器将使用位于最后的那个单元文件中的标识符。如果要使用其他单元文件中的标识符,必须要使用限定标识符,例如:UnitName.Identifier。

  如果单元A使用了单元B接口部分声明的标识符,那么我们称单元A直接依赖于单元B。于此同时,如果单元B声明的这个标识符直接依赖于单元C,那么我们称单元A间接依赖于单元C。对于一个单元文件来说,必须在uses子句中列出所有直接依赖的单元文件,而间接依赖的单元文件不必列出。关于单元相互依赖的一个例子如下所示。

文件Project1.dpr的内容:
01 program Project1;
02 uses Unit2;
03 const a = b;
04 //此处省略部分代码直到文件末尾...
文件Unit2.pas的内容:
01 unit Unit2;
02 interface
03 uses Unit1;
04 const b = c;
05 //此处省略部分代码直到文件末尾...
文件Unit1.pas的内容:
01 unit Unit1;
02 interface
03 const c = 3;
04 //此处省略部分代码直到文件末尾...

代码2.4 一个单元依赖的例子

  在代码2.4中,Project1直接依赖于Unit2,而Unit2直接依赖于Unit1,Project1间接依赖于Unit1。由于Project1没有引用Unit1,所以在Project1中不能访问常量c。

  当编译一个模块时,编译器需要搜索到模块中所有直接依赖和间接依赖的单元文件。除非单元文件中的源代码被修改过,否则编译器只需要使用编译单元文件(.dcu),而不需要使用源文件(.pas)。如果一个单元文件的接口部分被修改过,那么所有依赖于它的其他单元文件都需要被重新编译。但是,如果仅仅只是单元文件的实现部分或其他部分被修改,那么其他相关单元文件没必要进行重新编译。编译器会自动跟踪这些依赖关系,并且只有在必要时才会对单元文件进行重新编译。

  当多个单元文件直接或间接的相互引用时就会出现循环引用。例如,单元A引用了单元B,单元B引用了单元C,单元C又引用了单元A。最简单的,两个单元文件的相互引用也是一种循环引用。当一个循环引用出现在单元文件的接口部分时,就会造成单元文件的相互依赖,这是不允许的。例如,下面这个例子就会出现编译错误。

文件Unit1.pas的内容:
01 unit Unit1;
02 interface
03 uses Unit2;
04 //此处省略部分代码直到文件末尾...
文件Unit2.pas的内容:
01 unit Unit2;
02 interface
03 uses Unit1;
04 //此处省略部分代码直到文件末尾...

代码2.5 单元相互依赖

  然而,编译器允许循环引用出现在单元文件的实现部分。当需要进行循环引用时,至少要有一个单元文件必须将相关的单元引用放置在实现部分中。例如,在上面的例子中,为了代码2.5可以被正常编译,我们需要修改如下。

文件Unit1.pas的内容:
01 unit Unit1;
02 interface
03 uses Unit2;
04 //此处省略部分代码直到文件末尾...
文件Unit2.pas的内容:
01 unit Unit2;
02 interface
03 //此处省略部分代码...
04
05 implementation
06 uses Unit1;
07 //此处省略部分代码直到文件末尾...

代码2.6 单元相互引用

  为了减少循环引用所造成的编译错误,在编写代码时应该尽量把单元引用放置在单元文件的实现部分中。在接口部分中引用的单元文件,只包含那些在接口部分声明的标识符所需要使用到的单元文件。

2.4 命名空间

  命名空间(namespace)是一种源代码的组织方式。在Delphi中可以将命名空间视为一种容纳单元文件的容器。命名空间使用了一种类似于文件夹树的方式来组织这些文件。命名空间使用了层次性的命名方式来区分不同功能的模块,这样可以有效的区分那些不同模块中具有相同名称的标识符。

  命名空间必须在项目文件或单元文件的命名时确定下来。一个完整的命名空间包含了多个层次的标识符,每个标识符之间只能使用点号“.”来进行分隔。一个使用了命名空间的例子如下所示。

01 program MyCompany.Programs.Project1;
02
03 uses
04   Vcl.Forms,
05   MyCompany.Programs.Unit1 in 'MyCompany.Programs.Unit1.pas' {Form1},
06   MyCompany.MyWidgets.Unit2 in 'MyCompany.MyWidgets.Unit2.pas';
07
08 {$R *.res}
09
10 begin
11     //此处省略部分代码...
12 end.

代码2.7 一个命名空间例子

  在使用命名空间时,项目文件或单元文件的文件名同样需要与对应的名称相匹配。例如在代码2.7中,单元MyCompany.Programs.Unit1的文件名是MyCompany.Programs.Unit1.pas。编译后生成的编译单元文件是MyCompany.Programs.Unit1.dcu。另外,命名空间的字符串是不区分大小写的。如果两个命名空间仅有大小写上的区别,会被编译器认为是完全相同的。当由编译器负责维护一个命名空间的情况下,编译器会把这个命名空间应用到输出文件、错误消息和单元标识符的运行时类型信息(run-time type information,RTTI)中。类和类型的RTTI将会包含完整的命名空间说明。

  在命名空间中,每个层次的标识符都带有“包含”的性质。所以,在代码2.7中,Unit1视为Programs中的一个成员;Unit2视为MyWidgets中的一个成员;而Programs和MyWidgets视为MyCompany中的两个成员。

  在使用命名空间的同时,每个项目文件或单元文件都隐式的声明了自己的默认命名空间。默认命名空间是由命名空间去除最右边的标识符和点号所形成。例如,在代码2.7中,项目MyCompany.Programs.Project1的默认命名空间是MyCompany.Programs。一个项目的默认命名空间是由这个项目的名称来确定的。项目中的单元文件可以声明自己属于项目的默认命名空间,也可以声明自己属于另一个命名空间。在代码2.7中,单元MyCompany.Programs.Unit1就声明自己属于项目的默认命名空间,而单元MyCompany.MyWidgets.Unit2则声明自己属于另一个命名空间MyCompany.MyWidgets。如果一个单元文件要声明自己属于项目的默认命名空间,那么这个单元的名称必须要包含完整的默认命名空间说明。如果省略了默认命名空间,即使这个单元文件被项目文件所引用,编译器也不会认为它属于项目的默认命名空间。

  如果一个单元文件属于项目的默认命名空间,那么使用uses子句引用这个单元文件时,可以省略单元名称中的默认命名空间。另外,在编译器选项中指定了搜索命名空间后,引用相应命名空间中的单元文件时,也可以省略单元名称中的默认命名空间。虽然可以这么做,但是在实际编程中不推荐这么做。在引用一个单元文件时,应当给出完整的命名空间说明。这样可以提高源代码的可读性,防止出现歧义。

  针对代码2.7中的项目文件,假设在单元MyCompany.Programs.Unit1中声明了一个窗体类型的全局变量Form1。那么我们假设单元文件MyCompany.MyWidgets.Unit2.pas中的内容如下所示。

01 unit MyCompany.MyWidgets.Unit2;
02
03 interface
04
05 uses
06     MyCompany.Programs,          //错误
07     MyCompany,                     //错误
08     Unit1;                                  //正确
09
10 procedure MyProcedure;
11
12 implementation
13
14 procedure MyProcedure;
15 begin
16     MyCompany.Programs.Unit1.Form1.Caption := 'abcd';         //正确
17     Programs.Unit1.Form1.Caption := 'abcd';                                 //错误
18     Unit1.Form1.Caption := 'abcd';                                                     //正确
19     Form1.Caption := 'abcd';                                                                 //正确
20
21     with MyCompany do        //错误
22     begin
23         //此处省略部分代码...
24     end;
25 end;
26
27 end.

代码2.8 命名空间的使用

  使用uses子句引用单元文件时,不允许出现部分命名空间。通过引用这种不完整的命名空间,从而引用该命名空间中的所有单元文件,这是无法实现的,编译器不支持这种引用方式。所以代码2.8中的第6行和第7行是错误的。使用限定标识符,并使用命名空间来指定标识符时,也不允许出现部分命名空间。所以代码2.8中的第17行也是错误的。同样的,在with语句中也不允许出现部分命名空间。所以代码2.8中的第21行对于with语句的使用也是错误的。总之一个原则,在使用命名空间指定一个标识符时,必须给出命名空间的完整说明。

  注意一点,在当前例子中,单元MyCompany.Programs.Unit1是属于当前项目的默认命名空间的。所以在代码2.8中,可以省略单元名称中的默认命名空间,直接使用Unit1。但是,如果Unit1声明自己属于另外一个命名空间时,那么代码2.8中的第8行和第18行就是错误的。

  评论这张
 
阅读(247)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017