第10章Fortran程序单元•一个Fortran程序中通常不是只由一个主程序组成,而是由几个按某种方式划分的不同程序单元来共同组成。尽管Fortran程序中允许只有主程序而没有子程序,但绝不允许只有子程序而没有主程序。在Fortran中,程序的执行总是从主程序开始的。•Fortran中的程序单元可以大体划分为主程序、子程序两种,其中子程序又可以进一步划分为函数子程序、子例行子程序和数据块子程序。数据块子程序通常用于实现变量的初始化赋值,函数子程序和子例行子程序在用途上基本是一致的,但是也有许多不同之处。本章将详细介绍Fortran中的程序单元和它们的基本用法。10.1主程序•顾名思义,主程序是一个实际程序中的主体,其他类型的程序单元都是以某种方式来辅助主程序的执行。在Fortran语言中,一个程序的执行始终是从主程序的第一条可执行语句开始的,所以每个完整的Fortran程序都必须有且只允许有一个主程序。主程序定义的一般语法形式如下:•[PROGRAM[程序名]]•[说明部分]•[可执行部分]•[CONTAINS•内部过程]•END[PROGRAM[程序名]]10.2语句函数•语句函数通过一句代码定义来实现某种特定的处理功能,它是Fortran77时代的遗留产物。严格来说,语句函数不属于程序单元的范畴。但是在实际应用中,语句函数以其灵活的应用、小巧的结构在程序中发挥着重要的作用。•在实际的编程过程中,程序员往往会遇到这种情况:一些简单的函数会在一个程序单元中的不同地方重复用到,而Fortran系统并不提供这种内部函数;如果采用函数子程序的形式来描述这些简单的函数又会觉得没有这种必要。例如,要求解函数的值,将其编写成函数子程序可以顺利解决。但是,Fortran语言提供了一种更为简单的手段——语句函数。10.2.1语句函数的定义•在Fortran中定义一个语句函数的形式如下:•fun([d-arg[,d-arg]...])=expr•1.Fun2.d-arg3.Expr4.语句函数示例5.需要注意的问题:在使用语句函数进行编程时,下面一些问题是需要引起注意的:•语句函数通常在函数比较简单,能够用一条语句(包括换行)就能进行定义时才使用;•语句函数是一种非执行语句,需要放置在所有可执行语句之前和相关的类型说明语句之后;•语句函数的作用范围仅限于定义它的程序单元之内,不允许跨程序单元进行语句函数的调用;•语句函数不能作为子程序调用时的实参,也不允许在EXTERNAL语句中出现;•语句函数中出现的虚参必须是变量名,不能是常量、表达式或是数组元素;•语句函数通过表达式得到的函数值的类型必须与函数名的类型一致。10.2.2语句函数的引用•语句函数在完成定义后,就可以在程序单元中进行引用了。实际上,在前一小节的例程中已经演示了语句函数引用的一般方式。本小节将对语句函数的引用方式进行具体的说明。•语句函数的引用方式与Fortran中内部函数的引用方式完全一致,就是用程序中定义的实参替换掉语句函数定义中的虚参。实参必须是与虚参类型相同的常量、变量或表达式。10.3函数子程序•函数子程序和子例行子程序是子程序的两种常用基本形式。它们的共同特征就是作为数据处理过程的集合。但是这两种子程序也不完全相同,函数子程序会返回一个函数值,且通常不会改变哑元的数值。因此,函数子程序更像是数学上的一个函数。而子例行子程序通常用于完成一项更为复杂的任务,通过哑元或者其他手段返回几个结果,哑元的数值通常会在程序的执行过程中改变。10.3.1定义函数子程序•下面给出函数子程序的一般形式为:•[prefix]FUNCTIONname([d-arg-list])[RESULT(r-name)]•...•END[FUNCTIONname]•1.prefix说明项•prefix说明项是一个可选参数,可以使用如下两种形式来书写:•type[keyword]•或•keyword[type]•2.d-arg-list•d-arg-list表示函数的哑元列表。如果函数子程序不包含哑元,则哑元列表可以省略,但是函数名后的括号不能省略。•3.RESULT关键字•RESULT关键字用于声明将函数的返回值保存在其后的变量名中,称为函数结果名。10.3.2调用函数子程序•函数子程序的调用与内在函数的调用形式一样。在主调程序的任意位置,可以通过下面的语句形式将函数子程序的计算结果赋值给变量:•V=函数名(实元表)•其中,V表示用于接收函数计算结果的变量;实元表是程序中实际传入函数子程序的变量列表,除非有特殊说明,变量列表中的实元个数以及类型必须与函数子程序定义时的虚参在个数和类型上一致。如果函数不包含哑元,则调用形式是在表达式中直接写上函数名再跟空括号即可:•V=函数名()10.3.3函数子程序示例——进制转换•下面来看一段函数子程序的实例,代码将一个4字节的整数用16进制的形式表示出来。首先给出的是程序的函数子程序单元HEX。•FUNCTIONHEX(n)•IMPLICITNONE••CHARACTER(LEN=8)::HEX•CHARACTER(LEN=1)::H(0:15)=(/'0','1','2','3','4','5','6','7',&•'8','9','A','B','C','D','E','F'/)•INTEGER::n,j,nn••HEX=''••DOj=8,1,-1•nn=n/16•HEX(j:j)=H(n-16*nn)•IF(nn==0)EXIT•n=nn•ENDDO••ENDFUNCTION10.3.4函数子程序示例——分形•在数学上有一个特殊的分支——分形(fractal),所谓分形是Mandelbrot将自然界的一些特殊复杂图形(如海岸线、树叶外形、雪花结晶类型等)进行数学理想化后提出的一种概念,其核心思想是图形的任意细小部分都与图形的整体具有自相似性,这种图形的维数不是整数,而是分数维。分形的一个典型例子就是Koch曲线,它具有雪花的外形,可以通过对一段直线反复进行某一简单的操作而得到。把这个过程用数学语言来描述,就是在复空间内定义的一种简单迭代过程,它是一个图形的缩小映射,从而产生自相似曲线。10.4子例行子程序•同函数子程序相比,子例行子程序通常用于完成更为复杂的任务。子例行子程序接受外界传入的参数并对其进行处理,子例行程序名不会用来返回处理结果。形象一点来说,函数子程序像检验机,它不改变参数的值但会告诉外界一个检测结果;而子例行子程序更像一个加工机器,外界来的参数经过它的加工会以新的形象出现。本节主要介绍子例行子程序的相关知识。10.4.1定义子例行子程序•子例行子程序同函数子程序非常相似,但是子例行子程序不会有返回值。这种形式的子程序是以SUBROURTINE语句开始,END语句结束的过程。其一般语法形式如下:•[前缀]SUBROUTINE子程序名[([哑元列表])]•……•END[SUBROUTINE[子程序名]]•1.哑元列表•2.前缀•3.子程序名•4.END语句10.4.2子例行子程序示例•下面直接来看一段例子,这段代码依次读入三个实数,并按它们的大小重新开始排序。•程序的执行结果如下:•请输入三个实数:•1.3452.718282.71827•三个实数的先后次序如下:•2.7182802.7182701.345000•调用子例行子程序时的实元必须是与哑元类型相同的变量、数组、数组元素和常数。当用CALL语句进行调用时,哑元和实元才按哑元列表中的顺序一一对应,取得同一数值。10.5子程序的多入口点和多折返点•尽管子程序中不允许直接定义其他的子程序,但是在Fortran77时代,可以通过特殊的方式在同一个子程序中定义多个不同的过程入口。通过调用不同的过程定义来实现调用同一个子程序中的不同执行段。除了提供多入口点外,Fortran77时代也提供特殊的多折返点来实现特定条件的子程序调用返回方式。10.5.1ENTRY语句与多入口点•Fortran语言中的子程序中可以通过ENTRY语句来提供多个入口点。•程序的执行效果如下:•请任意输入一个实数:•-30.0•这是一个负数•它的立方根为:-3.107233•在上面的代码中,子程序SIGN内部通过ENTRY语句为一段执行代码定义为一个入口点Negative(A)。在主调程序中,可以根据情况选择子程序SIGN中的不同执行段:直接调用SIGN将会执行入口点Negative(A)前的执行代码,并在ENTRY语句前的RETURN语句返回主调过程;如果调用Negative将执行入口点Negative(A)后的执行代码,并在下一个RETURN语句返回主调过程。10.5.2子程序的多折返点•一般来说,当子程序执行完成之后,通常会直接返回主调程序的调用处继续进行执行。关于这一点,Fortran语言中也提供了一种特殊的返回方式来改变子程序的折返点,将子程序的返回点指定到主调程序的其他位置。•程序的执行结果如下:•请输入一个正整数[负数-退出]:0•计算结果S=0.0000000E+00[=0]•请输入一个正整数[负数-退出]:2•计算结果S=0.9092974[0]•请输入一个正整数[负数-退出]:5•计算结果S=-0.9589243[0]•请输入一个正整数[负数-退出]:0•需要注意,能够实现多折返点的子程序仅限于子例行子程序,不包括函数子程序,函数子程序通过RETURN语句只能返回到主调程序中的调用点处。10.6Fortran90/95中的特殊子程序类型•在Fortran90/95标准中,除了继续对前述的一般子程序类型提供支持外,还新增了三种特殊的子程序类型。这三种子程序类型就是前述章节中曾经提到过的RECURSIVE、PURE和ELEMENTAL三种属性。RECURSIVE属性允许过程进行自身调用,也就是常说的递归调用;PURE和ELEMENTAL属性都用于数组的并行处理。10.6.1RECURSIVE属性•在Fortran90/95标准之前,Fortran中的子程序是不允许进行自身调用的。在新标准中,Fortran子程序开始允许进行自身调用,也就是经常在编程中听到的“递归”。能够进行递归调用的一个前提条件就是递归过程在被调用时,其中的局部变量会使用不同的内存地址,以便在完成递归后能够依次统计不同内存地址上的结果。•1.递归函数子程序•2.递归子例行子程序10.6.2PURE属性•在函数子程序或是子例行子程序的定义语句前添加PURE语句,将使子程序具有PURE属性。一般来说,并不需要使用这种属性,它通常适用于并行计算并在使用上有较多的限制。•具有PURE属性的子程序,其参数必须是只读的,即INTENT(IN)。•具有PURE属性的子程序,其参数都必须有赋值属性。•具有PURE属性的子程序,其中的变量不允许具有SAVE属性。•具有PURE属性的子程序,其包含的内部过程也必须具有PURE属性。•具有PURE属性的子程序,不能够使用STOP以及输入输出相关语句,如READ、WRITE等。•具有PURE属性的子程序,只能够读取而不能改变全局变量的值。10.6.3ELEMENTAL属性•ELEMENTAL属性与PURE属性非常相似,只不过它是一个针对数组的应用。在具有ELEMENTAL属性的过程中,不允许出现数组参数。该属性主要用于配合Fortran90/95中对于数组的整体操作。•程序的执行结果如下:•1.0000003.0000005.0000007.0000009.000000•0.5403023-1.7147170.63428801.994638-2.73339110.7数据块程序单元•由于COMMON语句中的变量不能够在子程序或主程序中通过DATA语句来直接设置初始值,需要在一个统一的程序单元中进行数据的初始化工作。这种统一的程序单元就是数据块程序单元。•数据块子程序单元是一种为有名公用块中的变量定义初始值的程序单元。它只允许包含变量声明和变量初始值,不可以包含可执行语句。数据块程序单元是一种落后的程