第三部分:第 13章 编译单元
2004.11.3 AI程序设计 1
第 13章 编译单元
本章介绍 Visual Prolog编译单元的有关内容, 包括接口, 类声明, 类实
现, 各种类型转换, 条件编译, 异常处理, 预处理程序指令等 。
一个程序由若干编译单元组成 。 编译器分别编译这些编译单元 。 编译的
结果是一个个目标文件 。 这些目标文件 ( 可能还有其它文件 ) 连接在一
起形成项目的目标文件 。 一个程序必须确实包含一个目标段, 它是程序
的入口点 。
在一个单元中所有的引用名被声明或被定义的情况下, 一个编译单元必
须自包含 。 在几个编译单元中可以包括接口定义和类声明 ( 定义或声明
必须在包含它们的所有单元内一致 ) 。 然而类实现 ( 定义 ) 只能在一个
单独的单元中被定义 。 每个被声明的项也必须在项目中被定义, 但是一
些项可以在程序库中定义, 就是说它们不需要文本定义 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 2
第 13章 编译单元
一个编译单元 ( 可能用 #include指令构成 ) 是编译数据项的序列 。
compilationUnit,
compilationItem-list-opt
一个编译数据项是一个接口, 类声明, 类实现, 目标段, 或者是在条件
编译中所说的一个有条件的编译数据项 。
compilationItem,
conditionalItem
interfaceDefinition
classDeclaration
classImplementation
goalSection
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 3
第 13章 编译单元
13.1 接口
13.2 类声明
13.3 类实现
13.4 类型转换
13.5 条件编译
13.6 异常处理
13.7 预处理程序指令
本章小结
本章习题
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 4
本节介绍 Visual Prolog的接口的有关概
念, 内容包括接口的基本概念, 接口于对象,
开放限定 ( Open Qualification), 支持限
定 ( Support Qualification) 等 。
13.1 接口
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 5
13.1.1 接口的基本概念
一个接口定义定义了一个命名的对象类型 。 接口可以支持
其它接口 。 详细内容参见支持限定 。
在接口中声明的所有谓词都是接口类型对象的对象成员 。
接口也是一个全局作用域, 在其中可以定义常量和论域 。
这样, 在一个接口中被定义的常量和论域不是该接口指示的
类型的一部分 ( 或具有该类型的对象 ) 。
这 样 的 论 域 和 常 量 可 以 通 过 限 定 接 口 名
interface::constant或使用开放限定由其它作用域引用 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 6
13.1.1 接口的基本概念
interfaceDeclaration,
interface interfaceName scopeQualifications sections
endinterface interfaceName-opt
interfaceName,
lowerCaseIdentifier
在构造器尾部的接口名 interfaceName( 如果存在 ) 必须
与构造器开始的接口名 interfaceName相同 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 7
13.1.1 接口的基本概念
在构造器尾部的接口名 interfaceName( 如果存在 ) 必须与构造器
开始的接口名 interfaceName相同 。
作用域限定 ScopeQualifications必须是下面的类型,
支持限定 supportsQualification
开放限定 openQualification
段 sections必须是下面的类型,
常量段 constantsSection
论域段 domainsSection
谓词段 predicatesSection
接口谓词段 predicatesFromInterface
条件段 conditionalSection
所有包含在条件段的部分也必须是这些类型。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 8
13.1.2 接口与对象
如果一个接口没有明确地支持任何接口,那么它就隐含
地支持内部接口对象。
对象是一个空接口,即它不包含谓词等内容。
对象的目的是作为所有对象的通用基本类型。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 9
13.1.3 开放限定
开放限定( Open Qualification)可以更方便地引用类层次的
实体。开放段把一个作用域名代入另一作用域,以使这些名字可
以在不受限制的情况下被引用。
开放对于对象成员的名字没有影响, 因为无论如何它们只能被
一个对象访问 。 但是类成员名, 论域, 算符和常量可以不受限制
地被访问 。
当名字以这样的方式被带进一个作用域时, 可能会出现有些名
字变得不明确 。
开放段只会在它们所出现的作用域内产生影响 。 尤其是指, 在
一个类声明中的开放段不会影响类实现 。
openQualification,
open scopeName-comma-sep-list
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 10
13.1.4 支持限定
支持限定( Supports Qualification)只能在接口定义 interfaceDefinition
和类实现 classImplementation中使用。
支持限定用于以下两种情况,
? 指定一个接口 A扩展到另外一个接口 B,因此,对象类型 A是对象类型 B的子
类型。
? 声明一个特定类的对象“私下”具有比一个指定作为构造类型的类更多的对
象类型。
支持存在一个传递关系:如果接口 A支持接口 B,并且接口 B支持接口 C,那接
口 A也支持接口 C。
如果一个接口没有明确支持任何接口, 那么就暗指它支持预定义的接口对象 。
当支持用于一个类的实现中时,结果是 "This"不但可以与构造类型一起使用,
而且还能与任何私有的所支持的对象类型一起使用。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 11
13.1.4 支持限定
supportsQualification,
supports interfaceName-comma-sep-list
支 持 限 定 supportsQualification 只 能 在 接 口 定 义
interfaceDefinition 和类实现 classImplementation中使用 。
注意, 如果接口有冲突的谓词, 则它们就不能在一个支持限定内一起
使用 。
如果谓词具有相同的名字和变元数, 具有不同的原始接口, 那就是冲
突的 。
一个谓词的原始接口是该谓词文字上被声明的谓词的接口, 同时它反
对由支持限定间接声明的接口 。 因此如果同一接口在支持链中出现两次
或更多次, 它也不会发生冲突 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 12
13.1.4 支持限定
举例
考虑下面的接口,
interface aaa
predicates
insert, (integer X) procedure (i),
end interface
interface bbb
predicates
insert, (integer X) procedure (i),
end interface
interface cc
supports aaa,bbb % conflicting interfaces
end interface
接口 cc是非法的,
因为在 aaa中所
支持的 insert/1
以 aaa为源,而
在 bbb中所支持
的 insert/1则是
以 bbb为源的。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 13
13.2 类声明
一个类声明( Class Declarations)定义针对环境的类的外部特征:环境完
全可以看见和使用那些在类声明中提及的实体。我们说类的声明指定了类的公共
部分。
一个类声明可以包含常量和论域的定义,以及谓词的声明。
在类声明中所提到的一切都属于该类,而不属于它所构造的对象。与该对象
相关的一切,必须在该类所构造的对象的构造类型中被声明。
特别值得注意的是,一个类声明并不描述任何与代码继承有关的内容。代码
继承是一个完全私有的事件,它只能在类实现中被声明。(这不像其它的面向对
象程序设计语言,它在实现中隐藏所有的细节)
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 14
13.2 类声明
classDeclaration,
class className constructionType-opt scopeQualifications sections end
class className-opt
constructionType,
, interfaceName
className,
lowerCaseIdentifier
在类声明尾部的类名 className(如果存在的话)必须与其头部的类名
className一致。
注意,可以使用与指定为该类的构造类型的接口名构造类型
constructionType相同的类名 className。写作,
class interfaceAndClassName, interfaceAndClassName
注意,类和接口可以声明域和常量,并且由于它们在同名的空间内结束,所
以一定不会发生冲突(因为它们只能以相同的接口名或类名限定)。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 15
13.2 类声明
作用域限定 scopeQualifications 必须是 开放限定 openQualification
类的 。
段 sections必须是下面的几种,
常量段 constantsSection
论域段 domainsSection
谓词段 predicatesSection
构造段 constructorsSection
条件段 conditionalSection
构造段 constructorsSections 只 在 该 类 声 明 为 构 造 类 型
constructionType时才是合法的 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 16
13.3 类实现
本节介绍类实现( Class Implementations)
的有关知识,包括类实现的基本概念、继承限定
( Inherits Qualification)、归结限定
( resolve qualification)、委托限定
( Delegate Qualification),This修饰、构造
( Construction)、终结( Finalization)等内
容。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 17
13.3.1 类实现的基本概念
类实现用于提供类声明中所声明的谓词的定义和构造器,以及它构造
的对象所支持的任意谓词的定义。
类可以私有地(即在实现内部)声明和定义比类声明中提到的更多的
实体。特别地,一个实现可以声明用于实现类和对象声明的事实数据库。
实现是一个混合作用域,在这个意义上来说,它包括了类的实现和类
所产生的对象。类中的类部件在类的所有对象间共享,与对象部件相反,
对象部件相对每个对象来说是单独的。类部件和对象部件都可以包含事
实和谓词,而论域、算符和常量总是属于类部件,就是说它们不属于单
个对象。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 18
13.3.1 类实现的基本概念
缺省时,类实现中声明的所有谓词和事实对象都是对象成员。为了声
明对象成员,段关键字(即 predicates 和 facts)前必须加上前缀
class。所有在这样的段中声明的成员都是类成员。
类成员可以引用类的类部件,但是不能引用对象部件。
另一方面,对象成员既可以访问类的类部件,又可以访问对象部件。
在实现的代码中,所有对象谓词都包含宿主对象。所包含的宿主对象
也可以直接通过特殊变量 "This"来访问。
classImplementation,
implement className scopeQualifications sections end
implement className-opt
在类实现尾部的类名 className(如果存在的话)必须与其头部的
类名 className一致。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 19
13.3.1 类实现的基本概念
作用域限定 ScopeQualifications必须是下面的几种,
? 支持限定 supportsQualification
? 开放限定 openQualification
? 继承限定 inheritQualification
? 归结限定 resolveQualification
? 委托限定 delegateQualification
支持限定描述接口列表,这些接口由类实现私有地给予支持。委托
限定把接口谓词或对象的功能委托给对象谓词,它们可以作为事实变量
被存储起来。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 20
13.3.1 类实现的基本概念
段必须是以下几种,
? 常量段 constantsSection
? 论域段 domainsSection
? 谓词段 predicatesSection
? 构造器段 constructorsSection
? 事实段 factsSection
? 子句段 clausesSection
? 条件段 conditionalSection
只有类的类名 className声明了一个构造类型 constructionType
时,构造器段 constructorsSections才是合法的。声明了一个构造类
型 constructionType的那些类也是对象构造器,可以构造所声明的构
造类型的对象。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 21
13.3.1 类实现的基本概念
举例
下面这个例子说明了类事实怎样在类的对象中被共享,而对象事实
不被共享。考虑接口 aa和类 aa_class,
interface aa
predicates
setClassFact, (integer Value) procedure (i),
getClassFact, () -> integer procedure (),
setObjectFact, (integer Value) procedure (i),
getObjectFact, () -> integer procedure (),
end interface
class aa_class, aa
end class
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 22
谓词的指针是这样一种指针, 它们可以从各个类和对象事实中存取其值,
implement aa_class
class facts
classFact, integer,= 0,
facts
objectFact, integer,= 0,
clauses
setClassFact(Value),-
classFact,= Value,
getClassFact() = classFact,
clauses
setObjectFact(Value),-
objectFact,= Value,
getObjectFact() = objectFact,
end implement aa_class
假设这个类考虑目标 goal,
goal
A1 = aa_class::new(),
A2 = aa_class::new(),
A1:setClassFact(1),
A1:setObjectFact(2),
ClassFact = A2:getClassFact(),
ObjectFact = A2:getObjectFact(),
类事实在所有的对象中被
共享, 所以 通过 A1设置
的 类 事 实 也会 影 响通 过
A2 获 得 的 值 。 因此,
ClassFact的值只有一个,
就是通过 A1设置的值 。
另一方面,对象事实属
于每个对象。所以,在
A1中设置的对象事实不
会影响 A2中存储的值。
因此,ObjectFact的值是
零,它是 A2中初始化的
事实值。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 23
13.3.2 继承限定
继承限定用于声明一个实现对一个或多个类的继承。继承只影响类的
对象部件。从其它类继承的目的是继承那些类的行为。
当类 cc继承了类 aa,那么类 cc的对象将包含类 aa的一个嵌入对象。
对象谓词可以被继承:如果类不执行它的某个对象谓词,但是它所继
承的其中一个类执行了这些谓词,那么这些谓词就将用于当前类。
从其他类继承的类对于继承类没有任何特权:它只能通过构造器类型
接口访问所嵌入的对象。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 24
13.3.2 继承限定
继承必须是明确的。如果类定义了谓词本身,那么不会出现含糊的
问题,因为这个谓词定义是被显示表达的。如果只有一个继承类支持谓
词,那么类所提供的定义也是明确的。但是如果两个或两个以上的类支
持该谓词,那么类所提供的定义就是含糊的。在这种情况下,必须通过
一个归结限定,使这种含糊性得到解决。(参见归结限定)
源自继承类的对象谓词可以从当前类的对象谓词直接调用,这是因为
内嵌的子对象( sub-object)被隐含地用作谓词所有者。类限定可以用
于解决来自继承类对象谓词的调用模糊性。
inheritsQualification,
inherits className-comma-sep-list
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 25
13.3.3 归结限定
正如其它地方讲过的那样, 凡是与谓词调用有关的含糊问题, 都可以通过使用
限定名来避免 。 但是当涉及到继承问题时就不成立了 。 考察下面的例子,
interface aa
predicates
p, () procedure (),
,.,
end interface
class bb_class, aa
end class
class cc_class, aa
end class
class dd_class, aa
end class
implement dd_class inherits bb_class,cc_class
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 26
13.3.3 归结限定
在本例中, 类 bb_class 和类 cc_class哪一个向类 dd_class提供 aa
的实现是不确定的 。 ( 注意:当说到一个类实现一个接口时, 就是指这
个类向这个接口中声明的谓词提供定义 。 )
当然有可能向 dd_class的实现添加子句, 这将有效地解决这项工作 。
比如考虑下面的子句, 它会从 bb_class中输出谓词 p,
clauses
p(),- bb_class::p(),
但是, 用这一代码并没有真正继承 bb的行为, 实际上是向该类的
bb_class部件委托了这项工作 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 27
13.3.3 归结限定
要解决这种含糊性问题(并使用真正的继承而不是委托),使用一个归结
( resolve)段。归结段包含若干个解,
resolveQualification,
resolve resolution-comma-sep-list
resolution,
interfaceResolution
predicateFromClassResolution
predicateRenameResolution
predicateExternallyResolution
归结限定用于从指定的源程序中确定实现。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 28
13.3.3 归结限定
? 谓词归结
predicateFromClassResolution,
predicateNameWithArity from className
一个来自类归结( class resolution)的谓词,声明该谓词由指定的
类实现。
为了归结一个类的谓词,
? 该类必须实现待归结的谓词,意指该谓词必须源于本应继承的同一谓
词。
? 该类必须在继承段中提到。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 29
13.3.3 归结限定
谓词重命名归结
谓词重命名归结( Predicate Rename Resolution)声明一个谓词以另外
一个名字被实现。该谓词必须来自一个继承类,并且它的类型、模式和流必须
确实匹配。
predicateRenameResolution,
predicateNameWithArity from className,,predicateName
? 接口归结
接口归结( Interface Resolution)用于归结来自继承类的完整接口。这样,
一个接口归结就成为声明接口中所有的谓词应当由同一类归结的一个捷径。
interfaceResolution,
interface interfaceName from className
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 30
13.3.3 归结限定
? 外部归结
一个谓词的外部归结( External Resolution)声明该谓词并没有在
类本身中被实现,而是在外部程序库中被实现的。外部归结只能用于类
谓词,即对象谓词不能被外部归结。
在程序库中,调用约定、连接名和参数类型要与实现相符是非常重要
的。
私有的和公有的谓词都能被外部归结。
predicateExternallyResolution, predicateNameWithArity externally
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 31
13.3.3 归结限定
?动态外部归结
一个谓词的外部归结也向来自 DLL的私有和公共类谓词的动态载入提
供语法。该语法是,
predicateExternallyResolutionFromDLL,
predicateNameWithArity externally from DllNameWithPath
DllNameWithPath,
stringLiteral
如果谓词 predicateNameWithArity在 DLL DllNameWithPath中被
丢失,那么动态载入就给运行程序提供了可能性,直到实际调用这个被
丢失的谓词为止。在这样的调用中将出现运行时错误。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 32
13.3.4 委托限定
一个委托段包括许多的委托,
delegateQualification,
delegate delegation-comma-sep-list
delegation,
predicateDelegation
interfaceDelegation
委托限定用于将对象谓词的执行委托给指定的源。
有两种委托限定,即谓词委托和接口委托。接口委托,用于将一个接
口声明的一整套对象谓词的实现委托给另一个作为事实变量存储的对象
的实现。这样,一个接口委托就成为声明以下内容的一个捷径,即接口
中所有谓词的实现应当委托给以事实变量存储的对象的实现。
委托段除了它是委托给那些保持类的构造对象的事实变量而不是委托
给继承类之外,它看起来像是归结段的对应物(谓词或接口)。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 33
13.3.4 委托限定
? 谓词委托
对象谓词委托( Predicate Delegation)声明该谓词的功能被委托
给以事实变量 factVariable_of_InterfaceType所指定的对象中的谓
词。
predicateDelegation,
predicateNameWithArity to factVariable_of_InterfaceType
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 34
13.3.4 委托限定
考察下面的例子,
interface a
predicates
p1, () procedure (),
p2, () procedure (),
end interface
interface aa
supports a
end interface
class bb_class, a
end class
class cc_class, a
end class
class dd_class, aa
constructors
new, (a First,a Second),
end class
implement dd_class
delegate p1/0 to fv1,p2/0 to fv2
facts
fv1, a,
fv2, a,
clauses
new(I,J):-
fv1,= I,
fv2,= J,
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 35
13.3.4 委托限定
随后可以构造类型 a的对象并把它们赋给以事实变量 fv1和 fv2来
定义对象, 从而定义真正委托 p1和 p2功能性定义的类的对象 。 考
虑下列例子,
goal
O_bb = bb_class::new(),
O_cc = cc_class::new(),
O_dd = dd_class::new(O_bb,O_cc),
O_dd, p1(),% This p1 from O_bb object
O_dd, p2(),% This p2 from O_cc object
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 36
13.3.4 委托限定
实际上, Visual Prolog的委托具有与向 dd_class的实现添加子
句一样的效果, dd_class由谓词的功能是, 输出, 的类的对象明
确指定 。 例如, 好像在 dd_class的实现中确定了下面的子句,
clauses
p1(),- fv1, p1(),
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 37
13.3.4 委托限定
? 接口委托
当需要指定在接口 interfaceName中声明的所有谓词的功能被委托
到来自同一被继承类的对象的谓词时,可以使用接口委托说明,
interfaceDelegation,
interface interfaceName to factVariable_of_InterfaceType
这样接口委托就是一个捷径,声明了接口 interfaceName中声明的
所有谓词的功能应当被委托给以事实变量 factVariable_of_InterfaceType
存储的对象。对象被赋以事实变量 factVariable_of_InterfaceType,
该变量是 interfaceName 类型 (或其子类型 )的。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 38
13.3.4 委托限定
委托一个接口给一个通过事实变量传递的对象,
– 事实变量 factVariable_of_InterfaceType 必须具有一个接口
interfaceName 的类型或其子类型 。
– 对象所支持的接口必须被构造出来并被赋值给事实变量
factVariable_of_InterfaceType。
谓词委托比接口委托优先级更高 。 如果一个谓词的两种委托都被指定,
就是说谓词委托被指定到谓词, 并且在具有接口委托的接口中被声明 。
那么具有较高优先权的谓词委托将被执行 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 39
13.3.5 This修饰
对象谓词总是在一个对象中被调用。这个对象携带该对象的事实,并
且包含在对象谓词的实现中。对象谓词有权使用隐含的对象。这样的对
象被称为 ——"This"。有两种 "This"的访问,即隐式访问和显式访问。
1) 显式 "This"
在每个对象谓词的每个子句中,变量 This被隐含地定义并绑定到
,This”,也就是其成员谓词正在执行的对象。
2) 隐式 "This"
在一个对象成员谓词的子句中,可以直接调用另一对象成员谓词,因
为,This”是被隐含地包含的运算。只要调用的方法含义明确,超类的成
员也可以被直接调用(参见作用域和可视性)。同样地,对象事实(存
储在,This”中)也可以被访问。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 40
13.3.5 This修饰
3) This和继承
"This"总是引用一个属于用 "This"的类的对象, 假定接口 aa被声明如下,
interface aa
predicates
action, () procedure (),
doAction, () procedure (),
end interface
再假定类 aa_class如下,
class aa_class, aa
end class
然后执行,
implement aa_class
clauses
action(),-
doAction(),
This:doAction(),
doAction(),-
write("aa_class::doAction"),nl,
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 41
下面的目标,
goal
A = aa_class::new(),
A:action(),
将输出,
aa_class::doAction
aa_class::doAction
现在再考虑类 bb_class,其声明如下,
class bb_class, aa
end class
其实现如下,
implement bb_class inherits aa_class
clauses
doAction(),-
write("bb_class::doAction"),nl,
end implement
下列目标,
goal
B = bb_class::new(),
B:action(),
也将输出,
aa_class::doAction
aa_class::doAction
这是因为在隐式和显式情况下,aa_class中的 "This"都引用了类 aa_class的对象。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 42
13.3.6 构造器
本节介绍对象的构造,同样地,它只涉及产生对象的类。通过调用构造器构
造对象。构造器在类声明和类实现的构造器段中明确地声明。(参见缺省构造
器)
一个构造器实际上有两个相关的谓词,
? 一个类函数,返回新构造好的对象。
? 一个对象谓词,当初始化继承对象时使用。
这个相关的对象谓词用于执行该对象的初始化。这个谓词只能从类自身的构
造器中,以及从该类的继承类的构造器中被调用(即基本类初始化)。
这个相关的类函数是隐含定义的,即任何地方都不存在它的子句。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 43
13.3.6.1 缺省构造器
缺省构造器是一个名为 new的空变元构造器。如果一个
类根本就没有声明任何构造器,那么它就隐含地声明了缺省
构造器。就是说所有子句至少具有一个构造器。显式地重新
定义缺省构造器也是合法的。
不需要定义(即实现)缺省构造器;如果它没有定义,
则一个无效的定义隐含地被假定。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 44
13.3.6.1 缺省构造器
举例
假定一个接口 aa,考查下面代码,
class aa_class, aa
end class
类 aa_class没有声明构造器;因此, 它隐含地声明了缺
省构造器 。 这样可以创建一个如下的类 aa_class对象,
goal
_A = aa_class::new(),
% implicitly declared default constructor
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 45
13.3.6.1 缺省构造器
举例
实现类 aa_class隐含声明的缺省构造器是合法的,
implement aa_class
clauses
new(),-
,.,
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 46
13.3.6.2 子对象构造
所有的构造器都负责将被构造的对象初始化为有效状态。为了获得这
样的有效状态,所有的子对象(即继承类)也必须被初始化。
子对象可以通过两种方式中的任意一种来初始化。这两种方式或者是
程序调用一个继承类的构造器,或者是自动调用缺省的构造器。后者实
际上要求继承类具有缺省的构造器。
如果继承类不具有缺省构造器,那么必须显式地调用其它的构造器。
在具有子句的事实和事实变量的初始化之后,在进入构造器的子句之前,
必须立即执行继承类的构造器的缺省调用。
通过没有返回值的形式调用继承类的构造器。如果你的调用是有返回
值的形式,实际上是创建了一个新的对象而不是调用,This”中的构造器。
(参见下面的例子)
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 47
13.3.6.2 子对象构造
举例
类 bb_class的实现继承了类 aa_class,并且 bb_class的
缺省构造器用一个最新创建的 cc_class对象来调用 aa_class
的构造器。
implement bb_class inherits aa_class
clauses
new(),-
C = cc_class::new(),% create a cc_class object
aa_class::newC(C),% invoke constructor on inherited
sub-object
,.,
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 48
13.3.6.3 单个(对象)事实初始化
正如所有的构造器必须初始化或构造子对象一样,它
们也必须在首次引用对象的单个事实之前,对所有这些事
实进行初始化。
注意,单个类事实只能用子句初始化,因为它们与对
象无关。一个类事实可以在创建第一个对象之前被访问。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 49
13.3.6.3 单个(对象)事实初始化
举例
implement bb_class inherits aa_class
facts
counter, integer,= 0,
point, (integer X,integer Y) single,
c, (cc C) single,
clauses
point(0,1),
% The object is created and counter and
point are initialized before entrance,
% the default constructor
aa_class::new/0 is also invoked before
entrance
new(),-
C = cc_class::new(),
assert(c(C)),
,.,
end implement
这个例子表明,
( 1)怎样通过一个表
达式初始化一个事实变
量;
( 2)怎样通过一个子
句初始化一个单个事实
(点);
( 3)怎样在构造器中
初始化一个单个事实;
( 4)在哪里调用一个
继承类的缺省构造器。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 50
13.3.6.4 委托构造
作为用构造器直接构造对象的选择方案,可以将这一任
务委托给同一个类的另一构造器。这一点仅仅通过调用另外
一个构造器就可以实现(即不返回值的形式)。委托构造的
时候必须确保已经实际构造了对象,并且不是“重复构造
( over-constructed)”。单个事实可以被赋值任意多次,
因此它不能“重复构造”。另一方面,继承类在对象构造期
间只能被初始化一次。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 51
13.3.6.4 委托构造
举例
这个例子说明了一个以委托方式构造的典型应用。一个构造器
(new/0)用缺省值调用另外一个构造器 (newFromC/1)。
implement aa_class
facts
c, (cc C) single,
clauses
new(),-
C = cc_class::new(),
newFromC(C),
newFromC(C),-
assert(c(C)),
,.,
end implement
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 52
13.3.6.5 构造对象的规则
程序员必须确保,
?所有子对象都只能被初始化或构造一次。
?所有单个事实都应被初始化至少一次。
?在子对象被初始化或构造之前,没有子对象被引用。
?在单个事实被初始化之前没有被使用。
在编译时间内编译器能否检测出上述问题并不能得到保证。
编译器可以提示产生一个运行时间确认,它也可以提示非安全地省略
这些运行时间确认。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 53
13.3.7 终结
一旦程序不能到达一个对象,这个对象就被终结。语义上并不能确切
地指出 这个对象何时会终结。惟一能够确定的是,只要程序还能到达对
象就不会终结。习惯上,当对象被垃圾回收程序废弃的时候,它就终结
了。终结是构造的对立面,并将对象从内存中删除。
类也可以实现一个终结器,它是一个对象终结时(从内存被删除之前)
调用的谓词。
一个终结器是一个名为 finalize,没有参数也没有返回值的过程。这
个谓词隐含地被声明,并且不能由程序直接调用。
终结器的主要用途是释放外部资源,但是对于它所能做的事情并没有
限制。使用终结器时应当小心,它们的调用时间不能完全知晓,因此,
在终结器被调用的时候很难预测出整个程序的状态。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 54
13.3.7 终结
举例
这个例子用一个终结器确保一个数据库连接得到正确关闭,
implement aa_class
facts
connection, databaseConnection,
clauses
finalize(),-
connection:close(),
,.,
end implement aa_class
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 55
本节介绍 Visual Prolog的各种数据类
型的转换, 包括隐式转换, 显式转换
等内容 。
13.4 类型转换
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 56
13.4.1 隐式转换
隐式转换( Implicit Conversion)又称为自动转换,它
涉及到可视类型和构造类型等。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 57
13.4.1.1 可视类型和构造类型
一个接口定义了一个对象类型,所有支持该接口的对象
都有这个类型。以后用术语对象类型作为术语由接口定义的
类型的同义词。
同一对象在不同的上下文中可以视为具有不同的类型。
一个对象可见的类型被称为可视类型( view type),而构
造该对象的类的类型被称为构造类型( construction type)
或定义类型( definition type)。构造类型也是一个可视类
型。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 58
13.4.1.2 类型转换
如上所述,对象可以用作具有任意接口支持的类型。这部分介绍如
何处理各种类型的支持之间的转换。
?向上转换
如果某一术语静态地代表某一类型 T1,并且 T1被声明支持 T2,那
么显然变量引用的任何对象都将真正支持 T2。因此,向上的支持信息
被静态地告知。随后自动执行支持层次的所有向上转换。
?举例
假定支持接口 aa的接口 bb存在,并且类 bb_class具有构造类型 bb。
考查下面的代码,
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 59
implement,.,
predicates
ppp, (aa AA) procedure (i),
clauses
,..,-
BB = bb_class::new(),% (1)
ppp(BB),% (2)
clauses
ppp(AA),- % (3)
,.,
BB = convert(bb,AA),
% Conversion possible since
definition type of AA is bb
,.,
在标记为 ( 1) 的行中, 我
们创建了一个 bb_class对象:
该对象具有构造类型 bb。 变量
BB是这个新对象的引用 。 BB
提供了项目中这个对象的可视
类型 bb。
在标记为( 2)的行中,该
对象作为一个 aa对象传递给
ppp。隐含地执行从可视类型
bb到可视类型 aa的转换。当
对象到达标记( 3)的这一行
时,虽然构造类型还是 bb,但
它却具有可视类型 aa。
13.4.1.2 类型转换
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 60
13.4.2 显式转换
显式转换( Explicit Conversion)通过调用一个转换谓
词来执行。有几个转换谓词是可以利用的。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 61
13.4.2.1 受检查的转换
谓词 convert//2 和 tryConvert//2用于实现一个类型到另一类型的
安全转换。
任一个谓词都不能赋予真实的声明,但是这里有它们的伪声明,
predicates
convert, ("type" Type,_ Value) -> Type ConvertedValue,
tryConvert, ("type" Type,_ Value) -> Type ConvertedValue
determ,
convert//2和 tryConvert//2都采用,type”作为第一个参数,一个
任意类型的值作为第二个参数,并且随后返回已转换的值作为结果。,
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 62
13.4.2.1 受检查的转换
如果不可能转换的话, convert//2 将产生一个异常, 而
tryConvert//2只会失败 。
注意, 如果源类型是这个目标类型的子类型, 那么使用 convert//2
和 tryConvert//2就是多余的, 因为转换将被隐含地完成 。
convert//2和 tryConvert//2可以用于下面的情形,
* 从一个数值论域( number domain)转换到另一个数论值域
* 从对象转换到另一类型
如果可以确定转换永远也不会成功,那么编译器可以提出抗议(但不
是必须的)。比如,试图在没有重叠部分的两个数值论域之间进行转换。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 63
13.4.2.2 向下转换
当一个对象被转换成一个超类型(即一个被支持的接
口)时,关于对象的信息将被“遗忘”。注意,它的性能
并不是真的被丢失,它们只是在具有较小能力接口的对象
上下文中不可见。
在多数情况下,需要恢复该对象的实际能力。因此,
要既能够向上转换也能够向下转换。向下转换通常不能静
态地生效。因此,当恢复“丢失的”接口时,有必要用显
式转换。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 64
13.4.2.2 向下转换
举例
interface objectSet
predicates
insert, (object Elem) procedure (i)
getSomeElem, () -> object determ (i)
,.,
end interface
class objectSet_class, objectSet
end class
现在假定有某种对象类型 myObject,并且要建立相应的“组”类
myObjectSet_class。声明 myObjectSet_class如下,
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 65
interface myObjectSet
predicates
insert, (myObject Elem) procedure (i),
getSomeElem, () -> myObject determ (),
,.,
end interface
class myObjectSet_class, myObjectSet
end class
也就是说, myObjectSet具有 objectSet的所有谓词, 但是每一个出
现的对象都被 myObject替代 。 myObjectSet_class的实现从
objectSet_class继承, 这个嵌入或继承的 objectSet将携带这个组
的成员 。 该实现将实现下列不变量 ( invariant),嵌入的 objectSet
只能包含类型为 myObject的对象 ( 即使它们在技术上有类型对象 ) 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 66
该实现如下,
implement myObjectSet_class
inherit objectSet_class
clauses
insert(Elem),-
objectSet_class::insert(Elem),% (1)
getSomeElem() = Some,-
SomeObject = objectSet_class::getSomeElem(),% (2)
Some = convert(myObject,SomeObject),% (3)
,.,
end implement
在标记为( 1)的行,Elem被自动地从类型 myObject转换为对象。
在标记为( 2)的行,从嵌入的对象 object组收回一个对象。这个对
象在技术上有类型对象。但是由不变量知这个对象也支持 myObject。
随后,便可以安全地恢复 myObject接口。这一步在标记为( 3)的行
显式地完成。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 67
13.4.2.3 私有类型和公有类型
当一个对象用构造器建立时,它返回时就带有一个构造器类型。这
样的对象可以自动地被转换为任意支持的接口并且再显式地转换回来。
即使执行对象的类已经声明了更多的私有支持接口,它也不可能将
“公有”对象转换为任何私有对象。
但是在实现中,该对象可以被任意私自支持的类访问。此外,用任
意这些私有支持的类可以在实现外处理 "This"。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 68
13.4.2.3 私有类型和公有类型
这样的一个“私有”对象也可以在它的层次中被隐式地向上转换,
然后显式地向下转换。实际上这样的“私有”对象可以被显式地转换到
任意公有的或私有的支持接口。
因此一个对象有两个视图:公有视图和私有视图。私有视图包括公
有类型。对象不能从一个视图转换到另一个视图,但是既然私有视图包
括公有视图,那么私有视图就可以转换成任意支持的类型。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 69
13.4.2.4 无检查的转换
谓词 uncheckedConvert/2用于执行基于存储器表示( memory
representation)的非安全转换。该谓词不能以任何方式修改内存,它
只是强制编译器用另一类型来解释存储段。
注意该谓词非常不安全,使用时要特别警惕。
当与其他语言接口时,为了解释这些语言所用的存储映像,才使用该
谓词。
uncheckedConvert/2只能用于位大小完全相同的内存段。但是一
个指针代表了多种数据,并且这些数据位具有相同大小。
predicates
uncheckedConvert, ("type" Type,_ Value) -> Type ConvertedValue,
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 70
13.4.2.4 无检查的转换
举例
predicates
interpretBufferAsString, (pointer BufferPointer) -> string Value,
clauses
interpretBufferAsString(BufferPointer) = unchekedConvert(string,
BufferPointer),
这个谓词把一个指针所代表的缓冲区解释为 ( 转换为 ) 一
个字符串 。 当内存块有作为字符串的正确表示时, 它才是合
理的 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 71
13.5 条件编译
条件程序设计结构是 Visual Prolog语言的一部分(与预处理相对)。只有编译项和程
序段可以是有条件的。
conditionalItem,
#if condition #then compilationItem-list-opt elseIfItem-list-opt
elseItem-opt
#endif
elseIfItem,
#elseif condition #then compilationItem
elseItem,
#else compilationItem
conditionalSection,
#if condition #then section-list-opt elseIfSection-list-opt elseSection-opt
#endif
elseifSection,
#elseif condition #then section-list-opt
elseSection,
#else section-list-opt
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 72
13.5 条件编译
这里条件 condition可以是任何表达式,这些表达式可以计算失败或
者成功。
为了确定哪一部分包含在最终的程序中,编译器在编译过程中计算条
件。最终程序不包含的部分被称为死支。
条件编译项的所有分支都要经过语法检查,并且必须在语法上是正确
的。就是说,死支也应当在语法上是正确的。
编译器只在需要时计算条件,即它不计算死支上的条件。
条件在编译时间被计算,并且因此必须具有这种计算的可能性。
一个条件可以不依赖于任何位于条件语句内的文本代码。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 73
13.6 异常处理
异常处理( Exception Handling)系统的基本部分基于两个内部谓词,
errorExit/1 和 trap/3。
? errorExit 产生一个异常
? trap 为一个特定计算设置一个异常处理器。
当 errorExit被调用,当前活动的异常处理器就被调用。这个异常处理器在它
原始的上下文中被执行,即在它被设置的上下文中而不是在产生异常的上下文中
被执行。
errorExit所调用的参数被传送到异常处理器。这个参数必须提供所需的该异
常的描述。
与附加的运行时间例程( additional runtime routines)一起有可能在该系
统的顶端建立起高水平的异常处理机制。
但是关于运行时间访问例程不在本节的讨论范围之内。同样,运行时间系统
怎样处理发生在其内部的异常也不在我们的讨论范围之内。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 74
13.6 异常处理
Trap的第一个参数是与新的异常处理器一起执行的条件。第二个参数必须是
变量。如果在异常处理器活动的时候它被调用,那么这个变量就被绑定到
errorExit被调用的值上。第三个变量是异常处理器,如果在异常处理器活动
的时候 errorExit被调用的话它将被调用。
这个异常处理器可以访问在第二个参数中声明的变量,从而检查所产生的异
常。
? 举例
clauses
p(X),-
trap( dangerous(X),Exception,handleDangerous(Exception) ),
如果在执行 dangerous时产生异常,那么 Exception将被绑定到异常值,并
且控制将转移到 trap的第三个参数。在这种情况下,Exception被传递给
handleDangerous。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 75
13.7 预处理程序指令
预处理程序不是 Visual Prolog语言的一部分。
每个编译指令以 #字符开始。支持下面的编译指令,
?条件编译指令,#if,#then,#else,#elseif,#endif
?源文件包含,#include
?编译时间信息,#message,#error,#requires,
#orrequires
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 76
13.7.1 条件编译
条件编译编译器指令可以用于程序编译项和程序段(在条件编译一章中描述
了它的语法),因此对于其它预处理程序指令也一样,
conditional_PP_Section,
#if condition #then PP_Directive-list-opt elseIf_PP_Section-list-opt
else_PP_Section-opt #endif
elseif_PP_Section,
#elseif condition #then PP_Directive-list-opt
else_PP_Section,
#else PP_Directive-list-opt
PP_Directive,
#include string_literal
#message string_literal
#requires string_literal
#orrequires string_literal
#error string_literal
条件 condition必须是布尔表达式,
在编译中可以求值计算。
每一个条件编译语句
conditional_PP_Section必须在同
一文件中,就是说,编译指令 #if,,
#then,#elseif 和 #else (如果存
在的话 ),以及同一嵌套层的 #endif
都必须在同一文件内。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 77
13.7.2 源文件包含
#include指令用于在编译中将另一文件的内容包含到程序的源代码中。它的
语法是,
pp_dir_include,
#include string_literal
string_literal 应该指定一个存在的文件名。这个文件名可以包含一个路径名,
但是必须记住用于给出子目录的反斜杠字符是一个换码符。基于此,当在直接写
入源文本的路径中使用反斜杠时,必须使用两个反斜杠字符。
#include "pfc\\exception\\exception.ph"
% Includes pfc\exception\exception.ph file
或者在文件名前加一个 @作前缀,
#include @"pfc\vpi\vpimessage\vpimessage.ph"
% Includes pfc\vpi\vpimessage\vpimessage.ph
这个指令使用“仅包含第一个出现的文件”的语义。也就是说,如果一个编
译单元中对于同一文件,有几个包含指令,那么它就只包含一次即第一个出现的
指令。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 78
13.7.2 源文件包含
每个被包含的文件必须包含一个或几个完整的作用域;一个被包含的
文件不能包含不完整的作用域 。 也就是说它应当包含一个或几个已完成
的接口声明, 类声明和类执行, 或者几个预处理程序指令 。
编译器以下列方式查找指定的包含文件,
1) 如果文件名包含一个绝对路径, 那么这个文件就应当被包含;
2) 否则, 编译器在当前目录中搜索指定的包含文件;
3) 否则, 编译器在由 /Include命令行选项定义的路径中搜索指定的包
含文件 。 因为这些路径在该选项中被指定, 所以得到处理 。 在 VDE 中,
可以在 Project Settings 对 话 框 的 Directories 标签的 Include
Directories中设置这些路径 。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 79
13.7.3 编译时间信息
指令 #message,#requires,#orrequires和 #error用于在编译程序模块
时发布用户定义的消息到一个列表文件,并中断编译。
这些指令既可以用于作用域(接口声明、类声明或类实现)的外部,也可以
用于作用域的内部,但应在段的外部。其语法如下,
pp_dir_message,
#message string_literal
pp_dir_error,
#error string_literal
pp_dir_requires,
#requires string_literal pp_dir_orrequires-list-opt
pp_dir_orrequires,
#orrequires string_literal
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 80
13.7.3 编译时间信息
当编译器遇到这些指令中的任何一个指令的时候, 它将产生相应的警告消息,
并将该指令文本放入一个列表文件 。
一个列表文件名可以用下列编译指令来指定,
/ListingFile:"FileName"
注意在冒号,,”(在 /ListingFile之后 )与 "FileName"之间没有空格 。
缺省时, 编译器不为 #message,#requires和 #orrequires指令产生情报消
息 。 我们可以通过下面指定的编译器选项, 打开这些情报信息的产生功能 。
/listing:message
/listing:requires
/listing:ALL
在这种情况下, 当编译器遇到像这样的指令,
#message "Some message"
它 就 把 下 面 的 文本 放 入 列表 文 件,C:\Tests\test\test.pro(14,10),
information c062,#message "Some message"
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 81
13.7.3 编译时间信息
#requires 和 #orrequires指令的例子如下,
#requires @"\Common\Sources\CommonTypes.pack"
#orrequires @"\Common\Lib\CommonTypes.lib"
#orrequires @"\Common\Obj\Foreign.obj"
#if someClass::debugLevel > 0 #then
#requires @"\Sources\Debug\Tools.pack"
#orrequires @"\Lib\Debug\Tools.lib"
#else
#requires @"\Sources\Release\Tools.pack"
#orrequires @"\Lib\Release\Tools.lib"
#endif
#orrequires "SomeLibrary.lib"
#requires "SomePackage.pack"
#if someClass::debugLevel > 0 #then
#orrequires @"\Debug\SomePackage.lib"
#else
#orrequires @"\Release\SomePackage.lib"
#endif
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 82
13.7.3 编译时间信息
#message指令的例子,
#message "Some text"
#if someClass::someConstant > 0 #then
#message "someClass::someConstant > 0"
#else
#message "someClass::someConstant <= 0"
#endif
class someClass
#if,:compiler_version > 600 #then
#message "New compiler"
someConstant = 1,
#else
#message "Old compiler"
someConstant = 0,
#endif
end class someClass
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 83
13.7.3 编译时间信息
#error指令的例子,
#if someClass::debugLevel > 0 #then
#error "Debug version is not yet implemented"
#endif
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 84
第十三章小结
本章介绍 Visual Prolog编译单元的有关内容,包括接口、类声明,
类实现、各种类型转换、条件编译、异常处理、预处理程序指令等。
一个程序由若干编译单元组成。编译器分别编译这些编译单元。编译
的结果是一个个目标文件。这些目标文件(可能还有其它文件)连接在
一起形成项目的目标文件。一个程序必须确实包含一个目标段,它是程
序的入口点。
第三部分:第 13章 编译单元
2004.11.3 AI程序设计 85
第十三章习题
1、理解接口的基本概念,为什么要引入接口声明?
2、分析下列程序段中的非法接口。
interface aaa
predicates
insert, (integer X) procedure (i),
end interface
interface bbb
predicates
insert, (integer X) procedure (i),
end interface
interface cc
supports aaa,bbb % conflicting interfaces
end interface
3、作用域限定中的支持限定、开放限定、继承限定、归结限定和委托限定分别
是什么含义?
4,Visual Prolog中的 This修饰与其他高级程序设计语言中的 This有什么区别?