第三篇计算任务的剖分与组合从前,有两个表匠,一个叫霍拉,一个叫坦普斯。两人都很受顾客的欢迎,他们各自的工场中的电话总是响个不停,因为老有新主顾上门。不过,霍拉发了大财,坦普斯却越来越穷,最后连店铺都给亏没了。这是为什么呢?他们做的表都是由1000个零件组成。坦普斯做表的方式是连续地把一只表从头做到尾,如果尚未装完一只表就不得不中途放下(比如说要去接客户电话了),那么装了一半的表就会立刻散掉,又得从头装起。顾客们越喜欢他的表,他的电话就越多,也就越难得到足够的不被打断的时间来装成一只表。霍拉做的表并不比坦普斯的简单。但他经过设计,用十个零件装成一个组件。十个组件又可装成更大的组件。十个大组件构成的系统就是整只表。因此,当霍拉不得不放下装了一部分的表去接电话时,他只损失了一小部分活儿,他装表所用工时只是坦普斯所用工时的一个零头。…在这个寓言里面,我的中心思想是,具有复杂性的系统最好是采取层级结构的形式,而层级结构有一些与系统的具体内容无关的共同性质。我将论证说,层级结构是构造复杂事物的建筑师们所使用的主要结构方式之一。----H.Simon[1]《TheSciencesoftheArtificial》一个计算任务可以很简单,使用几个语句进行描述,执行之后就可以得到结果,但是如果说我们人类使用计算机只是进行那种简单的计算,显然是浪费。而复杂的计算任务,则和任何复杂事物一样,具有非常庞大的内部结构,人类解决复杂问题的一般思路,就是把一个大的结构分解为相对比较小的结构,如果可能就一直分解到非常简单的结构,分别解决了那些简单结构的问题,按照我们分解大问题的逻辑,也就解决了开始的复杂问题。如何运用程序语言来表达这个解构的思路,就是本篇我们要讨论的主要论题。首先我们讨论一个FORTRAN程序所可以具有的结构,按照这个语言对程序结构模本的规定,我们就可以规划相应的针对计算任务的问题解构方式。所谓FORTRAN程序的结构,就是一个FORTRAN程序可以包含那些程序单位,然后这些单位又必须如何组装在一起。所以我们分类讨论了FORTRAN的程序单位之后,就需要讨论数据流与指令流是如何进行不同程序单位之间的通讯的,通过这种通讯,一个由许多程序单位组成的FORTRAN程序就构成了一个有机的整体,恢复了被支解的问题的本来结构。特别的,我们还需要讨论最为重要的程序单位,就是过程,它的可执行程序单位的主体。其中FORTRAN语言以标准形式给出的固有过程,相当于为解决常见计算问题而准备的常用工具,熟练使用它们可以达到事半功倍的效果。[1]HerbertA.Simon,1916-2001,20世纪所谓“认知科学革命”的核心人物,人工智能的巨擘,在计算机科学和心理学领域都作出了开创性贡献。1975年获得图灵奖,1978年以决策理论荣膺诺贝尔经济学奖,1993年美国心理学协会授予他终生杰出成就奖,1994年获选中科院外籍院士,生前多次访华。第12章程序的单位一个FORTRAN程序可以由那些单位组成,在第一篇以及第4章都已经简单涉及过,在这里我们要详尽地讨论这个问题,特别是给出每种程序单位的结构与功能。12.1程序单位FORTRAN的程序单位分为两大类:●可执行程序单位;●不可执行程序单位。其中可执行程序单位,用来执行一个完整的功能,包括:●主程序;●外部函数子程序;●外部子例行程序子程序。不可执行程序单位,用来为其他程序单位提供定义,包括:●模块程序单位;●数据块程序单位。因此上面的五种类型的程序单位构成了FORTRAN程序的基本单位,不过在后面我们会看到,数据块程序单位属于早期版本的遗留物,完全是多余的。一个完整的FORTRAN程序至少需要一个主程序,而且也只能有一个主程序。一般说来,要完成一个完整的计算任务,除了一个主程序之外,往往还需要有函数以及子例行程序作为辅助,这时,主程序的作用就还包括驱动与管理这些作为过程的外部子程序,使得它们构成一个整体,从而完整地解决相关计算问题。模块程序单位主要是提供给编程者用来组织程序元素的。一个模块程序单位包含了如下几个方面的内容:●数据声明;●派生类型定义;●过程界面信息;●供其他程序单位使用的子程序定义。因此这样一个程序单位本身不是可执行程序单位。数据块程序单位用于给出命名公用块里面的变量的初始值,因此同样不是可执行程序单位。由于FORTRAN的现代版本引入了模块结构,而模块能够提供全局的数据初始化,因此数据块程序单位几乎可以说是多余的。由于在第13章我们将专门讨论过程以及过程的应用,因此属于过程的外部函数子程序和外部子例行程序子程序,在本章都只是简略说明,需要详细讨论的是主程序和模块。各种程序单位里面并不是能够使用任何FORTRAN语句,语句类型与程序单位之间的兼容性在下面的表中予以说明。表12-1语句与程序单元的兼容性语句主程序模块说明部分数据块外部子程序模块子程序内部子程序界面体USE语句可可可可可可可ENTRY语句否否否可可否否FORMAT语句可否否可可可否几种声明语句*可可可可可可可DATA语句可可可可可可否派生类型定义可可可可可可可界面块可可否可可可可语句函数#可否否可可可否CONTAINS可可否可可否否可执行语句可否否可可可否注意:*几种声明语句包括:PARAMETER语句,IMPLICIT语句,类型声明语句以及说明语句。#语句函数语句属于过时语言成分。一个FORTRAN程序总是从主程序的第一个可执行语句开始运行,在第三章我们已经讨论过一个完整FORTRAN程序的结构。在下面的图12-1中,我们再给出一个完整FORTRAN程序的示意图,它包含了一个主程序,一个模块,以及两个子例行程序。图12-1钟表制作程序的结构在上面的例子里面,我们假设在装配一块钟表时,零件装配与工具配套分别由两个徒弟完成,那么等负责工具配套的徒弟把零件都放置在适当的工具旁边的时候,就可以让钟表匠开始总的钟表装配工作了。因此模块ACCESSORY_BOX(附件箱)里面包含了子例行程序PARTS(零件)与TOOL(工具)所需要的一切数据与过程信息。主程序调用了子例行程序TOOL,而主程序本身不需要模块ACCESSORY_BOX里面的信息。12.2主程序主程序说明了整个FORTRAN程序的逻辑结构,同时整个程序的运行就是从主程序的第一个可执行语句开始的。不过从形式上看,一个主程序和外部子程序其实的非常类似的。一个主程序包括如下三个基本部分:●说明部分。这个部分定义了程序的数据环境。●运行部分。整个程序从这个部分的第一个可执行语句开始,该部分给出了整个程序运行的逻辑结构。●内部子程序部分。处于主程序内部的与主程序共享数据的过程。下面我们分小节说明主程序的说明部分和运行部分,由于内部子程序部分由一个或多个内部过程组成,而内部过程的讨论见12.3,因此在12.2略过。终止主程序运行的方式有如下两种:●在程序的任意位置执行STOP语句,就能即刻终止整个程序。所谓任意位置,包括组成程序的任意程序单位的任意位置。●程序的运行到达主程序的最后一个语句。12.2.1主程序的组织主程序的形式(R1101)如下:[PROGRAMprogram-name][specification-part][execution-part][internal-subprogram-part]END[PROGRAM[program-name]]下面是一个最最简单的FORTRAN程序:【例12-1】END下面是一个稍微有意思一点的最简单程序:【例12-2】PROGRAMHIPRINT*,“HELLO”END主程序的一般规则如下:●PROGRAM语句作为主程序的程序头是可选的,但是其他的程序单位都必须具有程序头。●如果程序名称出现在END语句当中的话,那么该名称必须和PROGRAM语句里面的名称一样,并且放置在关键词的后面。不能单独出现END语句当中。●主程序不提供哑元。●主程序不能在任何位置被引用,也就是说,主程序不能被直接或间接地递归运行。●主程序不能包含RETURN语句和ENTRY语句,不过主程序里面的内部过程可以包含RETURN语句。12.2.2主程序的说明部分主程序的说明部分主要就是用来描述程序的数据环境。主程序里面所能够包含的语句类型见表12-1,具体列出如下表12-2:表12-2主程序说明部分允许使用的语句ALLOCATABLEPARAMETERCOMMONPOINTERDATASAVEDIMENSIONTARGETEQUIVALENCEUSEEXTERNAL派生类型定义FORMAT界面块IMPLICIT语句函数语句INTRINSIC类型声明语句NAMELIST主程序的说明部分的一般规则如下:●OPTIONAL以及INTENT属性或语句都不能在主程序的说明部分出现,因为它们都只能应用于哑元。●可访问性说明PUBLIC以及PRIVATE都不能出现于主程序,因为它们都只能应用于模块内部。●在主程序里面,动态对象没有意义。●尽管在主程序里面可以使用SAVE属性或语句,但它们在主程序里面并没有实际的作用。12.2.3主程序的运行部分主程序的运行部分由可执行语句构成,能够出现在主程序的运行部分的语句列出如下表12-3所示:表12-3主程序运行部分允许使用的语句ALLOCATEGOTOBACKSPACEIFCALLIF结构CASE结构INQUIRECLOSENULLIFYCONTINUEOPENCYCLEPRINTDATAREADDEALLOCATEREWINDDO结构STOPENDWHEREENDFILEWHERE结构ENTRYWRITEEXIT算术IF语句FORALL赋值语句FORALL结构计算GOTO语句FORMAT指针赋值语句12.3内部过程内部过程和外部过程的主要差别就在于它们的的位置不同:内部过程必须封装在主程序或其他过程子程序内部,这就导致以下后果:●内部过程的名称是局部的而不是全局的;●内部过程只能被包含了它的定义的程序单位所引用;●内部过程能够访问它的宿主的数据对象;●内部过程可以递归,不能包含ENTRY语句,也不能作为实元传递。构造内部过程的主要原因如下:●内部过程提供了能够很方便地访问宿主环境的过程。●内部过程提供了一种具有语句函数功能的多语句形式。●便于模块设计以及具有更好的软件工程效能。内部过程之所以能够提高安全性以及灵活性,是因为其界面非常清晰。宿主的内部过程部分的形式(R210)为:CONTAINSinternal-subprogram[internal-subprogram]…其中的内部子程序由一个或多个内部过程组成,而内部过程或者是由如下形式(R1216)的函数组成:function-statement[specification-part][execution-part]ENDFUNCTION[function-name]或者是由如下形式(R1221)的子例行程序组成:subroutine-statement[specification-part][execution-part]ENDSUBROUTINE[subroutine-name]【例12-3】PROGRAMWEATHER…CONTAINSFUNCTIONSTORM(CLOUD)…ENDFUNCTIONSTORMEND其中的过程STORM就是主程序WEATHER里面的一个内部过程。内部过程的一般规则如下:●内部过程内部不能再包含内部过程,即内部过程不能嵌套。●内部过程不能包含ENTRY语句。●内部过程不能包含PUBLIC和PRIVATE属性或语句●内部过程不能作为实元传递。●内部过程的说明部分除了可以包含主程序的说明部分所许可的语句之外,还可以包含INTENT语句以及OPTIONAL语句。●内部过程的运行部分除了可以包含主程序的运行部分所许可的语句之外,还可以包含RETURN语句。●在CONTAINS语句之后,至少需要有一个内部子程序。内部过程可以被它的宿主的运行部分引用,也能够被同一个宿主里面的所有内部过程引用,包括它自身,因此内部过程可以直接或间接地使用递归的形式。内部过