Loading AI tools
来自维基百科,自由的百科全书
Simula,一種編譯式的程式語言,由奧利-約翰·達爾與克利斯登·奈加特,在1960年代於挪威奧斯陸的挪威计算中心,開發出來了Simula I與Simula 67兩代。它承繼了ALGOL 60作為基礎,被認為是第一個物件導向程式設計的程式語言。
编程范型 | 多范型: 指令式, 过程式, 结构化, 物件導向, 并发 |
---|---|
設計者 | 奧利-約翰·達爾 |
實作者 | 克利斯登·奈加特 |
发行时间 | 1962年 1967年 (Simula 67) | (Simula I)
当前版本 |
|
型態系統 | 静态、名称式 |
作用域 | 词法 |
實作語言 | 主要为ALGOL 60(有一些Simscript成份) |
操作系统 | 类Unix系统、Windows、z/OS、TOPS-10、MVS |
主要實作產品 | |
Portable Simula Revisited[2], GNU Cim[3] | |
啟發語言 | |
ALGOL 60, Simscript[4] | |
影響語言 | |
Smalltalk[5]、CLU[6]、C++、BETA、Object Pascal、Modula-3、Java |
Simula 67介入了对象、类、子类(后来惯称为子类继承超类)、虚过程[8],还有协程、离散事件模拟和特征性的垃圾收集[9]。Simula的影響经常被低估[10],Smalltalk[5]、CLU[6]、C++、Object Pascal、Modula-3、Java和后来的很多编程语言,受到了Simula 67的启发。BETA是Simula的现代后继者。
1957年,克利斯登·奈加特開始在NDRE的Ferranti Mercury電腦上寫模擬器,為此他需要更強大的程式語言。1962年1月,奧利-約翰·達爾開始跟他合作,他們受到ALGOL 60的啟發。1962年5月,發展出第一份模擬器程式語言提議,語言取名為SIMULA,后改称为Simula I。此時,克利斯登·奈加特受到Sperry Rand公司邀請,去協助他們開發UNIVAC 1107電腦。UNIVAC軟體部門的主管鮑伯·貝莫,力邀克利斯登·奈加特前往國際資訊處理協會(IFIP)舉行的第二次國際會議上,發表了論文「SIMULA-擴展ALGOL到離散事件網路的描述」[11]。
1963年8月,挪威計算中心(NCC)購買到UNIVAC 1107,在UNIVAC的合約同意下,奧利-約翰·達爾在這台電腦上安裝以凯斯西储 1107 ALGOL 60的編譯器來實作的Simula I。1965年1月,Simula I終於可以在UNIVAC 1107上完全的運作。Simula I在1968年被移植到了Burroughs B5500電腦,以及后来蘇聯的URAL-16電腦。
1965年,東尼·霍爾首次提出“記錄類”(record class)構造的概念[12],1966年,克利斯登·奈加特與奧利-約翰·達爾二人,將Simula I的具有“准并行”性质的“進程”,擴展成了具有了記錄類性质的廣義進程,隨即改稱為“物件”[13]。1967年5月,奈加特和達爾在奧斯陸舉辦的IFIP工作小組論壇中,發表了關於類與子類聲明的論文,形成Simula 67的第一份定義文件[14]。
1968年召開的會議,組成了SIMULA標準小組,並發表了第一份官方Simula標準文件「SIMULA 67通用基础语言」[15]。在1960年代后期和1970年代早期,Simula 67主要实现于四个系统之上:挪威计算中心的UNIVAC 1100系列,挪威计算中心的IBM System/360和System/370,奥斯陆大学在Kjeller联合安装的CDC 3000系列,和瑞典国防研究所(FOA)的DEC TOPS-10。
当前的Simula 67业界标准,是在1986年修订的“标准SIMULA”[7],它在1987年被接受为瑞典国家标准[16]。它有四个主要实现:Simula AS、Lund Simula、GNU Cim[3]和Portable Simula Revisited[2]。
Simula影响了Smalltalk[5],和后来的面向对象编程语言[10]。C++語言和Java語言的創始人,都認可自己受到了Simula的重要影響[17]。
Simula 67包含通用算法语言ALGOL 60的多数特征作为自己的子集[7],它是大小写不敏感的。Simula 67的中心概念是对象,对象是自我容纳(self-contained)的一段程序即一个块实例,它拥有由一个类声明定义的自己的局部数据和行动(action)。类是一种过程[18],它有能力引起在它被调用后仍存活的一个块实例,而这些实例就叫做这个类的对象。为了操纵对象和相互关联对象,语言介入了链表处理设施。
Simula 67为了将整个程序执行组织为:对象所属的诸“行动阶段”的一个序列,而将其所必需的基本功能特征,包含在作为“系统类”的“环境类”之中。在环境类中有被称为“标准系统类”的模拟器类Simulation
,它定义了充当系统时间轴的“定序集合”(sequencing set)对象SQS
、进程类Process
和事件通告类EVENT_NOTICE
,定序集合的成员是事件通告对象,它通过其PROC
特性提及一个进程对象;事件通告表示了对应这个进程对象的下一个行动阶段的一个事件,被调度在系统时间EVTIME
时发生。
块是成序列的声明,跟随着成序列的语句,并被包围在关键字begin
和end
之间。块自身是一种语句[19],在Simula 67中,它包括子块和有前缀(prefixed)块,在语法上,子块就是无前缀的ALGOL 60的块,即匿名块和过程主体。
声明担负定义在程序中用到的量(quantity)的特定属性,并给它们关联上标识符,这包括:简单变量、数组、switch
(标签列表)、过程、类和外来声明。块中的每个语句,都可以是块或复合语句。复合语句与块相比在语法上没有声明。
所有的块,自动地介入名称目录(nomenclature)的一个新的层级:在这个块内出现的标识符,可以通过合适的声明,而被指定为局部于所论及的这个块;这个标识符在这个块里面的所表示的实体,不存在于它的外面;这个标识符在这个块外面的所表示的任何实体,在这个块里面是不可见的;在Simula 67中,可通过连接或远程访问使它成为可见的。
除了表示标签的标识符之外,一个标识符,如果出现在一个块中,而且并非声明于这个块中,则它非局部于这个块。因为块中的语句自身可以是一个块,局部和非局部于一个块的概念,必须递归地去理解。
块是一种形式描述或模式,关乎汇聚的数据结构和关联的算法以及行动[20]。当一个块执行的时候,生成这个块的一个动态实例[21]。一个块实例,可以被认为是它的形式描述的文字复本。在计算机中,一个块实例可以采用某一种形式的内存区域,它包含需要的动态块信息,并包括空间来持有局部于这个块的变量的内容[22]。
块的任何一个内部的块,仍然是一个模式,但是在其中出现的非局部标识符,标定了局部于在字面上外在包围(textually enclosing)的块实例的项目[23]。非局部于内部的块的标识符绑定,对这个内部的块的任何随后的动态实例,保持有效[24]。
块实例中的局部变量,标识了分配给块实例的内存片段。在进入一个块之中的时候,任何局部于这个块即在其中声明的变量,都会被初始化。局部于一个块的一个变量,是一个内存设备,其内容依据这个变量的类型,要么是一个值,要么是一个引用。
值类型可特征化为直接关联着一个可能值的集合,即这个类型的“值范围”。引用概念,对应于一个名字或一个“指针”的直观想法。在Simula 67中有两种引用类型,对象引用类型和文本引用。对于值类型不关联着引用概念。提供了引用值的机制,还反映了机器的寻址可能性;在特定简单情况下,一个引用可以被实现为一个存储的值的内存地址。
过程与块相比,它有一个名字,可以在程序的一些不同地方提及,并且在调用时可以给出参数[25]。过程和块都不可能建立到它或它内部的引用[25];对于一个给定块,有可能生成它的一些可以共存和交互的实例[22],例如递归过程的实例。
过程的参数传送模态,除了有传值调用和传名调用[26],在Simula 67中,又增加了传引用调用。过程的缺省的传送模态,在Simula 67中,对于值类型的参数是传值调用,对于所有其他类型的参数是传引用调用;故而在过程声明的参数规定中,增加了以name
为前导的名字部份,用来指定所述及的参数采用传名调用。
在过程主体内传名调用的形式参数的每次出现,都引起对实际参数的一次求值。在Simula 67中,这个求值发生在过程语句的上下文中,就是说不会出现标识符冲突,因为过程主体和它的变量此时是不可见的。过程调用的执行,在有参数的情况下要经历如下步骤:建立形式参数块实例;求值对应于传值调用或传引用调用的实际参数,并将其结果赋值给形式参数块实例的对应变量;过程主体被初始化并开始执行。
真正(proper)过程,不定义特定类型的函数指定式的值,在Simula 67中,它被称为具有普遍(universal)类型,任何类型都从属(subordinate)于普遍类型。
类声明定义一个程序(数据和行动)模式,而符合(conforming)这个模式的对象被称为“属于相同的类”。不同于Smalltalk 80等面向对象编程语言,Simula 67的类和后来的Modula-3的对象类型,不进行实例变量与类变量的区分。
对于一个给定对象,类声明中形式参数、在虚拟部份中规定的虚拟量,和声明为局部于类主体(body)的量,叫做这个对象的特性(attribute)。一个特性的声明或规定叫做特性定义。在1986年修订的语言标准中,通过保护规定可以限制类特性标识符的可见性。
类声明中的形式参数,不适用传名调用,这是为了保证实际参数在类所引起的对象存活期间不能消失[27]。在规定部份中需要对每个形式参数进行规定,这些参数被当作局部于类主体的变量。类主体通常是一个块,即使它如语法形式所允许的那样是块以外的一个语句,也表现得如同一个块。一个分裂(split)主体表现为一个块,在其中符号inner
表示一个虚设(dummy)语句。
Simula 67的类主体中除了有定义特性的声明,还可以有定义行动的语句。如果在一个类声明中有行动定义,则符合这个模式的行动,可以由属于这个类的所有对象执行。如果在类声明中没有指定行动,则定义了一类纯数据结构,很多后来的面向对象编程语言就是这样,将对象作为记录或结构的扩展。例如:
class Order(number); integer number;
begin
integer numberOfUnits, arrivalDate;
real processingTime;
end;
属于特定类的对象,通过对象表达式中的对象生成式new
来生成,并制作出到它的引用。例如,想要多少就能生成多少属于这个Order
类的一个新对象:
new Order(103);
属于一个对象的行动,可以都在就一个过程而言的一个序列中执行。这些行动,还可以作为一系列的独立子序列或“行动阶段”(active phase)来执行。在一个给定对象的两个行动阶段之间,可以出现任何数目的其他对象的行动阶段。
一个类可以被用作到另一个类声明的“前缀”,从而将前缀所定义的性质,建造入这个新的类声明定义的对象之中。具有前缀类C
和类标识符D
的一个类声明,定义了类C
的一个子类D
。属于子类D
的对象,由自身是类C
的对象的“前缀部份”,和类D
的类声明主体所描述的“主体部份”组成。这两部份串接而形成了一个复合对象,它可以用串接成的类声明来正式的描述,串接的过程被认为先于程序执行而发生。
类只能在定义它的块层级中用作前缀。前缀类自身也可以有前缀。类不能出现在自己的前缀序列中。子类被称为内部(inner)于前缀类,而前缀类被称为外部(outer)于子类,但不可将子类称为前缀类的内部类。这不同于后来的面向对象编程语言中的称谓:“派生类”扩展“基础类”,或“子类”继承“超类”。例如:
Order class SingleOrder;
begin
real setupTime, finishingTime, weight;
end;
SingleOrder class Plate;
begin
real length, width;
end;
属于Order
类的子类如SingleOrder
类和Plate
类的新对象,都含有Order
类定义的数据,再加上在各种类声明中定义的补充数据。例如,属于Plate
类的对象含有Order
类、SingleOrder
类和Plate
类定义的所有数据。
一个标识符在一个块中的出现,如果并未处在远程标识符之中或在有重新定义的内部的块之中,则被称为“未托付”(uncommitted)出现。对于子类的声明及其前缀类串接成的类声明,在局部于子类的特性标识符,和局部于其前缀类的特性标识符,二者的未托付出现之间的可能冲突,将凭借对涉及到的局部于子类的特性标识符的适合的系统性变更来避免,但是对应虚拟量的标识符不变更。
对于前缀类和子类声明串接成的类声明,它的形式参数列表,由前缀类的形式参数列表,和附加的子类声明的参数列表二者合并而成。它的值部份、规定部份和虚拟部份,是前缀类和子类声明相应各部份的并集。结果的虚拟部份不能包含某个标识符的多于一次出现。
对于类主体,最初的begin
和随后的声明被称为“块头部”,从第一个可执行语句直到inner
之前的语句被称为“初始运算”,在inner
之后的语句和最终的end
被称为“复合尾部”。如果前缀类主体是分裂主体,用前缀类的块头部复本替代子类的块头部中的begin
,将前缀类的初始运算复本插入到子类的块头部之后,用前缀类的复合尾部复本替代子类的复合尾部中的end
。如果前缀类主体不是分裂主体,它被解释为如同将;inner
插入到最终的end
之前。
设Cn
是具有前缀序列C1, C2, ……, Cn-1
的一个类,这里类Ck
(k = 1, 2, ……, n)
的下标k
叫做前缀层级,X
是属于类Cn
的一个对象,则X
是一个复合对象;非正式的说,串接机制有如下结果:
X
拥有的特性集合,是在序列C1, C2, ……, Cn
中诸类所定义特性集合的并集。在类Ck
(1 <= k <= n)
中定义的特性,被称为定义在前缀层级k
。X
拥有的“运算规则”,由来自这些类主体的诸语句构成,它们所属的前缀层级遵循规定的次序。来自类Ck
的语句,被称为属于对象X
的前缀层级k
。X
的前缀层级k
的语句,能访问在对象X
的等于或外部于k
的前缀层级上定义的它的所有特性,但是如果外部的类Ci
(i < k)
中存在冲突定义,则导致对应特性不可见而不能直接访问。这些不可见特性仍有访问方式,例如通过使用过程或this Ci
。X
的前缀层级k
的语句,不能立即访问在对象X
的内部于k
的前缀层级上定义的它的那些特性,除非通过虚拟量。k
的分裂主体中,符号inner
,表示对象X
的属于内部于k
的前缀层级的运算规则的诸语句,或者在k = n
时表示虚设语句。如果序列C1, ……, Cn-1
都没有分裂主体,则在对象X
的运算规则中诸语句所属的前缀层级遵循递增次序。用作前缀的系统类,与其前缀链的所有的类一起,被当作声明于最小的包围其字面上出现的块中。因此重新声明可以出现在一个程序的内部的块层级中。
有前缀块的一个实例,是由块前缀生成的一个单一的对象,与这个主体块的实例,串接而成一个复合对象,其串接定义采用类似于类的串接规则。块前缀的类的形式参数,按块前缀的实际参数所示初始化。对有前缀块有两个限制:一个类如果在其中通过使用this
来引用类自身,则它作为块前缀是非法的。块前缀的类标识符所提及的类,必须局部于最小的包围这个有前缀块的块。
当属于各种类的很多对象,作为同一个程序的各个部份而共存的时候,需要能够对个体对象指定名字,为此介入了叫做“引用”的新的基本类型;还要能相互关联对象,比如通过二叉树和各种其他类型的列表结构。Simula 67将循环双向链表叫做“集合”,它将集合类Simset
作为“标准系统类”介入为环境类的一部份。
对于某个类的一个对象,有一个唯一的关联的“对象引用”标定这个对象,并且对于任何类C
,都有一个关联的对象引用类型ref(C)
。这种类型的量,被称为由类C
限定;它的值,要么是一个对象,要么是表示“没有对象”的特殊值none
。
限定(qualification)将值的范围,限制至包括在限定类中的这些类的对象。不管何种限定,值的范围都包括值none
。一个对象引用的限定,是一个类标识符,它因此服从声明的作用域和可见性规则。限定关联于所提及的类声明所局部于的那个块的一个实例。这蕴涵着特定的关于限定有效性的检查,不能单独在程序正文的基础上进行,因此这种测试必须在程序执行期间完成。
引用是有限定的,蕴涵着一个给定引用所提及的对象,只能属于这个限定中提到的类,或属于这个限定类的子类。如果一个对象引用类型的限定,是另一个对象引用类型的限定类的子类,则前者被称为从属于后者。例如:
ref(Order) next, previous;
对象表达式是获得对一个对象的引用的一种规则。对象表达式的值,是被引用的对象或none
。运算符:-
读作“指称”(denote),它指示将一个引用,指派(assign)到处在“引用指派符”左侧的引用类型的变量或过程标识符。例如:
next :- new Plate(50); previous :- next; next :- none;
而运算符:=
读作“成为”(become),指示将一个值,指派到处在赋值符左侧的值类型的变量或过程标识符,或者将一个文本值,指派到在赋值符左侧者所引用的那个文本帧(frame)。
一个对象的一个特定特性,由下列三项信息来完全标定(identify):
对于任何特性标定,第二项类信息都是在字面上定义的,从而形成静态绑定[28]。属于其他对象的特性,可以要么单个的通过“远程标识符”(“点表示法”),要么成组的通过“连接”(connection)机制,从其外面“远程访问”来引用和使用。
远程标识符的形式为:简单对象表示式.特性标识符
,它可用作简单变量、数组变量、函数指定式(designator)和过程语句中的标识符,这种点表示法给出对信息的单独片段的访问,例如:
if next.number >= previous.number then ……;
它将名为next
的类Order
的对象的特性number
,比较于名为previous
的类Order
的对象的特性number
。
“成组访问”可通过连接语句inspect
来完成,例如:
inspect next when Plate do begin …… end; inspect new Plate(50) do begin …… end;
在这里的begin
和end
之间的语句中,next
引用的对象所属的Plate
类的所有信息片段,都可以直接提及。
在do
后面的语句,如果是块以外的一个语句,它表现得也如同一个块。连接语句进而表现得如同被叫做“连接块”的第二个虚构(fictitious)的块所包围。连接机制的用途,是为在连接块中的特定特性标定,提供对象信息和在when
子句中的类信息的隐式定义。
设对象表达式求值为X
,在连接块执行期间,称对象X
为“被连接上”了。连接块有一个关联的“块限定”,如果连接块有when
子句,它是前面的类标识符,如果没有when
子句,它是前面的对象表达式的限定。
虚拟(virtual)量,是在类声明中由virtual:
前导的量,它有双重用途:
不同于一般的特性标定,虚过程形成“半动态绑定”[8],在某种意义上是类似传名调用的机制[27]。这种机制不同于Smalltalk 80等面向对象编程语言中,上溯子类至超类的继承链来找到最近的方法实现的那种动态绑定。
一个对象的一个虚拟量,要么是“无匹配的”的,要么被标定为具有一个“匹配”特性,它是在这个虚拟量的前缀层级上或在内部前缀层级上声明的,其标识符重合(coincide)于虚拟量的标识符的一个特性。匹配特性必须与虚拟特性有相同的种类。在一个给定前缀层级上的匹配量的类型,必须重合或从属于这个虚拟规定的类型,和在任何外部前缀层级上声明的任何匹配量的类型。
在1986年修订的语言标准中,虚过程量可选的可使用procedure <过程标识符> <过程规定>
方式来指定,即凭借它的类型,并且如果它有形式参数,则还凭借它们的类型、种类和传送模态。含有过程规定is <过程声明>
的虚过程量,只能由有相同类型的过程来匹配,并它与这个过程规定具有相同的过程头部。
例如,下面实现散列表的HashTable
类,声明了整数类型的虚过程hash
并随即实现了它,又定义了要用到这个散列函数hash
的查找过程lookup
:
class HashTable(n); integer n;
virtual: integer procedure hash;
begin
integer procedure hash(t); text t;
begin
……
end hash;
text array table(0 : n-1); ……
integer procedure lookup(t, old);
name old; Boolean old; text t;
begin
integer i, istart; Boolean entered;
i := istart := hash(t);
while not entered do
begin
……
end;
lookup := i;
end lookup;
end HashTable;
HashTable class ALGOL_hashing;
begin
integer procedure hash(t); text t;
begin
……
end hash;
end ALGOL_hashing;
作为子类的ALGOL_hashing
类和以HashTable
类为前缀的有前缀块,可以用自己的hash
过程实现,替代在HashTable
类中的实现。在子类的类主体和有前缀块的主体块中,能获得到过程lookup
,而它用到的过程hash
是这里提供的替代实现。
对象表达式,具有类型ref(限定)
。对象表达式的限定规则包括:对象生成式(generator)、局部对象或限定对象,分别由跟随符号new
、this
或qua
的标识符的类来限定(qualification)。
对象生成式new C
,这里标定了类C
,它涉及到属于类C
的对象的生成和执行。这个对象是类C
的一个新实例。对象生成式的求值构成自如下行动:
begin
进入其中,它籍此变成在“系附”状态下运行。当所生成的对象执行了detach
基本过程变为“脱离”状态,或者经过这个对象最终的end
退出而变为“终止”状态,对象生成式的求值完成。局部对象this C
,这里的C
是类标识符。如果用在类C
或C
的任何子类的类主体中,或用在其块限定为类C
或C
的一个子类的连接块中,则这个对象表达式是有效的。在一个给定的上下文中,一个局部对象的值,是一个对象或连接到一个对象,它是这个对象表达式在其中有效的、最小的在字面上外在包围的块实例;如果没有这样的块,则这个对象表达式在这个上下文中是非法的。对于一个过程或类主体的实例(上下文),“在字面上外在包围”(textually enclosing)意为包含它的声明。
暂瞬限定X qua C
,这里的X
表示一个简单的引用表达式,而C
是类标识符。设类D
是对象X
的限定,对于限定对象X qua C
,如果类C
外部于或等于类D
,或者是D
的一个子类,则这个对象表达式是合法的,否则是为非法。如果X
的值是none
,或者是属于外部于C
的类的一个对象,则这个对象表达式求值形成一个运行时间错误;在其他情况下,X qua C
的值,就是X
的值。对一个串接的类对象的暂瞬限定,限制或扩展它的特性通过检视或远程访问的可见性。
对象关系表达式,使用运算符is
和in
,来测试一个对象的类成员关系。关系X is C
,在X
引用的是属于类C
的对象之时,值为true
,否则值为false
。关系X in C
,在X
引用的是属于类C
的对象,或者X
是内部于类C
的类之时,值为true
,否则值为false
。
不同于算术关系表达式使用比较运算符=
和<>
,对象引用关系表达式,使用比较运算符==
和=/=
,来比较引用,从而区别于对应的被引用的值。两个对象引用X
和Y
,在它们都引用相同的对象,或者它们都是none
之时,被称为是“同一”的。关系X == Y
,在这种情况下拥有的值为true
,否则值为false
。关系X =/= Y
的值,是X == Y
的值的否定。
不同于ALGOL 60规定了量的作用域与可见性,在Simula 67中,一个标识符定义及其关联的标识符,在其作用域与可见性之间需要做出区别。
一个标识符定义的局部块,是在字面上最近的包围块,即子块、前缀块、过程主体或类主体,还包括围绕for
语句的受控语句、过程声明、类声明和连接块等的虚构块。这个标识符和它的定义,被称为局部于这个块。
标识符定义,只在它们的作用域内是可见的。一个特定定义的可见性,在它的作用域内可以受到下列限制:
this
或qua
,可以导致一个或多个重新定义被暂时停止。hidden
及或protected
前导的保护部份中。一个标识符的重新定义,不允许出现在它的局部块的头部,这禁止了在同一个块中出现相同标识符的两个定义。
一个程序执行的各个组成部份,都是块即子块、有前缀块、连接块和类主体的动态实例。一个块实例,被称为“局部于”直接包含它的描述文本的那个块实例。例如一个给定类的实例,局部于包含这个类声明的块实例。最外层块的实例不局部于块实例。
在任何时间,“程序顺序控制”(Program Sequence Control),首字母简写为PSC,参照在一个块实例中当前被执行的程序点;形象的说,PSC“定位”至这个程序点,并被这个块实例所“包含”。进入任何块,都涉及到生成这个块的一个实例,于是PSC进入这个块实例,到达它的第一个可执行语句上。
一个块实例在任何时间,都是处在四种执行状态之一:系附(attached)、脱离(detached)、恢复(resumed)或终止(terminated)。没有在字面上给出前缀的任何类,都被定义为前缀了一个虚构(fictitious)的类,它只有一个特性即detach
过程,因此所有类对象或有前缀块的实例,都拥有detach
过程。在环境类ENVIRONMENT
中,还定义了用于调度的call
过程和resume
过程。
end
的时候,PSC返回到紧随导致这个块实例生成的语句或表达式的程序点。detach
过程,进入脱离状态。通过执行call
过程,可以使一个脱离状态的对象重新进入系附状态,它借此变为系附到包含这个调用语句的那个块实例上。通过执行resume
过程,可以使一个脱离状态的对象进入恢复状态。不使用detach
、call
或resume
过程的一个程序执行,是一个简单的系附状态的块的嵌套结构。end
,或通过goto
语句离开它的时候,这个对象进入终止状态。没有块实例系附到一个终止状态的类对象上。终止状态的对象,仍作为一个数据项而存在,它可以通过针对这个对象的包括过程和函数特性的这些特性的远程标定来引用。每当一个块实例不复存在,局部于或系附于它的所有块实例也不复存在。一个对象的动态作用域,因而受限于它的类声明的作用域。在Simula 67最初标准中曾提到过,语言实现可以使用垃圾回收技术[9],来进一步缩减一个对象的有效寿命的跨度。一个数组声明的动态作用域,可以扩展超出包含这个声明的块实例的作用域,因为传引用调用参数传送模态适用于数组。
一个准并行系统[29],由“构件”(component)构成。构件是块实例组成的嵌套结构,其中标定(identify)这个构件的那个块实例,叫做“构件头领”。在每个系统中,构件中总是有一个被称呼为“主构件”,在Simula 67最初标准中它叫做“主程序”,其他构件叫做“对象构件”。
构件的定序(sequencing),由detach
、call
和resume
过程支配。detach
过程针对隐式指定的一个对象进行操作,而call
和resume
过程显式指定所操作的对象。
一个准并行系统,由包含局部的类声明的一个子块或有前缀块的任何实例所标定。标定了一个系统的块实例,叫做“系统头领”。一个系统的主构件的头领(head),重合(coincide)于系统头领。最外层的标定了一个系统的块实例,被称为“最外层系统”。
一个系统的主构件的头领,总是处在系附状态。一个系统的对象构件的头领,确切的就是局部于系统头领的那些脱离状态的或恢复状态的对象。在任何时间,在一个系统的构件之中,有确切的一个构件被称为“生效”(operative)的。不生效的构件,有关联的“重新激活点”,它标定在这个构件被激活(activate)时,要在它那里继续执行的程序点。一个对象构件是生效的,当且仅当这个构件的头领处在恢复状态。
称谓一个块实例P
被一个块实例X
“动态包围”,当且仅当存在一个块实例序列:(P = Z0), Z1, ……, Zn-1, (Zn = X)
(n >= 0)
,使得对于i = 1, 2, ……, n
有着:Zi-1
系附到Zi
;或者Zi-1
是一个恢复状态的对象,它关联的系统头领系附到Zi
。终止状态和脱离状态的对象,不被除了自身之外的块实例动态包围。
将当前包含PSC的块实例动态包围起来的块实例链,叫做“运行链”。在运行链上的块实例,被称为是“运行”(operating)的,最外层的块实例总是运行的。一个构件只要其头领是运行的它就是运行的。
一个系统如果有一个构件是运行的,它就是运行的;在任何时间,一个系统最多有一个构件是运行的;运行的系统的头领,可以是不运行的。一个运行的构件总是生效的;如果一个系统的生效构件是不运行的,则这个系统也是不运行的。在不运行的系统中的生效的构件,是当这个系统成为不运行的时候在运行的构件,当这个系统再次成为运行的时候它将仍是运行的。
对于一个不生效的构件C
,设它的头领是X
,如果一个块实例P
包含了它的重新激活点,则P
被X
动态包围,并且P
除了自身之外不动态包围块实例。由构件头领X
动态包围的这个块实例序列,被称为构件C
的“重新激活链”。除了X
之外,这个链上的所有构件头领,标定了生效而不运行的构件。在构件C
成为运行的时候,在它的重新激活链上所有块也成为运行的。
除了系统构件,程序执行还可以包含不属于特定系统的“独立对象构件”。任何这种构件的头领,都是一个脱离状态的对象,它局部于一个类对象或一个过程主体实例,也就是说不局部于某个系统头领。根据定义,独立构件总是不生效的,只能对它执行call
过程。
准并行系统的detach
/resume
机制,是一种协程[30]。在Simula 67最初标准中,没有恢复状态,并且没有规定call
过程,但call
过程通常会出现在当时的语言实现中[31]。detach
/call
机制,相当于现在称为生成器的“半协程”,而最初标准中的resume
过程可理解为detach
过程与call
过程的组合[32]。1986年修订的语言标准,增补定义了call
过程,新增了恢复状态,并且重新定义了resume
过程,它不可再理解为detach
过程与call
过程的组合。
准并行系统,被创建于进入包含局部的类声明的一个子块或有前缀块的时候,借此生成的实例成为这个新系统的头领。初始时,这个主构件是生效的,并且是这个系统唯一的构件。
建立一个对象构件,要通过针对一个系附状态的对象,执行detach
过程,借此PSC返回到这个对象系附到的那个块实例。这个对象进入脱离状态,并成为一个新的不生效的构件的头领,这个构件的重新激活点被定位到紧后于这个detach
过程调用。如果这个对象局部于一个系统头领,则这个新的构件成为这个关联的系统的成员。
通过detach
和call
过程,可以形成“半对称定序”,这只涉及到对象构件,而不区分它们是属于系统的构件还是独立的构件。
call
过程,可以重新激活这个构件,借此PSC移动到它的重新激活点上。这个头领重新进入系附状态,并变成系附到包含这个call
过程调用的块实例上。这个构件正式的失去了本身(作为构件)的状态。通过detach
和resume
过程,可以形成“对称构件定序”,这只涉及到属于一个准并行系统的那些构件。对立于半对称定序中的“调用者”与它的“被调用者”,在对称构件定序中的“恢复者”与它的“被恢复者”,具有完全的对称联系。
resume
过程,可以重新激活这个构件,借此PSC移动到它的重新激活点上;这个构件的头领进入恢复状态,而这个构件变成生效的。这个系统以前生效的构件变成不生效的,并且它重新激活点被定位到紧后于这个resume
过程调用;如果这个构件是一个对象构件,则它的头领进入脱离状态。detach
过程调用,这个系统的主构件重获生效的状态,借此PSC移动回到主构件的重新激活点上。以前生效的构件变成不生效的,并且它重新激活点被定位到紧后于这个detach
过程调用。这个构件的头领进入脱离状态。PSC经过一个类对象的最终end
的效果,除了使这个对象成为终止状态,而非脱离状态之外,相同于针对这个对象执行detach
过程的效果。其结果是它不会得到重新激活点,并且在它已经拥有作为构件头领的状态时,失去这种状态。
Simula 67下的经典Hello, World!範例:
begin
outtext("Hello, World!");
outimage;
end;
outtext
过程将字符串输出到缓冲区,而outimage
过程将缓冲区内容输出到标准文件,二者都定义在输出文件类OutFile
中,而它是文件类File
的子类。
class Tree(val); integer val;
begin
ref(Tree) left, right;
procedure insert(x); integer x;
begin
if x < val then
begin
if left == none then
left :- new Tree(x)
else
left.insert(x)
end
else if right == none then
right :- new Tree(x)
else
right.insert(x);
end insert;
ref(Tree) procedure find(x); integer x;
begin
if x = val then
this Tree
else if x < val then
(if left == none then
none
else
left.find(x))
else if right == none then
none
else
right.find(x);
end find;
end Tree;
在find
过程的主体中出现了表达式this Tree
,它产生的值所引用的是当前节点。这里通过函数指定式X.find(x)
,来调用对象X
的find
过程,如果X.val = x
,则这个函数的结果是到X
自身的引用值。
Simula 67标准通过如下实例来诠释叫做“准并行系统”的协程机制:
begin comment 系统S1;
ref(C1) X1;
class C1;
begin
procedure P1;
detach;
P1
end C1;
ref(C2) X2;
class C2;
begin
procedure P2;
begin
detach;
! 可尝试detach或resume(X1);
end P2;
begin comment 系统S2;
ref(C3) X3;
class C3;
begin
detach;
P2
end C3;
X3 :- new C3;
resume(X3)
end S2
end C2;
X1 :- new C1;
X2 :- new C2;
call(X2); ! 可尝试resume(X2);
end S1;
这个例子程序中,有两个准并行系统S1
和S2
。系统S1
是对应最外层子块的最外层系统,它包含了两个类声明C1
和C2
。系统S2
对应于类C2
的类主体中的匿名块,它包含了一个类声明C3
。
在PSC进入最外层子块开始处,产生系统S1
的系统头领,到达第28行的系统S1
主构件的第一个可执行语句,开始生成对象X1
,进入类C1
,PSC到达第7行的类主体的第一个可执行语句,调用了类C1
的特性过程P1
,进入第6行的过程体的第一个可执行语句,这时的状况是:
[S1] ← (X1) ← (P1) ← PSC
这里方括号表示系统头领,圆括号表示其他种类的块实例,左向箭头表示系附,使用粗体表示这个块实例执行了对象生成式。执行第6行的detach
语句后,对象构件头领X1
进入脱离状态,保存X1
的重新激活点为第6行P1
过程体结束,回溯到第28行对象产生式结束,系统S1
的对象X1
生成完毕,这时的情况是:
[S1] ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
这里的竖杠将对象构件头领列为一个系统的成员。PSC到达系统S1
主构件第29行,开始生成对象X2
,进入类C2
,PSC到达第17行的类主体中匿名块开始处,进入这个子块产生系统S2
的系统头领,PSC到达第24行的系统S2
主构件的第一个可执行语句,开始生成对象X3
,进入类C3
,PSC到达第20行类主体开始处,这时的情况是:
[S1] ← (X2) ← [S2] ← (X3) ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
执行第21行类主体的第一个可执行语句detach
后,对象构件头领X3
进入脱离状态,保存X3
的重新激活点为第22行,回溯到第24行对象产生式结束,系统S2
的对象X3
生成完毕,这时的情况是:
[S1] ← (X2) ← [S2] ← PSC
| |
| (X3) ← <X3重新激活点>
|
(X1) ← (P1) ← <X1重新激活点>
PSC到达系统S2
主构件的第25行,执行resume(X3)
语句后,保存系统S2
主构件的重新激活点为第26行,对象构件头领X3
进入恢复状态,PSC恢复到第22行的对象X3
的类主体之中,这时的情况是:
[S1] ← (X2) ← [S2] ← <S2重新激活点>
| |
| (X3) ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
这里的下划线指示这个块实例处在恢复状态。执行第22行的类C2
的特性过程P2
,到达在第14行的过程体的第一个可执行语句,这时的情况如下,并标记为“状况A”:
! 状况A ; [S1] ← (X2) ← [S2] ← <S2重新激活点> | | | (X3) ← (P2) ← PSC | (X1) ← (P1) ← <X1重新激活点>
执行在第14行P2
过程体的detach
语句后,对象构件头领X2
进入脱离状态,保存X2
的重新激活点为第15行,回溯到第29行对象产生式结束,系统S1
的对象X2
生成完毕,这时的情况如下,并标记为“状况B”:
!状况B ; [S1] ← PSC | (X1) ← (P1) ← <X1重新激活点> | (X2) ← [S2] ← <S2重新激活点> | (X3) ← (P2) ← <X2重新激活点>
注意对象X3
仍是系统S2
的生效构件,它并没有自己的重新激活点。序列(P2, X3, X2)
是X2
的重新激活链,这里的X3
是恢复状态,而它的系统头领S2
系附到了X2
。
PSC进入系统S1
主构件第30行,执行call(X2)
语句后,对象构件头领X2
进入系附状态,PSC恢复为第15行的P2
过程体之中,这时的情况重现为前面的“状况A”(S1
不加粗体)。
如果将系统S1
主构件中第30行改写为一个resume(X2)
,执行后对象构件头领X2
进入恢复状态,保存系统S1
主构件的重新激活点为第31行,PSC恢复为第15行的P2
过程体之中,会出现如下情况:
[S1] ← <S1重新激活点>
|
(X1) ← (P1) ← <X1重新激活点>
|
(X2) ← [S2] ← <S2重新激活点>
|
(X3) ← (P2) ← PSC
序列(P2, X3, X2)
是X2
的运行链。
如果将P2
过程体中第15行的注释替换为一个detach
语句,执行后对象构件头领X2
进入脱离状态,保存X2
的重新激活点是第16行,PSC恢复到第31行的系统S1
主构件之中,这时的情况重现为前面的“状况B”。
如果将P2
中第15行的注释替换为一个resume(X1)
语句,执行后对象构件头领X2
进入脱离状态,对象构件头领X1
进入恢复状态,保存X2
的重新激活点为第16行,PSC恢复到第6行的P1
过程体的过程结束,会出现如下情况:
[S1] ← <S1重新激活点>
|
(X1) ← (P1) ← PSC
|
(X2) ← [S2] ← <S2重新激活点>
|
(X3) ← (P2) ← <X2重新激活点>
下面例子中,定义了一个字形类Glyph
,它是抽象基础类,并且有二个实现子类:字符类Char
和行类Line
:
begin
class Glyph;
virtual: procedure print is procedure print;
begin
end;
Glyph class Char(c);
character c;
begin
procedure print;
outchar(c);
end;
Glyph class Line(cs);
ref(Glyph) array cs;
begin
procedure print;
begin
integer i;
for i := 1 step 1 until upperbound(cs, 1) do
cs(i).print;
outimage;
end;
end;
ref(Glyph) rg;
ref(Glyph) array rgs(1 : 4);
rgs(1) :- new Char('A');
rgs(2) :- new Char('b');
rgs(3) :- new Char('b');
rgs(4) :- new Char('a');
rg :- new Line(rgs);
rg.print;
end;
这里的虚过程量print
具有过程规定is procedure print
,它匹配既没有形式参数也没有结果值的print
过程,如果不加以这种过程规定,则它可以匹配具有任何形式参数和任何结果值的print
过程。在Simula 67中,没有不可以实例化带有纯虚过程类的特定限制,因而缺乏真正的抽象类的概念,所有类都可以被实例化,但是调用纯虚过程会产生运行时间错误。
在下面的离散事件模拟例子中,Sam、Sally和Andy正在逛商店买衣服,他们必须共享一个试衣间。他们每人只能浏览商店大约12分钟,并接着独占的使用试衣间大约3分钟,每个行动都服从正态分布,他们的试衣间经历被模拟如下:
Simulation
begin
class FittingRoom;
begin
ref(Head) door;
Boolean inUse;
procedure request;
begin
if inUse then
begin
wait(door);
door.first.out;
end;
inUse := true;
end;
procedure leave;
begin
inUse := false;
activate door.first;
end;
door :- new Head;
end;
procedure report(message); text message;
begin
outfix(time, 2, 0);
outtext(": " & message);
outimage;
end;
Process class Person(pname); text pname;
begin
while true do
begin
hold(normal(12, 4, u));
report(pname & " 要求用试衣间");
fittingroom1.request;
report(pname & " 已进入试衣间");
hold(Normal(3, 1, u));
fittingroom1.leave;
report(pname & " 已离开试衣间");
end;
end;
integer u;
ref(FittingRoom) fittingRoom1;
fittingRoom1 :- new FittingRoom;
activate new Person("Sam");
activate new Person("Sally");
activate new Person("Andy");
hold(100);
end;
主程序是前缀着模拟器类Simulation
的有前缀块。模拟器类可在任何块上使用,而且模拟器甚至可以嵌套,比如在模拟某人做模拟的时候[33]。时间过程time
、等待过程wait
、保持过程hold
和激活过程ACTIVAT
,定义在Simulation
类之中。
进程类Process
是Simulation
类的嵌套类。激活语句activate
,只有处在Simulation
类所包含的类的对象之中,或处在其前缀部份是这种对象的有前缀块之中,才是有效的。激活语句的作用,被定义为得到调用激活过程ACTIVAT
的那种效果。
集合类Simset
是Simulation
类的前缀类,其中定义了三个嵌套类:表示集合整体的Head
类,表示集合元素的Link
类,和二者的前缀类链表类Linkage
。首位过程first
,定义在Head
类中;退出过程out
和在等待过程wait
中用到的进入过程into
,定义在Link
类中。
在试衣间类FittingRoom
中,为了让人们排队等待访问试衣间,使用了门对象door
,它是队列类即Head
类的一个对象。试衣间类定义了两个过程:要求过程request
:如果试衣间中正有人使用,则他必须等待于门队列之中,即wait(door)
;当他可以使用试衣间之时,相应的从门队列中移除自己,即door.first.out
。离开过程leave
:如果门队列中有人等待的话,放行其中第一个人,即activate door.first
。
个人类Person
是Process
类的子类,它的行动是浏览商店和在试衣间试衣,使用保持过程hold
来描述其所用时间,并调用对应的试衣间对象的过程来要求和离开试衣间。正态分布的随机抽签过程normal
,定义在环境类ENVIRONMENT
中,它的最后的参数,必须是指定一个伪随机数串流的一个整数。
主程序是进程类Process
的子类MAIN_PROGRAM
类的对象。它建立FittingRoom
类的实例fittingRoom1
对象,接着建立并激活Person
类的三个对象,从而将他们三人放置入事件队列之中,主程序在终止前保持100分钟的模拟时间。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.