企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 第十一章 ## 程序和单元 Delphi 应用程序中的单元,或说程序模块可谓老道精深。实际上,单元是程序模块化的基础,类是继它之后才有的。在Delphi 应用程序中,每个窗体都有一个相对应的单元。用相应的工具按钮, 或File > New Form 菜单命令,在工程中添加一个新窗体,实际上是增加了一个新单元,也就是建立了该新窗体的类。 ### 单元 虽然所有窗体都在单元中定义,但反之则不然。除窗体外,单元中还可以定义一系列能访问的例程。选择File > New菜单命令,然后在Object Repository的New 页中选择Unit 图标,随即当前工程中就会加入一个空白单元。单元代码分区存放,空白单元的代码如下: ~~~ unit Unit1; interface implementation end. ~~~ 单元的概念比较简单,单元名与文件名相同,而且必须唯一。单元包括界面区(interface)及实现区(implementation),界面区用于声明其它单元能看到的部分;实现区存放界面的实现代码及外部不可见的声明。此外还有两个可选的区,即初始化区及结束区,其中初始化区存放初始化代码,当程序加载到内存时执行;结束区存放程序终止时执行的代码。 单元总体结构如下: ~~~ unit unitName; interface // other units we need to refer to uses A, B, C; // exported type definition type newType = TypeDefinition; // exported constants const Zero = 0; // global variables var Total: Integer; // list of exported functions and procedures procedure MyProc; implementation uses D, E; // hidden global variable var PartialTotal: Integer; // all the exported functions must be coded procedure MyProc; begin // ... code of procedure MyProc end; initialization // optional initialization part finalization // optional clean-up code end. ~~~ 界面区头部的uses子句表示需要访问的外部单元,这些外部单元中定义了你需要引用的数据类型,如自定义窗体内所用的控件。 实现区头部的uses子句用于表示只在实现部分访问的单元。如果例程或方法的代码需要引用其他单元,你应该把这些单元加到实现区子句中,而不是界面区。你所引用的单元必须在工程文件目录中能找到,或在工程选项对话框的 Directories/Conditionals 页设定这些单元的搜索路径。 > C++程序员应该知道uses语句和include 指令是不同的。uses语句只是用于输入引用单元的预编译界面部分,引用单元的实现部分在单元编译时才考虑。你引用的单元可以是源代码格式(PAS),也可以是编译格式(DCU),但是必须用同一版本的Delphi进行编译。 在单元的界面区中可以声明许多不同的元素,包括过程、函数、全程变量及数据类型。在Delphi 应用程序中,数据类型可能用得最频繁。每创建一个窗体,Delphi 会在单元中自动建立一个新的数据类型--类(class)。在Delphi 单元中不仅能定义窗体;还能象传统单元一样,只包含过程及函数;还可以定义与窗体和任何可视控件无关的类。 ### 单元的工作空间 Pascal单元是封装性和可视性的关键,它很可能比类中的 private 和 public 关键字还要重要。(实际上,private关键字与类单元的工作空间有关)。一个标识符(如一个变量、过程、函数或数据类型)的工作空间是指能访问标识符的代码段。基本原则是:标识符在它工作空间内才有意义,也就是说,只在其声明的代码块中才有意义,在工作空间外你不能访问这些标识符。例如: * 局部变量:如果你在例程或方法代码块内声明一个变量,那么单元外部不能访问这个变量。该标识符的工作空间就是定义标识符的整个例程,其中包括嵌套例程(除非嵌套例程内有一个同名标识符覆盖了外部定义)。当调用到例程时,其变量压入栈内存中,例程一结束,栈中的内存就自动释放。 * 全程隐藏变量:如果你在单元的实现部分声明一个标识符,那么在单元外你不能使用它,但是能在单元内任一代码块及过程中使用它。程序一启动就会为全程隐藏变量分配内存,程序终止内存释放,你可以在单元初始化区给它赋初值。 * 全程变量:如果你在单元的界面部分声明标识符,那么该标识符的工作空间就扩大了,任何Use它的单元都能访问它。这类变量的内存分配及生命周期与上类变量相同,唯一不同的是其可见性。 只要程序单元的uses子句中列出某一单元名,那么所列单元界面区中声明的任何标识符该程序都能访问。窗体类的变量就是这样声明的,所以你可以在其他窗体代码中访问这个窗体以及它的公共域、方法、属性和组件。当然把什么都声明为全局标识这种编程习惯并不好。除了明显的内存消耗问题外,使用全程变量使代码维护和更新变得困难。一句话,你应该尽量少用全程变量。 ### 单元用作命名空间 uses 语句是访问其他单元工作空间的标准技术,通过该语句你能访问其它单元的定义内容。如果恰巧两个单元声明的标识符同名,也就是说你可能有两个同名的类或例程,遇到这种情况,你可以用单元名作前缀定义类型或过程名,由此进行区分。例如用Totals.ComputeTotal访问Totals 单元中的ComputeTotal 过程。不过这种情况最好不要经常遇到,因此强烈建议不要在同一程序中用同一名字表示两个不同的东西。 然而,如果查阅VCL库和Windows 文件,你会发现一些Delphi 函数和Delphi 可用的Windows API 函数同名,不过参数往往不同,下面以Beep 过程为例说明这个问题。 新建一个Delphi 程序,添加一个按钮,然后写入以下代码: ~~~ procedure TForm1.Button1Click(Sender: TObject); begin Beep; end; ~~~ 执行程序,单击按钮,你会听到一个短促的声音。现在移到单元的uses语句,把原先的代码: ~~~ uses Windows, Messages, SysUtils, Classes, ... ~~~ 改为下面的样式,只要把SysUtils单元移到Windows之前: ~~~ uses SysUtils, Windows, Messages, Classes, ... ~~~ 现在如果重新编译这段代码,你会得到一个编译错误:”Not enough actual parameters”(实际参数不够)。问题在于Windows 单元定义了另一个带两个参数的Beep 函数。应该说uses子句中第一个单元的定义被后面单元的定义覆盖,解决方法很简单: ~~~ procedure TForm1.Button1Click(Sender: TObject); begin SysUtils.Beep; end; ~~~ 不管uses子句中单元顺序如何排列,以上代码都能编译通过。在Delphi中很少有其他命名冲突的情况,因为Delphi 代码通常放在类的方法中,如果不同类中有两个同名的方法不会产生任何冲突,只是使用全程例程会产生冲突问题。 ### 单元和程序 Delphi 应用程序源代码文件可分成两类,它们是一个或多个单元文件及一个程序文件,单元文件可以看成是次一级的文件,它被应用程序的主体——程序文件——引用。理论上如此,实际上程序文件通常是自动产生的,作用有限,程序启动并运行主窗体时才会用到它。程序文件的代码,或说Delphi 工程文件(DPR),可用手工进行编辑,也可通过工程管理器及其与应用程序、窗体相关的选项进行编辑。 程序文件的结构通常比单元文件的结构简单得多。下面是一个程序文件的源代码: ~~~ program Project1; uses Forms, Unit1 in Unit1.PAS {Form1DateForm}; begin Application.Initialize; Application.CreateForm (TForm1, Form1); Application.Run; end. ~~~ 从上可见,文件中只有一个uses区和应用程序的主体代码,主体代码包含在begin 和 end 关键字之间。程序的uses子句特别重要,因为需要通过它来管理应用程序的编译和连接。 * * * * * ### 结束语 讨论完Delphi中Pascal 应用程序的结构,本书就翻过了最后一章,至少目前是这样,欢迎来email发表你的意见和要求。 如果想进一步学习Delphi Object Pascal中面向对象的内容,你可以参考已出版的书《精通Delphi 5》(Mastering Delphi 5 ,Sybex,1999),详细情况可访问网址www.marcocanto.com,你也可以从该网址上下载本书的最新版本。