Fortran完全自学手册(红宝书)


第1章 Fortran概述 • 作为一门诞生于上个世纪50年代后半期的高级计 算机语言,Fortran在这个C/C++、Java等新兴语 言大行其道的时代仍然活跃在人们的视野之中。 Fortran语言的长项在于数值计算,在科学研究和 工程设计领域有着广泛的用途。在描述数学语言 的自然性方面,Fortran同现存的其他高级语言相 比有着明显的优势。对于科研工作者和工程技术 人员而言,Fortran语言的易学性和易用性是公认 的。 1.1 Fortran起源 • Fortran是英语中“公式(Formula)”和“翻译(Translation)” 两个单词取前几个字母的缩写形式,意即“公式翻译”。Fortran 主要面向科学研究、工程设计或企事业管理中经常遇到的能够用 数学公式表达的数值计算问题。因为可以像抄写教科书里的公式 一样书写数学表达式,它比用英文书写的自然语言更接近数学语 言。这使得Fortran在科研工作者和工程技术人员中拥有庞大的使 用人群。Fortran语言是第一个被正式推广的高级计算机语言。在 四十多年的发展历程中,它始终是数值计算领域所使用的主要语 言。本小节主要介绍Fortran的起源。 • 第一代Fortran语言是在1954年提出来的,称为Fortran I。它于 1957年在IBM 704计算机上得以实现。其开发者巴科斯的目标是开 发一种容易理解、简单易学又几乎能像汇编语言一样高效运行的 计算机语言,他和他的团队在这一点上取得了极大的成功。但是 这一计划在最初阶段并不被人们看好,这其中包括巴克斯的上司 冯·诺依曼。 1.2 Windows下Fortran编译器使用 • 由于Windows操作系统有着庞大的用户群体,因此Windows系统下 的编译器开发非常活跃。目前市场上常见的Fortran编译器包括 Visual Fortran系列、Salford的FTN系列、G95/gFortran的 Windows版本、Absoft公司的Absoft Fortran,Macrovision公司 的PGI Visual Fortran等。这其中,影响较大、功能较全、性能 较好、应用最广泛的Fortran编译器提供了集成开发环境的Visual Fortran系列。 • Visual Fortran系列编译器起源于微软公司开发的Fortran Power Station 4.0编译系统。该系统结合了当时微软最新开发的 Develop Studio集成开发环境(IDE),使得Fortran程序的开发 方式跟上了时代的潮流。但是该编译系统在实际使用中出现的问 题较多,例如没有严格的数组越界检查功能等。这套工具随后被 卖给了数字设备(DEC)公司继续开发,随后产生了一个曾经非常 流行的Fortran编译器系统。 1.2.1 安装Compaq Visual Fortran • Compaq Visual Fortran的安装过程比较简单。用 户将安装光盘放入CD-ROM后,计算机会自动运行 光盘中的安装程序。 【Select Folder】对话框安装欢迎界面 1.2.2 创建第一个Fortran工程 • Compaq Visual Fortran的图形用户界面直接使用 了微软公司的Develop Studio 6.0集成开发环 境,极大地方便了程序的编写、调试和修改。在 图形界面下,编写、调试一个Fortran程序首先需 要创建一个Developer Studio工程。 1.2.3 在Fortran工程中添加源代码 • 完成工程的基本设置之后,就可以向Fortran工程 添加用户编写的源代码了。向Fortran工程中添加 源代码可以参照下面的步骤来进行操作。 1.2.4 在Fortran工程中新建源文件 • 除了添加已有的源代码来对工程进行填充外,还 可以在工程中创建新的源代码文件。在工程中创 建新的源代码文件有两种方式可供选择。下面的 图示演示了第一种操作方法。 1.2.5 在Compaq Visual Fortran中编译源文件 • 在Compaq Visual Fortran中有两种编译模式可供 选择:一种是Debug模式(编译出来的程序姑且称 作调试版本);另一种是Release模式(编译出来 的程序姑且称作发行版本)。同一个源代码经过 这两种编译模式编译出来的程序在执行效率和文 件大小上都有很大区别: • 编译程序在Debug模式下会向生成的可执行文件写 入很多调试信息和控制语句,因此编译出来的可 执行文件“个头大”、“跑得慢”。 • 在Release模式下,编译程序只会将源代码中的有 效语句编译成可执行文件,因此编译出来的可执 行文件“个头小”、“跑得快”。 1.3 Linux下常见Fortran编译器 • 作为开源的操作系统,Linux因其较好的安全特性也拥有较 多的使用人群,特别是一些资质较深的计算机爱好者。在 Linux系统下,常见的Fortran编译器包括GNU的Fortran 77 编译器G77、GNU的Fortran 95编译器gFortran和G95、 Intel公司开发的Intel Fortran Compiler等。这里主要介 绍gFortran和G95的基本用法。 • 在Linux系统下安装Fortran编译器需要在控制台窗口中进 行。以G95编译器的安装为例,首先打开一个控制台窗口, 并进入到想要安装G95编译器的文件夹中。接下来运行如下 命令即可: • wget -O - http://ftp.g95.org/g95-x86-linux.tgz | tar xvfz - • ln -s $PWD/g95-install/bin/i686-pc-linux-gnu-g95 /usr/bin/g95 1.4 常见编译器性能比较 • 各个编译器由于实现的方式有所区别,因此实际编译出的执行程 序也有区别。这种区别主要体现在可执行程序的大小、程序的运 行速度、编译所花费的时间等方面。本节主要给出Windows操作系 统下,一些常见编译器的性能比较。程序员可以以此为参考,在 软件开发时选择合适的编译器版本。 编译器名称和版本号 厂商或组织 编译器名称 版本号 Absoft Absoft Pro Fortran 95,简称APF95 8.0 Compaq Compaq Visual Fortran,简称CVF 6.6C Salford Salford FTN77,简称FTN77 4.02 Salford Salford FTN95,简称FTN95 3.4.1605 GNU G77 3.2 20020907 Intel Intel Fortran Compiler,简称IFC 10.1 Lahey Lahey/Fujitsu LF95,简称LF95 7.2 N.A.Software N.A.Software FortranPlus,简称NAS 2.2 1.5 常用编辑器 • 通常,编译器厂商为了用户更方便使用,会随同 编译器为用户提供简单的编辑器来书写源代码。 例如,Compaq Visual Fortran借用了微软的 Develop Studio 6.0集成开发环境。Intel Visual Fortran虽然没有集成可视化开发环境, 但它可以嵌入到Develop Studio .NET集成开发环 境中。不过对于某些用户而言,他们更习惯使用 独立的编辑器来进行源代码的编辑,而在命令行 模式下对源代码进行编译调试。独立的源代码编 辑器大多也能用于普通文本的编辑。很多程序设 计语言的源代码文件同普通文本文件的差别仅仅 是文件的扩展名不同而已。本文主要介艹S玫囊 恍Fortran源代码编辑器和基本的使用方法。 1.5.1 UltraEdit • UltraEdit软件是IDM Computer Solutions公司开发的一款性能优 秀、功能强大的文本编辑器,能够用于编写多种进制格式的文本 文件和各种程序设计语言的源代码(如C、C++、JAVA、HTML等)。 该编辑器配合自定义的语言配置文件,可以实现指定程序设计语 言的语法高亮、代码折叠、自动缩进和补位、代码自动补全、列 操作模式等功能。本小节主要介绍UltraEdit文本编辑器的基本用 法。 • 在讲解UltraEdit的用法前,建议首先用保存在本书配套光盘 /Tools/IDM文件夹中的wordfile.txt文本文件替换掉UltraEdit安 装目录中的对应文件。光盘中的wordfile.txt文本文件已经由作 者添加了Fortran语言的语法定义模块,收集了尽可能全的 Fortran语句、内部函数、运算符、关键字等信息,添加了代码折 叠、缩进和补位的定义,能够实现语法关键字的高亮显示、源代 码折叠、自动缩进和补位等功能。在完成了语法配置文件的替换 之后,就可以按照下图所示的步骤学习应用UltraEdit软件进行 Fortran源代码编辑的过程。 1.5.2 ZionEdit • ZionEdit是一款定位于Windows平台下的开源源代 码编辑器。软件作者的开发目标就是打造一个方 便易用的源代码编辑环境。ZionEdit的体积小巧、 功能完善、界面友好,是不可多得的开源源代码 编辑器。目前该软件的最新版本为1.0.35版。 • 这款编辑器同样支持多种程序设计语言,能够定 制语法高亮方案。特别是列编辑模式,能够方便 的实现大段代码的整体注释。读者可以在软件作 者的主页http://www.zionedit.org中获取相关的 软件。 1.5.2 gEdit • gEdit是Linux系统下常用的一款文本编辑器。该 编辑器体积不大、功能较为完善,能够支持语法 自动高亮和自动保存。最主要的是该软件的使用 较为简单,很容易上手。对于一些对功能要求不 多、对操作的简易性较高的用户而言,是替代那 些功能完善、操作复杂的专业软件,如Emacs、 Vim等的不二之选。需要注意的是,gEdit只能运 行在Linux的图形用户界面下,在字符模式下则不 能正常运行。如果想在字符模式下进行Fortran源 代码的编辑,建议学习Emacs、Vim等不需要图形 环境的文本编辑器。 1.6 第一个Fortran程序——Hello! Fortran 90/95 • 1.2节和1.5节分别介绍了几种常用的Fortran编译器和源代码编辑器。在 对软件的介绍过程中,我们已经开始了与Fortran源代码的初步接触。在 光盘\Program\chap01\文件夹中也提供了一段最简单的源代码。本节就主 要通过对一个基本程序的介绍来使大家对Fortran程序有一个初步的认识。 • 对于了解C/C++语言的程序员肯定都非常熟悉一个著名的C/C++入门程序, 即Hello! World。该程序通过最简单的几句代码使初学者对C/C++程序的 结构有了一个基本的了解。本节将借鉴这一做法,对Fortran程序的基本 结构进行讲解。 • 用UltraEdit、ZionEdit、gEdit或是Compaq Visual Fortran编辑窗,输 入如下代码段。 • PROGRAM HELLO • IMPLICIT NONE • PRINT *, “Hello! Fortran 90/95” • END PROGRAM HELLO 第2章 Fortran语法基础 • Fortran从诞生至今,已经走过了长达50余年的发 展历程中。在这一发展历程中,相关的语言标准 先后制定了5部。具体的语法也随着标准的变更而 变化着。在每一个新标准中,前一个标准中的一 些旧语句、语法或是被废弃、或是建议减少使 用,如Fortran 90标准中就建议减少使用 “COMMON”语句;或者新增了一些新的语句,如 Fortran 95标准中的“FORALL”语句。本章将主 要介绍Fortran的基本语法。 2.1 程序书写格式 • 在1.6小节中,程序HELLO已经展示了Fortran的一 种源代码书写格式,即自由格式(Free Format),这种格式是Fortran 90/95标准中的一 大特色。同Fortran 77标准中的旧格式,即固定 格式(Fixed Format)相比,取消了在源代码书 写上的诸多限制,程序编制起来更为灵活。两种 格式书写的源代码通过文件扩展名来进行区别。 2.1.1 固定格式(Fixed Format) • 采用固定格式书写的Fortran源代码,文件扩展名 为*.F或*.FOR。在固定格式中,Fortran标准规定 了若干个列范围。每一个列范围都有自己的用途 和约定,程序必须严格按照这些约定进行编写。 2.1.2 自由格式(Free Format) • 采用自由格式书写的Fortran源代码,文件扩展名为*.F90。自由格式是Fortran 90标 准中的一大特色,废除了固定格式中的许多限制。它并不规定程序代码中每一行第几 列字符的具体作用,在源代码的编写上显得相当自由。同固定格式相比,自由格式主 要在以下几个方面进行了改进。 • 行的定义。在自由格式中每行可以书写132个字符。程序语句的位置没有了只能从第7 列后开始书写的固定约定,可以在任意一列开始书写。同一行中可以顺序书写多条程 序语句,语句之间用分号“;”进行分隔。 • 注释行的定义。在自由格式中,惊叹号“!”后的字符均被视为注释。但是,字符串 中的“!”出外,如“PRINT *, ‘Hello! Fortran 90/95’”中,“Fortran 90/95’”不会被认为是注释。 • 续行的定义。在Fortran 90/95标准中规定,当程序代码中的一行超过132个字符时, 至多可以有39个续行。续行标志固定为“&”。当一行代码的最后一个字符为“&” 时,则表示下一行与本行接续;当一行代码的第一个字符为“&”时,则表示本行与 上一行接续。 • 行号的定义。在Fortran 90/95标准中规定,行号只需写在代码行的最前面即可。 • 空格的应用。Fortran 90/95标准中规定,语句和变量名中间不能有空格。例如,关 系运算符“==”不能写成“= =”;“ABS(-2.0)”与“AB□S(-□2.0)”作用不再相 同,“AB□S(-□2.0)”不能正常通过编译。 2.1.3 兼顾两种格式的代码写法 • 由于现存大量旧的Fortran代码(Fortran 77程序)都是采用固定 格式编写的,在一些编译器上可能会由于格式的问题而出现编译 错误。因此,有必要提供一种能够兼顾两种格式的代码写法,以 便于将固定格式的代码转换为符合自由格式要求的代码。能够兼 顾两种格式的代码在书写时需要注意的要点有以下几条: • 开始列。除注释行外的每一行代码只能从第7列开始编写。这主要 是由于自由格式中并没有规定代码编写的起始列,因此可以套用 固定格式的规定。 • 注释行。标明注释行的符号只能用感叹号“!”,并且应该写在第 一列。注释行必须单独占一行,不能出现其他语句的行尾。 • 续行方式。续行标志只能使用符号“&”。在接续的两行中,前一 行应该在72列后、132列前添加续行标志;后一行则应该在第6列 书写续行标志。 • 空格使用。在Fortran 90/95标准中,语句和变量中间不允许出现 空格。因此,兼顾两种格式的源代码中也不允许在语句和变量中 出现空格。 2.2 字符集 • “字符集”是指能够用于编写Fortran程序的所有字符及符号。不 是所有的外文字母或数字符号都能被某一高级计算机语言接受, 每一种高级计算机语言都会规定它所能使用的字符。Fortran中允 许使用的基本字符主要分为两大类:文字字符和特殊字符。文字 字符主要包括26个英文字母、10个自然数字和下划线。如表所示。 2.3 空格的使用 • 在Fortran 90/95标准中,空格不能出现在语句和变量名中间;除 了在字符串常量或是输入输出中外,不代表任何具体意义。其主 要作用是分隔语句和变量,使代码更为易读。在国外的一些科学 研究和工程设计机构,如美国航空航天局(NASA)等,在开发科 学计算程序时对空格的使用有一些很好的规定。 • 在程序中要确保不在Fortran定义的语句或变量名中出现空格,不 要尝试通过空格来对变量名进行区别; • 在编写公式时,尽量通过空格来使公式的书写和阅读更为清晰易 懂; • 在编写大段代码时,可以使用整行的空格来对代码段进行功能划 分。 • 建议在编写程序时,尽量遵守这些规定。下面来看一段使用空格 来确分四则运算的实例,先给出不使用空格进行区分的范例: • Y=X+A*B/C-(0.5+C*D)**2 2.4 注释格式 • 尽管注释在编译过程中是一定会被编译器忽略的,但却是 源代码文件中十分重要的一部分。因为注释是保证源代码 具有可读性的一个非常重要的手段。在不同的高级计算机 语言中,表示注释的方法各有不同,甚至在一种语言里, 也可能有几种引入注释的方法。不同的注释方法各有优缺 点,在使用中应该根据实际情况进行选择。 • 在C语言程序中,需要使用“/*”和“*/”这样的特殊标 记来界定注释的内容而不管行边界。采用这种方式进行注 释时经常出现的错误就是漏掉结束的界定符,使得后面的 语句也变成了注解;好处也很明显,就是能够方便的实现 代码的块注释。 • 而Fortran 95语言中的“!”,Ada语言中的“-”,或是 C++语言中的“//”,注释都是在行的任意位置开始直到行 的末尾结束。这种做法更为合理、不容易出错,但要实现 大段代码的整体注释就不是特别方便了。 2.5 命名方式 • 在Fortran中为对象命名有一定的要求。简单的说, Fortran 77的命名要求和Fortran 90/95的命名要求是不同 的。本节主要介绍不同标准下的命名方式。 • 在Fortran 77标准中,程序和变量等对象的名字是由不多 于6个字符(包括英文字母A-Z、a-z和数字0-9)组成的字 符段。同时,对象名字的第1个字符必须是字母,名字的有 效使用范围原则上仅限于一个程序单元之中(内部过程除 外)。下面的代码段演示了Fortran 77标准的对象命名方 式。 • INTEGER 0B !错误 • INTEGER C-10 !错误 • REAL sin !正确 • REAL test:real !错误 2.6 程序单元和语句顺序 • 程序单元是程序中的基本组成部分,了解程序单 元的用途可以加深对计算机程序的理解。本节主 要介绍Fortran程序中的程序单元和句顺序的基本 概念及相关规定。Fortran中的程序单元分为两大 类:可执行程序单元和不可执行程序单元。 • 可执行程序单元,在程序中主要用来执行一个完 整的功能。可执行程序单元主要包括以下三种: 主程序单元、外部函数子程序单元和外部子例行 子程序单元。 • 不可执行程序单位,在程序中主要用来为其他程 序单元提供定义。不可执行程序单元有两种:模 块程序单元和数据块程序单元。 第3章 简单输入与输出 • 计算机程序的主要作用是对输入的数据进行适当 的加工处理以得到人们关心的结果,并将结果输 出。在进一步讲解程序设计的基本概念前,有必 要介绍简单的输入输出语句。这样,程序设计中 的一些问题可以直观的表现出来,便于对一些概 念的理解。 3.1 输入和输出概述 • 在Fortran语言中,数据的输入输出操作由相应的 输入输出语句来完成。输入输出语句负责向计算 机说明程序中哪些数据要进行输入输出、输入输 出数据的格式、在何种设备上进行输入输出等问 题。 • Fortran中的输入输出方式主要有以下三种: • 按用户指定的格式进行输入输出; • 按系统隐含的标准格式输入输出(也称为表控输 入输出、直接列表输入输出); • 无格式的输入输出。 3.2 简单输出语句 • 简单输出语句主要是指表控输出,即表控格式 (List Directed Format,也叫直接列表)输出。 在用这种格式进行输出时,计算机系统会用隐含 的输出格式为每一种数据类型规定输出的长度和 表示形式。例如,实数是用小数还是指数来表 达,小数位数有几位等。 • 在Fortran中没有专用的表控输出语句,表控输出 格式的输出都是输出语句的简化形式。具体来 说,PRINT语句和WRITE语句都能够进行表控输出。 下面分别对两种语句的表控输出用法进行说明。 3.2.1 PRINT语句的表控输出 • PRINT语句是Fortran中较为常用的一种输出语 句,用它来完成表控输出的语法格式如下所示。 • PRINT *[, 输出项,……] • 在PRINT语句中不能指定输出设备,只能向系统隐 含指定的设备输出数据(隐含指定的输出设备一 般是指计算机屏幕);语句中的星号“*”表示 “表控格式输出”,即按照计算机默认的格式进 行数据的输出;输出项可以有多个,每个输出项 之间用逗号“,”分隔;当语句中没有输出项时, 命令将输出一个空白行。 3.2.2 WRITE语句的表控输出 • 使用WRITE语句来完成表控输出的语法格式如下所 示。 • WRITE(*,*)[输出项,……] • WRITE语句中第一个星号“*”表示在系统隐含指 定的设备上输出;WRITE语句中的第二个星号“*” 表示“表控格式输出”;输出项可以有多个,每 个输出项之间用逗号“,”分隔;当语句中没有输 出项时,命令将输出一个空白行。 3.2.3 表控输出的特点 • 从上面两个例子中可以发现表控输出的一些特点。如果输 出的是整数,系统会自动为每个整数分配11列,当要输出 的数据位数不够时,左端以空格补齐。在输出实数时,给 每个实数分配的输出长度分为以下两种情况。 • 当实数能够用小数形式表达时,分配8列,小数点前最多7 位。如果小数点后有效位数不足,则用“0”补齐。 • 如果实数只能用指数形式表达时,则分配13列。其中,指 数部分占4位,小数点后占7位。 • 在输出数组时,数组元素会按照它在数组中存储的先后顺 序依次输出。在输出字符串时,直接按照字符串的实际长 度进行输出。 • 用户也可以在同一条PRINT语句或WRITE语句中输出整数、 实数和字符串。示例如下。 • PRINT *, 1000, 1.414, “Two Numbers” 3.3 简单输入语句 • 简单输入语句主要是指表控输入格式,又称为自 由格式输入。这种输入操作不需要用户指定输入 数据的具体格式,只要将数据按其合法的形式依 次输入就可以了。数据之间以逗号“,”或空格间 隔。Fortran中的表控输入语句也是特定输入语句 的简化形式。READ语句能够实现表控输入,其实 现形式有两种。下面分别给出两种形式的表控输 入语句。 3.3.1 READ语句的表控输入形式一 • 使用READ语句来完成表控输入的语法形式如下所 示。 • READ *[, 输入项,……] • 在这种形式中,不能指定输入设备,只能由系统 隐含指定的设备上输入(系统隐含指定的设备一 般是指键盘);语句中的星号“*”表示“表控输 入”,即按照数据合法的格式进行输入;输入项 可以有多个,每个输出项之间用逗号“,”分隔; 当语句中没有输入项时,系统将挂起程序的运行 直到用户键入回车符。 3.3.2 READ语句的表控输入形式二 • 第二种形式的READ语句表控输入的语法如下。 • READ(*, *)[输入项,……] • 语句中的第一个星号“*”表示“系统默认的输入设备”(一般指键盘);第二个星号“*”表示 “表控输入”;输入项可以有多个,每个输出项之间用逗号“,”分隔;当语句中没有输入项时, 系统将挂起程序的运行直到用户键入回车符。 • 下面用一个具体的代码来演示表控输入语句的应用。这段代码主要演示了第二种形式的READ语句表 控输入在程序的简单实现。 • TEST0305.F90 • ! 这是直接列表输入语句READ的范例 • PROGRAM TEST0305 • IMPLICIT NONE • ! 变量定义 • REAL :: A , B • INTEGER :: I, J • ! 表控输入 • READ(*, *)A, B • READ(*, *)I, J • ! 表控输出 • PRINT *, "Output:" • PRINT *, A, B • PRINT *, I, J • • END PROGRAM TEST0305 3.3.3 表控输入的特点 • 在应用READ语句进行表控输入操作时,输入的数 据应该符合READ语句中的要求,即输入的数据个 数应该与READ语句中的变量个数一致;输入数据 的类型应与READ语句中相应变量的类型一致。如 果输入的数据不够,READ语句将会等待用户继续 输入,直到输入所需的全部数据为止。如果输入 的数据过多,则多余的数据不起作用。如果输入 的数据与READ语句中的变量类型不一致,则可能 出现赋值错误。 第4章 常量和变量 • 常量和变量都是程序中预留的用于保存数据的内 存空间。常量的值在程序运行过程中始终不会发 生变化。而变量的值在程序的运行过程中是可以 变化的。在Fortran语言中,有五种基本的数据类 型可供使用。他们分别是整型(INTEGER)、实型 (REAL)、复型(COMPLEX)、字符型 (CHARACTER)和逻辑型(LOGICAL)。按用途, 又可以分数值型、字符型和逻辑型三种。相应的 常量和变量也可以分为这三种。本章将按照用途 介绍常量和变量的基本概念。 4.1 数值型常量及其类型 • 数值型常量的值在程序运行过程中不会发生改 变,它们通常也被称为常数。数值型常量包括整 型常量、实型常量和复型常量。本节主要介绍数 值型常量的类型和基本概念。 4.1.1 整型常量 • 整型常量也称为整型常数或整数。按照所需存储空间的大小,又 分为长整型和短整型两种。长整型占用4字节的存储空间,短整型 则占用2字节的存储空间。默认情况下为长整型。有些编译器还提 供额外的扩展整型常量,如Compaq Visual Fortran在x86及其兼 容机上额外提供占用1字节空间的整型常量;在OpenVMS、 Tru64UNIX和Linux系统中还提供占用8字节空间的整型常量。 • 由于存储整型常量的内存单元容量是一定的,因此整数所能表达 的范围是有限的。长整型能够存储的数值范围为-231到231-1,即- 2,147,483,648到2,147,483,647之间;短整型能够存储的 数值范围为-215到215-1,即-32,768到32,767之间。整型常量可 以包含正负号,也可以不包含(此时默认为正,即+2与2等价)。 在Fortran77标准中,常量内部的空格不起任何作用,如“123” 与“1□2□3”等价;但Fortran90/95标准中则不允许出现这种表 达方式。 4.1.2 实型常量 • 实型常量也称为实型常数或实数。按照所需存储 空间的大小,实数分为单精度和双精度两种。在 实数的表达方式上,可以分为小数形式和指数形 式两种。下面分别进行说明。 • 1.实数的精度:单精度实数占用4字节的存储空 间,有效位数6到7位;双精度实数占用8字节的存 储空间,有效位数15到16位。 • 2.实数的表达形式:当以小数形式来表达实数 时,小数点的前面或后面可以不出现数字,但不 允许小数点前后都不出现数字。例如,+10.4、- 0.8、34.、.01等都是合法的。 4.1.3 复型常量 • 复型常量也称为复型常数或复数。按所需存储空间的大小 分为单精度和双精度两种。复型常数是Fortran语言中特有 的一种数据结构,能够同时存储复数的实部和虚部。在程 序中,复型常数用一个括弧中的两个实数来表示。第一个 实数表示复数的实部,第二个实数表示复数的虚部。比如: (3.0,6.3)和(1.0E2,2.0E3)。在存储的时候,复型常数会 占据两个实数的存储单元。因此,单精度的复型常数占用8 字节的存储空间;双精度的复型常数则需要占用16字节的 存储空间。Compaq Visual Fortran在OpenVMS、Tru64UNIX 和Linux系统中还提供占用32字节存储空间的复型常量。 • 复型常量中实部和虚部所表达的数的范围与同精度的实数 是一致的。即单精度时的数值范围为1.17549435E-38到 3.40282347E38;双精度时的数值范围为 2.2250738585072013D-308到1.7976931348623158D308。 4.1.4 常量的存储形式 • 计算机在存储上述三种不同类型的常数时,并不区分数据 的具体形式,而是统一将数据转化为二进制的形式进行存 储。在转化的过程中,由于存储空间是一定的,因此存储 实型常数时会产生存储误差的问题,即计算机的截断问题。 在Compaq Visual Fortran安装目录的\DF98\BIN文件夹中 有一个BITVIEWER程序可以查看各种数据在计算机中存储的 二进制数据形式。通过它可以直观的了解计算机中数据的 存储方式。软件的图形用户界面如图4.1所示,图中最下面 一排的方框表示数据在计算机中的存储空间;每一个方框 只有两个数值,即0和1;右侧有多个选项可供选择,用于 表示数据所占用的存储空间。如4表示4字节、2表示2字节 等。 4.1.5 符号常量 • 在程序中有时会经常用到同一个常数。如一些数 学常数、物理常数等。如果每次都在程序中重复 书写使用这些常数值,会显得很繁琐。Fortran允 许用一个名字来对常量进行命名。比如常见的用 PI来代表圆周率3.1415926535。此时称PI为“符 号常量”或“符号常数”,即用符号来表示常量。 在Fortran中,可以使用PARAMETER语句将一个符 号声明为常量。比如将PI声明为常数 3.1415926535,可以用如下的语句实现: • PARAMETER (PI = 3.1415926535) 4.2 数值型变量及其类型 • 数值型变量主要是指整型、实型、复型三种变量。 系统会为程序中的每一个变量按照其类型开辟一 块存储单元,用于存储变量的值。每一个变量都 需要用一个名字(变量名)来识别,并且同一个 程序单元内不能用同一个变量名来命名不同的变 量。本节主要介绍数值型变量的类型和基本概念。 4.2.1 数值型变量概述 • 数值型变量的分类同数值型常量一样。整型变量用来存储 整型常量,实型变量用来存储实型常量,复型变量用来存 储复型常量。在存储空间的分配上,同类型的变量与常量 占据相同的存储空间。 • 变量名的作用是识别程序中的变量。Fortran中的变量名命 名有一定的规则,主要有以下几条。 • 变量名中只能包含26个英文字母和0~9十个数字;在 Fortran90/95标准中,还允许使用下划线“_”。 • 变量名的第一个字符必须是字母,不能是下划线“_”或数 字。 • 在Fortran77标准中,变量名不允许超过6个字符;在 Fortran90/95标准中,变量名不允许超过31个字符。 4.2.2 使用系统默认的隐含约定 • Fortran中约定:在没有强制规定变量类型的情况下,如果变量名的首字 母为I、J、K、L、M、N六个字母中的一个时,即认为该变量为整型变量, 而以其他字母开头的变量则默认为实型变量。这就是所谓的“I-N规则”。 “I-N规则”的使用有利有弊。好处就是不管在程序的什么位置,如果想 要临时添加一个变量,只要按照“I-N规则”的约定取好变量名就可以使 用了;缺点也是明显的,就是众多随意添加的变量使得程序阅读起来不是 很方便,更有可能造成人为错误。比如下面的代码段就是一个典型的错误 范例。 • TEST0401.F90 • ! 这是一个错误的变量定义范例 • PROGRAM TEST0401 • I = 3000 * 3 • WRITE(*, *)l • END PROGRAM TEST0401 4.2.3 使用类型说明语句声明 • 如果不想受“I-N规则”的约束,或是防止出现“I-N”规 则下的常见错误,可以使用类型说明语句对变量的类型进 行约定。 • 在Fortran中,基本的类型说明语句主要有六种。即 INTEGER语句、REAL语句、DOUBL PRECISION语句、COMPLEX 语句、LOGICAL语句和CHARACTER语句。数值型变量主要涉 及到前四种语句。即INTEGER语句(整型说明语句)、REAL 语句(实型说明语句)、DOUBL PRECISION语句(双精度说 明语句)和COMPLEX语句(复型说明语句)。类型声明语句 的语法形式如下: • 类型说明语句[([KIND=]数字)] [::] 变量名[, 变量 名……] 4.2.4 用隐含说明语句声明 • 除了上面提到的两种变量类型说明方法外,还可 以用隐含说明语句(IMPLICIT语句)将以某一字 母开头的所有变量都声明为同一种类型。该语句 的语法形式如下: • IMPLICIT 类型说明语句 (字母列表) • 其中字母列表中的字母用逗号进行间隔。如果是 连续的一串字母,可以用首尾字母间加符号“-” 的方式来进行缩写。比如“C-H”。下面的声明代 码将以字母A-G和N开头的变量都声明为整型: • IMPLICIT INTEGER :: (A-G, N) 4.2.5 数值型变量声明及其应用 • 在编写程序的过程中,显式的声明所有用到的变 量是一种良好的编程习惯,建议在编写实际的应 用程序时使用“IMPLICIT NONE”来进行强制的变 量类型检查。凡是没有进行类型说明的变量,在 编译过程中都会被编译器找出来。 • 在三种类型规定方法中,以类型说明语句的优先 级最高,IMPLICIT语句次之,“I-N规则”的级别 最低;类型说明语句和IMPLICIT语句都是不可执 行语句,必须出现在本程序单元中所有可执行语 句之前;类型说明语句只能在本程序单元内有 效;DOUBLE PRECISION语句在Fortran90/95标准 完全可以用REAL(8)语句代替,目前已很少使用。 4.3 字符型常量及变量 • 文字处理能力是Fortran语言的另一特色。早期 Fortran77中的文字处理能力较弱,在 Fortran90/95标准中的文字处理能力已经大大加 强。本节将介绍字符型常量和变量的基本概念。 4.3.1 字符型变量概述 • 字符型常量也称为字符串,它是用引号(单引号或双引 号,Fortran77中只允许用单引号)括起来的若干个字符。 字符串中的字符可以是计算机系统中允许使用的任意字 符,通常会大于Fortran字符集。比如字符型常量 ‘BEIJING’,”PLANE”,”#$@%&”,”北京”都是合法 的。 • 在计算机中,一个字符通常会占据一个字节的存储空间; 但某些语言的字符在计算中需要多个字节的空间来存储。 比如,一个汉字在计算机上会占据4字节的存储空间。字符 型变量是用来存储字符型常量的变量,在程序中必须事先 定义字符型变量才能使用。定义字符型常量可以使用 CHARACTER语句,有四种定义字符型变量的语法形式,这些 语法定义会在随后的小节进行介绍。 4.3.2 定义形式一 • 声明字符型变量的第一种语法形式如下。 • CHARACTER [::] 变量名 • 在这种形式的定义中,类型声明语句CHARACTER说 明其后变量名所代表的变量为字符型变量。 Fortran77和Fortran90/95中都可以采用这种形式 进行声明。但需要注意,在Fortran77下不能出现 分隔符“:”。采用这种形式进行声明时,允许对 变量的长度进行说明,说明的方式有两种: • 变量名*len •或 • 变量名(len) 4.3.3 定义形式二 • 第二种声明字符型变量的语法形式如下。 • CHARACTER([KIND=]n) [::] 变量名 • 在这种形式的声明中,关键字KIND用于指出赋值 号“=”后所跟的数字n为变量的种别参数(有关 种别参数的内容会在后面的小节进行讲解)。这 个数字实际上就是说明声明语句中变量名所代表 的变量的长度的,意义同定义形式一中的len。采 用这种形式也可以定义字符型数组。在实际的编 程实践中,也可以采用如下替代形式: • CHARACTER([LEN=]n) [::] 变量名 4.3.4 定义形式三 • 第三种形式的字符型变量声明的语法形式如下。 • CHARACTER*len [::] 变量名 • 在这种形式的声明中,类型声明语句后的星号“*” 用于指明其后所跟的数字表示变量的长度,意义 和前面两种定义形式中的差不多。这种形式的定 义也可用于声明字符型数组,形式如下: • CHARACTER*len [::] 变量名(dim) • 其中的dim用于说明字符数组中的纬度的长,即长 度。下面给出几个实例。 • CHARACTER*20 :: String • CHARACTER*10 :: Array_string(20) 4.3.5 定义形式四 • 在这种形式中,通过IMPLICIT语句来强迫使以某些字母开 头的变量被默认处理成字符型。语法形式如下: • IMPLICIT CHARACTER[*数字] 字母表 • IMPLICIT CHARACTER[(数字)] 字母表 • Fortran77标准中通常采用前一种形式,Fortran90/95标准 中通常采用后一种形式。定义中的数字表示字符型变量的 长度,即能够存储多少个字符。当数字紧跟CHARACTER语句 之后时,表示统一指定字符长度;当数字紧跟变量名之后 时,表示单独指定字符的长度。如果统一指定的字符长度 与变量个别指定的长度不一致时,以个别指定优先于统一 指定。定义中括弧内的部分可有可无。比如: • IMPLICIT CHARACTER(5) (G-N), CHARACTER X 4.3.6 字符型变量声明及其应用 • 实际上,只要字符变量的长度不为1,则可以将其 看作数组来进行理解。正因为如此,在对字符型 变量进行操作时,允许对字符型变量中的某一个 或某几个存储位置上的数据进行单独操作。这在 接下来的子字符串的相关内容中进行讲解。用 PARAMETER语句配合CHARACTER语句还可以定义有 名字符型常量,比如: • CHARACTER(9) :: Name • PARAMETER (Name = ‘Peter Pan’) •或 • CHARACTER(9), PARAMETER :: Name = ‘Peter Pan’ 4.3.7 子字符串的概念 • 在字符型数据的应用上,还有一个子字符串 (Substring)的概念需要了解。所谓子字符串是 指字符串的一部分。比如有一字符串为 “Beijing”,则“Bei”、“jing”、“ei”等 都是该字符串的子字符串。可以用下面的形式来 引用子字符串: • 字符串变量名([star]:[end]) • 其中,star和end都是整型表达式。他们指定了要 引用的子字符串在字符串中的起始和终止位置。 4.4 逻辑型常量及变量 • 逻辑量通常用于程序中的流程控制。在Fortran中,逻辑量 主要有三种:逻辑常量、逻辑变量和关系表达式。本小节 主要介绍Fortran中逻辑常量和逻辑变量的基本概念和应用。 Fortran中的逻辑常量只有两个: • TRUE,表示“真”,即满足逻辑条件; • FALSE,表示“假”,即不满足逻辑条件。 • 例如,当A=0时,此时“A<1”的值就为TRUE(真),而 “A<-1”的值就为FALSE(假)。逻辑常量又称为逻辑常 数,在程序中,它们通常被赋给一个逻辑变量。 • 逻辑变量被用来存放逻辑常量,它的值只能是TRUE或FALSE 中的一个。在程序中使用逻辑变量必须事先加以定义说明。 在Fortran中,逻辑变量的定义是通过LOGICAL语句来实现 的。该语句的一般形式为: • LOGICAL[([KIND=]数字)] [::]变量名[,变量名] 4.5 变量的初始化 • 变量在定义之后,通常会由系统按照自身设置为 变量赋给一个初值。这一过程称为变量的初始化。 但是有些系统并不提供这种功能。如果贸然使用 没有经过初始化的变量,很可能导致程序运行出 错。 • 在Fortran中,允许在声明变量的同时设置变量的 初置。在Fortran90/95中要设置变量的初置,可 以将想要设置的初值直接写在声明的变量之后。 使用这种方法进行初值的设置时,声明中的两个 冒号不能省略。 4.6 赋值语句 • 变量在声明后就可以使用了,在使用过程中,变 量需要保存不同的数据。将数据的值写进变量的 过程就是赋值的过程。在Fortran语言中,除了一 些专用的特殊语句外,最常用的赋值手段就是赋 值语句。赋值语句就是等号“=”,在Fortran中 有着广泛的用途。本节就将介绍赋值的概念和赋 值语句的作用。 4.6.1 何为赋值 • 所谓赋值,就是将一个常量的值传输给一个相应的变量。在Fortran中, 赋值除了通过DATA语句之外,还可以使用赋值语句来实现,并且赋值语句 具有比DATA语句更广泛的用途。赋值语句的作用就是将一个确定的值赋给 一个变量,语句的一般格式为: •V=P • 其中,表达式中的等号“=”就是赋值符;V代表一个变量名,P代表一个 表达式。 • Fortran的赋值语句有三种类型:算术赋值语句、逻辑赋值语句和字符赋 值语句。算术赋值语句的作用是将一个算术量赋予一个算术型变量;逻辑 赋值语句的作用是将一个逻辑量赋给一个逻辑性变量;字符赋值语句的作 用就是将一个字符型数据赋给一个字符型变量。尽管用途不一样,但是三 种赋值语句采用的都是同样的运算符号,即赋值符“=”。 • 赋值语句中的“=”号是赋值符,而不是等号。赋值符的作用是将赋值符 右边表达式的值传递给赋值符左边的变量。例如,赋值语句“X=3.0”的 作用是将数值3.0传递到变量X中。因此,在阅读程序代码时对赋值符的理 解应是带方向的,即将什么数据赋值给什么变量。 4.6.2 赋值过程的类型转换 • 在一个算术赋值语句V=e中,赋值变量(V)和表达式(e)的类型既可以 相同,也可以不相同。Fortran中对于赋值时的类型转换问题作了如下规 定。 • 如果变量V与表达式e的类型相同,则直接进行赋值,不需进行类型转换。 • 如果变量V与表达式e的类型不同,则首先进行表达式的求值,然后求值的 结果转换为赋值变量的类型。 • 在不同的类型之间进行转换是有限制条件的:数值型以及逻辑型数据之间 允许相互赋值转换;数值型数据与字符型数据之间则不允许进行直接赋值 转换,需要使用专用的字符函数;逻辑型数据不允许与字符型数据之间进 行直接或间接的转换。 • 整型数据与实型数据的相互转换遵循如下规则:整形向实型转换时,保持 数值的大小不变;实型向整型转换时,直接将实型的小数部分截去。 • 在由整型数据向逻辑型数据转换时,逻辑型数据的值由整型数据的奇偶来 决定:当整型数据为奇数时,转换成的逻辑型数据的值为TRUE;当整型数 据为偶数(包括0)时,转换成的逻辑型数据的值为FALSE。 • 在由实型数据向逻辑型数据转换时,实型数据会首先转换成整型,然后再 由整型向逻辑型转换。 第5章 种别和属性 • 在前面的章节中,谈到过数值型常量的存储空间 问题。不同精度的数值所需的存储空间是不同 的,相应的变量也应该与常量的存储空间一致。 如果变量的存储空间不够,则会造成常量在存储 时的精度丢失。在Fortran中,可以通过种别参数 来指定变量的储存空间,从而达到优化使用存储 空间、防止精度丢失的目的。本节主要介绍种别 和属性的基本概念。 5.1 种别概述 • 种别是一个全新的程序设计概念。通过它,程序 员可以更灵活的控制程序所占用的存储空间。本 节将对种别的一些基本概念进行介绍。 5.1.1 种别的概念 • 种别是Fortran 90/95标准中才开始引入的一种表示数据大 小和精度的概念。引入种别的概念后,Fortran中的数据, 不仅仅有一个类型所属,并且同一类型下还可进一步分出 若干个种别。种别数值的大小确定了该类型数据实际的大 小范围和存储精度。这就好比动物学中的分类,在猫科动 物这一大的类型之下,还有老虎、猎豹、家猫等的进一步 划分,它们的个头会有很大差异。 • 有了种别说明之后,计算机程序会更容易在不同硬件和软 件平台间进行移植。由于目前的计算机系统并不统一,多 种体系平台的现象依然客观存在。在不同的平台上,同种 类型的变量可能规定了不同的精度范围。因此,在某种计 算机上正确运行的程序在另一种计算机上运行时就有可能 出现溢出等现象。有了数据的种别属性之后,就可以有效 避免这种情况的发生。 5.1.2 种别的使用 • 在变量声明中,种别由种别说明符进行说明。带种别说明 符的类型说明语句的基本语法形式如下: • 类型说明([KIND=]种别值) [::] 变量名列表 • 种别说明符写在类型说明语句后的括号内,由关键字 KIND,赋值号“=”以及种别值组成。其中,关键字KIND和 赋值号“=”可以省略。例如程序中要说明一个实型变量 A,种别值为4;一个字符型变量C,种别值为10;一个整型 变量K,种别值为8。那么可以用下面的代码来进行声明: • REAL(KIND=4) :: A • CHARACTER(KIND=10) :: C • INTEGER(KIND=8) :: K 5.2 种别值和种别函数 • 前面已经提到过了,由于Fortran国际标准中只是 提出了数据的种别概念和定义,但并没有规定统 一的种别分类和相关的数值定义。这一现象是由 于当前计算机体系并不统一的现实造成的。因 此,具体的种别值是由各个编译器厂商自行确定 的。本节主要介绍Compaq Visual Fortran编译系 统中提供的几类种别值。 5.2.1 整型数据的种别 • 整型数据在Compaq Visual Fortran中被划分为4 种种别,种别值即表示整型数据所占内存空间的 字节数n。表所示是Compaq Visual Fortran中定 义的几种整型数据种别。 整型数据的种别 种别值n 取值范围 (-28n-1—28n-1-1) 备注 INTEGER([KIND=]1) 或 INTEGER*1 -128 到127 INTEGER([KIND=]2) 或 INTEGER*2 -32768 到32767 INTEGER([KIND=]4) 或 INTEGER*4 -2147483648 到2147483647 缺省值 INTEGER([KIND=]8) 或 INTEGER*8 -9223372036854775808到 9223372036854775807 仅适用于Alpha芯片机型 5.2.2 实型数据的种别 • 实型数据在Compaq Visual Fortran中被划分为3 种种别。在Fortran 90/95标准中并没有明确规定 实型数据中指数的允许范围和有效位数,表中的 数据仅适用于Compaq Visual Fortran。 实型数据的种别 种别值 取值范围 备注 REAL([KIND=]4) 或 REAL*4 10-38 到 1038,7位有效数字 缺省值 REAL([KIND=]8) 或 REAL*8 10-308 到 10308,15位有效数字 等价于双精度型DOUBLE PRECISION REAL([KIND=]16) 或 REAL*16 仅用于OpenVMS、Tru64 UNIX、 Linux操作系统 5.2.3 复型数据的种别 • 复型数据在Compaq Visual Fortran中被划分为3 种种别,如表所示。每种表示实型数据的方法都 可以用来表示复型数据的实部和虚部,数据的取 值范围可以参考前一小节。需要注意的是简写写 法与完整写法之间的差别。 复型数据的种别 种别值 备注 COMPLEX([KIND=]4) 或 COMPLEX*8 缺省值 COMPLEX([KIND=]8) 或 COMPLEX*16 等价于双精度复型DOUBLE COMPLEX COMPLEX([KIND=]16) 或 COMPLEX*32 仅用于OpenVMS、Tru64 UNIX、Linux操作系统 5.2.4 逻辑型数据的种别 • 逻辑型数据在Compaq Visual Fortran中被划分为 4种种别,如表所示。由于逻辑型数据只有两种, 即TRUE(真)和FALSE(假),因此不管具体是何 种种别,使用上并无太大区别。笔者的习惯是, 使用逻辑型数据时采用其默认的种别值即可,如 果想进一步节省内存,还可以使用种别值为1的逻 辑型数据。 逻辑型数据的种别 种别值 备注 LOGICAL([KIND=]1) or LOGICAL*1 LOGICAL([KIND=]2) or LOGICAL*2 LOGICAL([KIND=]4) or LOGICAL*4 缺省值 LOGICAL([KIND=]8) or LOGICAL*8 仅用于Alpha芯片机型 5.2.5 字符型数据的种别 • 字符型数据在Compaq Visual Fortran中只有1种 种别,种别值的大小为1。除了上述5种基本数据 类型外,Compaq Visual Fortran中还提供了一种 字节型数据BYTE。该类型的数据只占用一个字节 的存储空间,等价于INTEGER([KIND=]1)。 5.2.6 种别函数 • 尽管没有统一的标准来规定种别的具体分类和数值,但是 Fortran 90/95中提供了用于查询和选择种别值的内部函数。 下面分别介绍这些函数和它们的用途。 • 1.KIND函数:KIND函数用于查询变量所属的种别,函数的 原型为:C=KIND(X)。 • 2.SELECTED_REAL_KIND函数:SELECTED_REAL_KIND函数返 回与实型变量的取值范围和存储精度相一致的种别参数, 函数的原型为:C=SELECTED_REAL_KIND([p][,r])。 • 3.SELECTED_INT_KIND函数:SELECTED_INT_KIND函数返回 与整型变量的取值范围相一致的种别参数,函数的原型为: C=SELECTED_INT_KIND([m])。其中,整型参数m用来指出所 需要的取值范围在-10m到10m之间。 5.3 常数种别和进制 • 变量的种别可以在变量声明的过程中通过类型说 明语句进行说明,那么常数是否也有种别之分 呢?答案是肯定的。既然变量有种别之分,那么 用于给变量赋值的常数自然也有种别之分。种别 值较小(也就是取值范围较小、数据精度较低) 的常数在赋值给种别值较高的变量时,不会有什 么问题;但是反过来将种别值较高的常数赋值给 种别值较低的变量时就会出现问题,如丢失数据 精度等。 • 本节主要介绍如何对程序中出现的常数进行种别 的表明。 5.3.1 非字符型常数的种别说明 • 对于数值型或逻辑型常数这类非字符型常数来说,可以使 用后辍法来表示数据所属的种别。后缀法在常数数值后面 加上一道下划线“_”,然后再跟上具体的种别值。 • 对于实型常数,如果数据指数部分的字母是D时,禁止使用 种别值进行说明。因为字母D表示该数据已经是实型数据的 最高种别,种别值为8,即双精度实数。 • 对于复型常数,如果数据的实部和虚部都是用整数的形式 来表示的,那么它的精度和范围都会被默认为与缺省的实 型常数相同;如果实部和虚部都是用实数的形式来表示, 那么它的精度和范围会视具体的情况来确定:如果实部和 虚部具有相同的种别说明,则复数的种别就是该种别;如 果实部和虚部具有不同的种别说明,则复数的种别由较大 的种别值确定。 5.3.2 字符型常数的种别说明 • 如果是字符型常数,则应改用前辍法来说明数据 的种别。嵌缀法将种别值写在字符型常数的前 面,其间通过下划线“_”把数据部分和种别值连 接起来。例如下面的例子: • 1_’αβγ’ • 2_’计算方法’ • 字符串中使用了希腊字母和汉字,是假定所使用 的计算机系统支持希腊字母与汉字,并且规定这 两个字符串的种别参数分别是1和2。理论上说, 这样进行种别说明是可行的,因为汉字在内存中 会占用两个字节,而一般的英文字符只占用一个 字节。但是,实际情况却不是这样的。 5.3.3 数据的进制说明 • 除了前面两个小节介绍的情况之外,在Fortran 90/95中,还可以根据需要定义不同进制的正整数 常量,这包括二进制、八进制和十六进制。 • 二进制常量以字母“B”开头,后跟定界符括起来 的数字串,定界符可以是单引号“'”或者双引号 “””,数字只能是0或1。例如: • B’01011’ • B”01011” 5.4 属性说明 • 在类型说明语句中除了可以说明对象的类型和种 别之外,还可以进一步说明对象的属性。当一个 对象在类型说明语句中被说明具有某种属性后, 该对象就具有了某种附加的特殊功能。本节主要 介绍Fortran中属性的相关概念。 5.4.1 对象的属性 • 在Fortran 90/95中,一个对象可以没有附加的属 性说明,此时它只是普通的一般变量,与Fortran 77中的变量完全相同。一个对象也可以有多个附 加的属性说明,之后被说明对象就具有了多个附 加的特殊功能,并且确定了它在不同场合下的特 定使用方式。每种属性说明都有专门的关键字与 之对应,各属性关键字间用逗号进行分隔,之间 没有严格的先后关系,但是属性关键字应该写在 类型说明语句的种别说明符之后,分隔符“::” 之前。属性不仅仅可用于说明数据,还可以用于 说明过程。 5.4.2 常用属性说明 • 常用的属性说明是在编程实践中经常会碰到的,这些属性说明会 为程序设计带来某种方便。本小节将对这些常用属性进行简单的 介绍。 • 1.PARAMETER属性 • PARAMETER属性也称为常数名属性、参数属性,这种属性在前面的 小节中已经遇到过,这里作为单独的概念进行解释。 • 2.DIMENSION属性 • DIMENSION属性用于说明一个对象名是数组名,该语句的一般形式 如下: • 类型说明语句, DIMENSION[(形状说明符)] [::] 数组名[(形状说 明符)] • 3.EXTERNAL属性 • EXTERNAL属性用于说明一个函数为外部函数,该属性的一般使用 形式为: • 类型说明语句, EXTERNAL :: 外部函数名 第6章 常用内部过程 • 作为一门古老而又年轻的高级计算机语言, Fortran提供了大量的内部过程供程序员在编程过 程中使用。并且随着新标准的推出,Fortran语言 的内部过程还在继续扩展,能够提供更多的常用 功能,大大减轻了程序员的重复性劳动。本节主 要介绍常用内部过程的基本知识。 6.1 内部过程概述 • 在Fortran语言中,有很多常用的数学过程、字符处理过程 和系统过程。其中,数学过程和字符处理过程通常是以函 数子程序的形式存在,而系统过程则通常是以子例行子程 序的形式存在。这些过程为应用程序的编写提供了非常方 便的手段和丰富的功能。 • Fortran语言将这些功能各异、用途各异的过程分别编写成 一个个独立的子程序,编译后组成一个大的标准过程库 (有时也称为标准函数库),存放在外部存储介质(如计 算机的硬盘)上。用户在完成自身源程序的编译之后,使 用LINK命令将已翻译成二进制指令的目标程序与这个标准 过程库连接起来。通过这个连接,将程序中出现过程名的 地方用标准过程库中相应的一组指令代替,最终形成统一 的用户可执行程序。 6.2 常用数学函数 • 作为一种主要面向科学、工程和事务处理中的数 值计算问题的高级计算机语言,Fortran提供了丰 富的数学函数供用户在编程时使用。本小节主要 介绍在日常编程中经常会遇到和使用的数学函数。 6.2.1 绝对值函数 • 绝对值函数用于求出数据的绝对值,函数的原型如下: • C = ABS(X) • 其中,ABS是Fortran中绝对值函数的通用名。除了这个通 用名外,绝对值函数还有4个专用名,它们是: • IABS,专用于求默认种别的整型数据的绝对值,返回值也 为默认种别的整型数据。 • ABS,专用于求默认种别的实型数据的绝对值,返回值也为 默认种别的实型数据。 • DABS,专用于求双精度实型数据的绝对值,返回值也为双 精度的实型数据。 • CABS,专用于默认种别的复型数据的绝对值,返回值也为 默认种别的复型数据。 6.2.2 指数函数 • 指数函数用于求出数据的指数值,函数的原型如 下: • C = EXP(X) • 其中,EXP是Fortran中指数函数的通用名。除了 这个通用名外,指数函数还有3个专用名,它们是: • EXP,专用于求默认种别的实型数据的指数值,返 回值也为默认种别的整型数据。 • DEXP,专用于求双精度的实型数据的指数值,返 回值也为双精度的实型数据。 • CEXP,专用于默认种别的复型数据的指数值,返 回值也为默认种别的复型数据。 6.2.3 正弦函数 • 正弦函数用于求出数据的正弦值,函数的原型如下: • C = SIN(X) • 其中,SIN是Fortran中正弦函数的通用名。除了这个通用 名外,正弦函数还有3个专用名,它们是: • SIN,专用于求默认种别的实型数据的正弦值,返回值也为 默认种别的整型数据。 • DSIN,专用于求双精度的实型数据的正弦值,返回值也为 双精度的实型数据。 • CSIN,专用于默认种别的复型数据的正弦值,返回值也为 默认种别的复型数据。 • 函数在数学上等价于。函数中参数的单位是弧度而不是 度,在使用时需要注意这一点。 6.2.4 余弦函数 • 余弦函数用于求出数据的余弦值,函数的原型如 下: • C = COS(X) • 其中,COS是Fortran中余弦函数的通用名。除了 这个通用名外,余弦函数还有3个专用名,它们是: • COS,专用于求默认种别的实型数据的余弦值,返 回值也为默认种别的整型数据。 • DCOS,专用于求双精度的实型数据的余弦值,返 回值也为双精度的实型数据。 • CCOS,专用于默认种别的复型数据的余弦值,返 回值也为默认种别的复型数据。 6.2.5 反正弦函数 • 反正弦函数用于求出数据的反正弦值,函数的原 型如下: • C = ASIN(X) • 其中,ASIN是Fortran中反正弦函数的通用名。除 了这个通用名外,反正弦函数还有2个专用名,它 们是: • ASIN,专用于求默认种别的实型数据的反正弦 值,返回值也为默认种别的整型数据。 • DASIN,专用于求双精度的实型数据的反正弦值, 返回值也为双精度的实型数据。 6.2.6 反余弦函数 • 反余弦函数用于求出数据的反余弦值,函数的原 型如下: • C = ACOS(X) • 其中,ACOS是Fortran中反余弦函数的通用名。除 了这个通用名外,反余弦函数还有2个专用名,它 们是: • ACOS,专用于求默认种别的实型数据的反余弦 值,返回值也为默认种别的整型数据。 • DACOS,专用于求双精度的实型数据的反余弦值, 返回值也为双精度的实型数据。 6.2.7 正切函数 • 正切函数用于求出数据的正切值,函数的原型如 下: • C = TAN(X) • 其中,TAN是Fortran中正切函数的通用名。除了 这个通用名外,正切函数还有2个专用名,它们是: • TAN,专用于求默认种别的实型数据的正切值,返 回值也为默认种别的整型数据。 • DTAN,专用于求双精度的实型数据的正切值,返 回值也为双精度的实型数据。 6.2.8 反正切函数 • 反正切函数用于求出数据的反正切值,函数的原 型如下: • C = ATAN(X) • 其中,ATAN是Fortran中反正切函数的通用名。除 了这个通用名外,反正切函数还有2个专用名,它 们是: • ATAN,专用于求默认种别的实型数据的反正切 值,返回值也为默认种别的整型数据。 • DATAN,专用于求双精度的实型数据的反正切值, 返回值也为双精度的实型数据。 6.2.9 自然对数函数 • 自然对数函数用于求数据的自然对数值,函数的 原型如下: • C = LOG(X) • 其中,LOG是Fortran中自然对数函数的通用名。 除了这个通用名外,自然对数函数还有3个专用 名,它们是: • ALOG,专用于求默认种别的实型数据的自然对数 值,返回值也为默认种别的整型数据。 • DLOG,专用于求双精度的实型数据的自然对数 值,返回值也为双精度的实型数据。 • CLOG,专用于求默认种别的复型数据的自然对数 值,返回值也为默认种别的复型数据。 6.2.10 常用对数函数 • 常用对数函数用于求数据的常用对数值,函数的 原型如下: • C = LOG10(X) • 其中,LOG10是Fortran中常用对数函数的通用名。 除了这个通用名外,常用对数函数还有2个专用 名,它们是: • ALOG10,专用于求默认种别的实型数据的常用对 数值,返回值也为默认种别的整型数据。 • DLOG10,专用于求双精度的实型数据的常用对数 值,返回值也为双精度的实型数据。 6.2.11 取整函数 • 取整函数用于将数据转换为整型,函数的原型如下: • C = INT(X) • 其中,INT是Fortran中取整函数的通用名。除了这个通用 名外,取整函数还有3个专用名,它们是: • IFIX,专用于将默认种别的实型数据转换为整数,返回值 为默认种别的整型数据。 • INT,专用于将默认种别的实型数据转换为整数,返回值为 默认种别的整型数据。 • IDINT,专用于将双精度的实型数据转换为整数,返回值为 默认种别的整型数据。 • 函数的执行结果同数学上的取整有所不同,Fortran中的取 整函数只是将数据的小数部分截去,不使用四舍五入规则。 函数的参数可以是实型、整型和复型。 6.2.12 求余函数 • 求余函数用于求第一个变量被第二个变量所除后 的余数,函数的原型如下: • C = MOD(X1, X2) • 其中,MOD是Fortran中求余函数的通用名。除了 这个通用名外,求余函数还有2个专用名,它们是: • MOD,适用于默认种别的整型数据,返回值为默认 种别的整型数据。 • AMOD,适用于默认种别的实型数据,返回值为默 认种别的实型数据。 6.2.13 取符号函数 • 取符号函数返回第一个参数的绝对值与第二个参数 的符号的乘积,函数的原型如下: • C = SIGN(X1, X2) • 其中,SIGN是Fortran中取符号函数的通用名。除 了这个通用名外,取符号函数还有3个专用名,它 们是: • ISIGN,适用于默认种别的整型数据,返回值为默 认种别的整型数据。 • SIGN,适用于默认种别的实型数据,返回值为默认 种别的实型数据。 • DSIGN,适用于双精度的实型数据,返回值为双精 度的实型数据。 6.2.14 最大值函数 • 最大值函数用于找出一系列数据中的最大值,函数的原型为: • C = MAX(X1, X2[, X3]…) • 其中,MAX是Fortran中最大值函数的通用名。除了这个通用名 外,最大值函数还有5个专用名,它们是: • MAX0,适用于默认种别的整型数据,返回值为默认种别的整型数 据。 • AMAX0,适用于默认种别的整型数据,返回值为默认种别的实型数 据。 • MAX1,适用于默认种别的实型数据,返回值为默认种别的整型数 据。 • AMAX1,适用于默认种别的实型数据,返回值为默认种别的实型数 据。 • DMAX1,适用于双精度的实型数据,返回值为双精度的实型数据。 6.2.15 最小值函数 • 最小值函数用于找出一系列数据中的最小值,函数的原型为: • C = MIN(X1, X2[, X3]…) • 其中,MIN是Fortran中最小值函数的通用名。除了这个通用名 外,最小值函数还有5个专用名,它们是: • MIN0,适用于默认种别的整型数据,返回值为默认种别的整型数 据。 • AMIN0,适用于默认种别的整型数据,返回值为默认种别的实型数 据。 • MIN1,适用于默认种别的实型数据,返回值为默认种别的整型数 据。 • AMIN1,适用于默认种别的实型数据,返回值为默认种别的实型数 据。 • DMIN1,适用于双精度的实型数据,返回值为双精度的实型数据。 6.3 常用字符函数 • Fortran 90/95的内部函数中新增了许多和字符型 数据操作有关的函数供用户使用,可以很方便地 进行各种字符操作。 6.3.1 字符与数值转换函数 • 这是一系列函数的总称,涉及到4个基本函数: ICHAR,CHAR,IACHR和ACHAR函数。 • Fortran 90/95中允许把字符型的值转换成数值型 的值(已知字符得到字符在某种字符集中的序号 数值),也可把数值型的值转换成字符型的值 (已知字符在某种字符集中的序号数值进而得到 该位置上的字符)。 • 函数ICHAR的作用是根据字符得到字符在计算机字 符集中的位置,函数的原型为: • C = ICHAR(字符数据) 6.3.2 字符串长度函数 • 字符串长度函数用于求出参数字符实体的字段长 度,函数的原型为: • C = LEN(string) • 其中,string必须是字符型,可以是字符标量也 可以是字符数组。函数返回一个正整数,为该字 符实体的字段长度(包括尾随空格、中间空格在 内)。比如LEN(‘ABC 123 ’)的值是9(6个字 符加3个空格)。 • 如果想要得到不计尾部空格的字符串长度,则可 以使用LEN_TRIM函数。函数的原型如下: • C = LEN_TRIM(string) 6.3.3 子字符串位置索引函数 • 子字符串位置索引函数的作用是找出指定子字符串在目标 字符串内的起始位置,函数的原型如下: • C = INDEX(string, substring[,back]) • 其中,string表示要在其中进行索引的目标字符串; substring表示子字符串;back为逻辑型变量,当back的值 为.TRUE.时表示从目标字符串的后面开始搜索,当back的 值为.FALSE.或省略不写时,表示从目标字符串的前面开始 搜索。函数值返回一个正整数,指明子字符串在目标字符 串中是从第几个字符开始的。目标字符串和子字符串可以 是任何形式的字符实体。如果目标字符串中不含有子字符 串中的内容,则函数的返回值为0。例如 INDEX(‘VitaminC’,‘i’)的值为2; INDEX(‘VitaminC’,‘i’,BACK=.TRUE.)的值为6; INDEX(‘VitaminC’,‘b’)的值为0。 6.3.4 字符串验证函数 • 字符串验证函数的作用是确认指定的字符集中是否包含了 给定字符串中所有的字符,函数的原型为: • C = VERIFY(string, set, [,back]) • 其中,string为要在其中进行验证的字符集;set为给定的 字符串;back表示是从字符集的前面还是后面开始验证: 当back值为.TRUE.时表示从后面开始验证,当back的值 为.FALSE.或省略不写时表示从前面开始验证。函数的返回 值是一个正整数,指明给定字符串中与字符集相异的第一 个字符是从左边第几个字符开始的。如果没有相异的字 符,则返回函数值为0。比如VERIFY ('banana','nbc')的 值为2,VERIFY ('banana','nbc',BACK=.TRUE.)的值为6, VERIFY('banana','nbca')的值为0。 6.3.5 尾部空格除去函数 • 尾部空格除去函数的作用就是除去字符串尾部的尾随空 格,函数的原型为: • C = TRIM(string) • 函数的返回值与函数的输入参数类型相同、种别相同。返 回值就是输入参数除去尾部空格后剩余的部分。比如 TRIM(‘ABC 123 ’)///’34’的值是’ABC 12334’。 • 这个函数在进行文件名的处理上较为方便:为了保存文件 的完整路径名称,通常需要使用一个字段宽度较大的字符 变量(在Windows系统下,如果使用系统函数,一般要求 256个字符宽度)。如果输入的文件实际路径名较短,则在 显示时会留下大段的空格。使用TRIM函数就可以避免这种 现象。 6.3.6 字符大小比较函数 • 字符大小比较函数是一个函数族,共包含了4个基 本函数。其作用就是比较字符在ASCII字符集中的 先后位置,其原型为: • C = LGE(string_a, string_b) • C = LGT(string_a, string_b) • C = LLE(string_a, string_b) • C = LLT(string_a, string_b) • 四个函数都是用两个字符型实体作为参数,用来 判断它们之间是否满足大于等于、大于、小于等 于、小于关系。 6.4 常用系统过程 • 在Fortran 90/95中还增加了许多常用的系统过 程,这些系统过程大多采用子例行子程序的形式。 这里介绍的主要是用于获得系统时间和随机数的 系统过程。 6.4.1 CPU时间函数 • 该函数用于返回同处理器无关的精确的处理器运行时间,单位为秒。过程 的原型如下: • CALL CPU_TIME (time) • 其中,time是一个实型变量,用于保存处理器的运行时间。如果过程不能 返回有意义的时间,则会返回一个与处理器无关的负值。 • CPU_TIME函数可以用于获得某一段可执行代码在CPU中的实际运行时间, 比如下面的代码段用于确定代码执行时间并将其打印出来: • REAL :: time_begin, time_end •…… • CALL CPU_TIME ( time_begin ) • !可执行代码段 • CALL CPU_TIME ( time_end ) • PRINT *, 'Time of operation was ', time_end - time_begin, ' seconds' 6.4.2 日期和时间函数 • 该过程用于返回从实时时钟处获得的日期和时间相关信 息,日期和时间的格式符合国际标准ISO 8601:1988。过程 的原型如下: • CALL DATE_AND_TIME ( [date] [, time] [, zone] [, values] ) • 其中,date用于保存返回的日期信息,必须是一个至少有8 个字符长度的字符串。可以用于保存世纪、年、月、日等 信息,形式为CCYYNNDD。下面分别给出这些字符的意义: • CC:表示系统中所处的世纪; • YY:表示系统中所处的年份; • MM:表示系统中所处的月份; • DD:表示系统中所处的日期。 6.4.3 系统时间过程 • 系统时间过程用于保存从系统实时时钟处获得的 数据信息。过程的原型如下: • CALL SYSTEM_CLOCK ([count] [, count_rate] [, count_max]) • 其中,count用于保存处理器时钟的当前值,必须 是一个默认种别的整型变量。每有一个时钟计数 发生,count的值就会增加1,直到数值达到 count_max,然后count被清零重新开始计数。 • count_rate必须是一个整型数据,用于指定每秒 钟内触发的时钟计数次数。 • count_max也是一个整型数据,用来设置时钟计数 的最大值,可以为系统能够处理的最大整数。 6.4.4 随机数生成过程 • 随机数生成过程用于生成伪随机数,结果可以是 一个标量也可以是一个数组。过程的原型如下: • CALL RANDOM_NUMBER (harvest) • 其中,harvest必须是实型变量,可以是标量或数 组。随机数的大小范围在0到1之间。 第7章 运算符及表达式 • 在Fortran中提供了极为丰富的运算符,囊括了算 术运算、逻辑运算、字符运算等方面。特别是在 算术运算方面,作为这种高级计算机语言的专 长,Fortran提供了一些其他高级语言需要使用函 数才能进行的计算能力。例如乘方运算。本章开 始讲述有关Fortran运算符和表达式的相关内容。 7.1 算术运算 • 算术运算是Fortran语言中功能最为强大的部分, 也是Fortran直到今天仍然活跃在计算机舞台上的 重要原因。本节主要介绍Fortran语言中的算术运 算及其相关概念。 7.1.1 算术运算符及其优先级 • 在Fortran语言中编写算术表达式是相当方便的。除了部分 算术运算符在外观上有所不同之外,程序中的算术表达式 与教科书中的算术表达式几乎没什么区别。Fortran语言中 定义了五种基本的算术运算。这五种基本的算术运算和运 算符罗列如下: • 算术加法,运算符为加号“+”,表示数学中的加号; • 算术减法,运算符为减号“-”,表示数学中的减号; • 算术乘法,运算符为星号“*”,表示数学中的乘号; • 算术除法,运算符为撇号“/”,表示数学中的除号; • 乘方运算,运算符为两个连续的星号“**”,表示数学中 的乘方。 7.1.2 算术表达式 • Fortran语言中允许使用四种类型的表达式,即: 算术表达式,关系表达式,逻辑表达式和字符表 达式。本节介绍其中的算术表达式,其他类型的 表达式将在后面的小节中进行介绍。 • 所谓算术表达式是指由一个或多个算术运算符组 成的,能够完成特定计算任务的式子。在Fortran 程序中,算术表达式是由Fortran语言规定的算术 运算符和括号将各种算术运算量(包括常量、变 量、函数、数组及数组元素)连结起来的一个有 值表达式。例如下面就是一个Fortran算术表达式 的实例: • A + B – C*TAN(D)/(ABS(E) + F) 7.1.3 算术表达式中的类型转换 • 在本章的一开始,就已经介绍了Fortran中的常量和变量是 分类型的,那么不同类型的数据之间在算术表达式中是否 能够进行运算?如果可以,那么又该遵循什么规则呢? • Fortran语言允许不同类型的算术运算量(如整型、实型、 双精度和复型)之间进行算术运算,但不允许在算术运算 量和非算术运算量(如逻辑型、字符型)之间进行算术运 算。例如“2*10.0”是允许的,但“2*TRUE”是不允许的。 • 那么不同类型的算术运算量之间的运算结果应该是什么类 型呢?例如“2*10.0”的结果应该是“20”还是“20.0” 呢? 7.1.4 算术运算的误差 • 由于计算机在保存整型数据时是准确无误的,只存在保存 值的范围问题;而在保存实型数据时,不仅仅存在保存的 值有一个范围,而且在存储数据的精度上也是受存储空间 限制的。例如想要保存圆周率的精确值,这几乎是不可能。 在本书配套光盘/Tools/PI目录下有一个小软件Super可用 于计算圆周率,从这个软件可以看到想要精确存储圆周率 将要花费的存储空间是不可想像的。 • 正因为存储精度上的问题,计算机在进行整型数据之间的 运算时是准确无误的,结果不存在任何误差(只要结果在 整型数据的保存范围内)。而实型数据之间的运算就会由 于存储精度方面的问题而出现一些误差。来看两个实例。 7.2 关系运算 • 在实际的数学或工程问题中,经常会遇到比较两 个数据谁大谁小的问题。这就是一个关系比较的 问题。在Fortran中,提供了进行这类操作的运算 符和表达式规则。本节就开始对此进行介绍。 7.2.1 关系运算符 • 关系运算符也可以称作关系比较符,它是一个双 目运算符。Fortran中定义了六个基本的关系运算 符,这六个基本关系运算符在不同的Fortran标准 中有各自的表示方式。 Fortran 77中的 关系运算符 运算符定义 所代表的数学符号 意义描述 .GT. >大于 .GE. ≥ 大于等于 .LT. <小于 .LE. ≤ 小于等于 .EQ. =等于 .NE. ≠ 不等于 7.2.2 关系表达式 • 介绍了关系运算符之后,现在来介绍关系表达式。关系表达式是 最简单的一种逻辑表达式,它的一般形式为: • <关系运算量> <关系运算符> <关系运算量> • 关系元算量可以是算术量,也可以是字符量。这里只介绍算术量 的关系表达式,字符量的关系表达式将在字符表达式的小节中进 行介绍。算术量可以是数值型常量、数值型变量、数值函数,还 可以是算术表达式。下面是一些关系表达式的实例。 • A+B>3.5 等价于 A+B.GT.3.5 • 40>=40 等价于 40.GE.40 • 40<逻辑运算符><逻辑运算量>[<逻辑运算符><逻辑运算量>…] • 同关系表达式不同,一个逻辑表达式中可以包括多个逻辑运算符,例如逻 辑表达式“A.GE.10.0.AND.A+C.GT.B+D.OR..NOT..TRUE.”中包含 了.AND.、.OR.、.NOT.三个逻辑运算符。在逻辑表达式中除了有逻辑运算 符外,还可以有关系运算符和算术运算符。 • 下面是一些逻辑表达式的例子: • (A.GT.B).AND.(A.NE.C) 等价于(A>B).AND.(A/=C) • (X.LT.-1.0).OR.(X.GT.1.0) 等价于(X<-1.0).OR.(X>1.0) • .NOT.((A+C)*D.LE.0.0) 等价于.NOT.((A+C)*D<=0.0) • (A.GT.B).EQV.(C.LE.D) 等价于(A>B).EQV.(C<=D) • L1.NEQV.L2 7.3.3 逻辑表达式的运算秩序 • 在上面的一系列例子中,有的逻辑表达式中既有 逻辑运算符,又有关系运算符和算术运算符。对 于这种混合了多种运算符的表达式,Fortran语言 规定了不同运算符的运算顺序: • 首先计算算术表达式的值(例如前面一个范例中 的A+C和B+D)。 • 接下来再求关系表达式的值(例如前面一个范例 中的A.LE.10.0和A+C.GT.B+D)。 • 最后进行逻辑运算,各个逻辑运算符的先后顺序 是:.NOT.>.AND.>.OR.>.EQV.=.NEQV.。 • 如果逻辑表达式中有括号,则先进行括号内的运 算。 7.4 字符运算 • 除了为数值运算提供了丰富的运算操作外,Fortran语言中 字符型数据之间也可以进行运算。相应的字符运算符只有 一个,称为字符连接符。字符连接符为两个相连的撇号 “//”,撇号中间不允许插入空格。 • 字符运算符功能就是把符号前后的两个字符运算量连接在 一起。字符运算量可以是字符型变量、字符型常量、子字 符串、字符函数、字符数组元素等等。 • 在Fortran程序中,字符表达式是由Fortran语言规定的可 进行字符操作的运算符将各种字符运算量连结起来的一个 有值表达式。例如字符表达式’Vitamin’//’C’的值 为’VitaminC’;字符表达式A//’Include:’//A(2:6)//’ And ’//B(1:4)//’don’’t include ’//D将字符型变 量、字符型常量、子字符串等连在了一起。 第8章 程序流程控制 • 在前面的章节中,程序的执行都是按照语句出现 的先后次序来执行的。在实际的计算任务中,能 够按照固有的执行次序完成计算的问题只是少数 的简单问题。大多数的问题往往在程序执行过程 中,根据实现设计的计算步骤(也就是通常所说 的算法)往往会出现若干分支选项或是重复计算 的情况。流程控制就是提供一种选择,使得除了 常规的串行计算序列之外,能够应对这个序列中 可能出现的选择分支与循环的情形。本章将介绍 算法与流程的基本知识和两种基本的控制结构。 8.1 算法与流程 • 要想充分利用计算机的高速计算能力来处理实际 的问题,需要使用者能够将问题抽象成计算能够 理解的计算机语言,也就是使用者应该编写计算 机程序的能力。那么学习了一种计算机语言是否 就具有了编程的能力呢?答案是否定的。使用者 还需要具有将实际问题分解成一连串具体可操作 步骤的能力。这就涉及到算法的问题了。通常意 义上的编程高手,不仅仅是指这个人对于某一门 计算机语言有深入的研究,还指这个人在算法上 也有一定的造诣。 8.1.1 算法 • 学习一种计算机语言仅仅学习它的语法规则还不够,更重要的是 要学习如何针对各种类型的具体问题,制定行之有效的解决方法 和操作步骤,也就是所谓的算法(Algorithm)。只要学会了制定 正确且有效的算法,用何种高级计算机语言来编写具体的计算机 程序就仅仅只是一个工具选择的问题了。因此,算法的设计也是 计算机程序设计的核心内容。 • 需要注意的是,算法这个词所涉及的并不仅仅是计算的问题。算 法可以包括很多领域,泛指为解决实际而采取的方法和步骤。在 英语中,计算方法和算法是两个词。前者是“Computational Method”,后者是“Algorithm”。计算方法更像通常意义上所理 解的算法,它指求解数值解的近似方法。实际的“算法”一词含 义更为广泛。它不仅仅指数值计算中的计算方法,还可以指事务 处理中的规章流程、物资供应中的调配方法、糕点师傅制作蛋糕 的技法等。而程序员所关心的,自然仅仅是指能够在计算机上实 现的算法。 8.1.2 传统流程图 • 流程图是一种算法描述手段,它用一些图框来表示计算过程中各 种类型的操作。在图框中写出算法的各个步骤,然后用带箭头的 线条把这些图框连接起来,以表示执行的先后顺序。采用这种方 式来表示算法,形象直观,理解起来非常容易。美国国家标准化 协会ANSI规定了一些常用的流程图符号,已为世界各国的程序工 作者普遍采用。下面介绍最常用的一些流程图符号。 8.1.3 基本程序结构 • 前一节介绍的流程图又称为传统流程图。传统流程图中使用流程 线来指出各个框之间的执行顺序,流程线在使用上没有严格限制。 因此,流程图的绘制者可以根据自身的意愿将流程线画成他想要 的形式,随之而来的是程序的执行流程也随意地转来转去。这种 做法使流程图显得杂乱无章,阅读者需要花费相当的精力去追踪 程序流程,对算法逻辑的理解也较为困难。人们为这种情况起了 一个名字——BS,意思是一碗面条。 • 要提高算法的描述质量,使算法在设计和阅读方面都变得方便, 就必须限制流程线的滥用。即流程线不能毫无规律的四处乱转, 必须按一定的方向来绘制。但是,分支结构和循环结构又是在描 述算法时不可避免的。一个算法不可能按从头到尾的顺序执行下 来,总会有一些向前或向后的非顺序转移。针对这些问题,人们 设计了三种最基本的流程结构,使用这三种基本结构就可以表示 一个良好的算法。可以将这些基本结构称为预制件,一个算法就 是通过这些预制件像搭积木一样按顺序排列起来的。 8.1.4 用伪代码表示算法 • 用前面介绍的流程图来表示算法直观易懂,但画起来却比 较费事。同时在设计一个算法时往往不可能一蹴而就,经 常需要对原来的想法进行反复修改。这时,想要在已经画 好的流程图上添加新的元素就非常困难了。因此,流程图 只适宜于表示算法,用在设计算法的过程中并不是很理想 (特别是算法比较复杂,需要经常进行修改时就显得更不 方便)。 • 为了方便的进行算法设计,经常会使用一种称为伪代码的 工具。伪代码是用介于自然语言和计算机语言之间的文字 和符号来描述算法。使用伪代码来描述代码就像写一篇带 命题的叙事作文一样,只要按照事件发生的逻辑先后次序 来书写就可以了。它不使用图形符号,因此描述算法时相 当方便,格式紧凑、易于理解,最重要的是它很易于向计 算机程序语言过渡。 8.2 选择结构 • 选择结构提供一种多义执行的手段,即当判断条 件满足特定要求时就去执行特定的操作。选择结 构是三种基本程序结构之一。Fortran语言中提供 了多种语句来实现选择结构。本节将主要介绍 Fortran语言中选择结构的实现方式和相关的语句。 8.2.1 判断语句IF和分支选择语句SELECT CASE • IF语句和SELECT CASE语句在Fortran语言中都能够用于构造选择 结构,两者在语言功能上并无多大差别。本节简要介绍IF语句和 SELECT CASE语句的基本概念和作用,详细的用法会在后面的小节 进行介绍。 • 在Fortran 77时代,选择结构是通过IF语句和IF构造来实现的。 其中,IF语句只能提供单一选择,即条件满足就执行某种操作, 条件不满足则不执行任何操作。IF语句在有多个并列条件需要判 断时就很不方便,而且一旦某一个条件成立后,IF语句并不会跳 过其余的判断。这就使得判断的效率较低。在有多个并列条件需 要判断时,最好使用IF构造。IF构造能够提供多义选择,即构造 中存在多个条件选项,当不满足条件一时,就去检查条件二。只 要在进行检查的过程中有一个条件满足就会去执行相应的操作, 执行完相应的操作后就跳出所在的IF构造,而不会再去检查其余 的判断条件。 8.2.2 判断语句IF的基本用法 • 使用IF语句来实现选择结构通常有两种用法:语 句形式和构造形式。前者一般用于实现单一选 择,后者则可以实现二义选择。 • 1.语句形式 • 采用IF语句来实现单一选择的一般语法形式如下: • IF(逻辑表达式) 执行语句 • 2.构造形式:除了能够使用IF语句来实现单一判 断之外,还可以使用块IF语句来组成IF构造用于 多重选择。 • 3.IF语句的使用 8.2.3 判断语句IF实现多重判断 • IF构造除了可以实现二义判断之外,还可以实现多重判断。这时 候,IF构造中需要加入新的元素——ELSE IF语句。有了ELSE IF 语句后,IF构造中可以同时存在多个判断条件和多个执行模块, 但是只有其中一个条件能够成立,并且只有一个执行模块能够执 行。使用IF语句和ELSE IF语句来实现多重判断的基本形式如下: • IF(逻辑表达式1) THEN • THEN 块 • ELSE IF(逻辑表达式2) THEN • ELSE IF块 •…… • ELSE • ELSE块 • END IF 8.2.4 IF语句的嵌套 • IF构造除了能够单独使用外,构造中的任意一个语句块里都可以再次嵌入 另一个构造。被嵌入的构造可以是另一个IF构造,也可以是另一些形态、 功能不同的构造,如CASE构造、DO构造等。前提是必须将整个构造完整地 嵌入到IF构造的某一个语句块中,不允许被嵌入构造的一部分在一个语句 块中,另一部分在别的语句块或是不在被嵌入的IF构造中,即被嵌入的任 何构造不允许跨越两个独立的语句块。IF语句嵌套的形式可以表示如下: • [构造名1:]IF(逻辑表达式1) THEN • [构造名2:]IF(逻辑表达式2) THEN • [构造名3:]IF(逻辑表达式3) THEN •…… • ENDIF[构造名3] •…… • ENDIF[构造名2] •…… • ENDIF[构造名1] 8.2.5 IF语句的特殊用法 • 在Fortran 77中,IF语句还能够实现一种特殊的选择方法,也就是算 术IF语句。算术IF语句根据算术表达式值得结果,有条件的将程序的 计算流程转到三条执行语句中的一句。IF语句的这种用法的基本形式 如下: • IF (表达式) 标号1, 标号2, 标号3 • 其中,表达式为标量整型或实型表达式,两端用括弧括起来;标号1至 3必须是本程序单元中的有效可执行语句的标号。语句中所有的三个语 句标号都必须书写,但并不一定要指向三个不同的语句,在同一个算 术IF语句中允许同一个语句标号出现多次。算术IF语句在执行时,首 先会计算表达式的值,根据表达式的值来确定要执行哪一个标号指定 的执行语句: • 如果表达式的值小于零,则程序流程转到标号1指定的语句去执行; • 如果表达式的值等于零,则程序流程转到标号2指定的语句去执行; • 如果表达式的值大于零,则程序流程转到标号3指定的语句去执行。 8.2.6 SELECT CASE语句的用法 • SELECT CASE语句是Fortran 90/95标准中新增加的语句。该语句 用于容纳CASE块,为程序员提供了一种从多个备用可执行分支选 项中选取一个来执行的手段。尽管多重判断的IF构造也可以实现 这一功能,但是在某些多条件选择的应用场合使用IF构造会使代 码显得比较繁琐、层次关系比较复杂。使用CASE构造就可以避免 这一问题,编写出来的代码也显得更加直观、简洁。 • CASE构造的作用和IF构造非常类似,它也用于编写分叉选择算 法,即根据判断条件的成立与否来区分操作不同的可执行模块。 不同之处在于:CASE构造只能把某个判断条件的可能结果区分成 若干个孤立的离散值或片断(这意味着CASE构造中的判断条件不 允许出现重叠),按不同的值或片断进行不同的操作。如果遇到 判断条件比较复杂或者存在多种判断条件互相交叉的情况时, CASE构造在处理这些判断条件时显得不是很方便,在这种情况下 只能考虑使用IF构造来进行处理。 8.2.7 SELECT CASE语句的应用 • 下面给出一些SELECT CASE语句的实际应用例子。 • 首先演示的程序使用了整型表达式的CASE构造, 例子的原型就是程序TEST0802的个人所得税计算 程序。在进行改动前需要注意,CASE构造中的选 择表达式是不允许为实型表达式的。因此,需要 一个将实型数据转换为整型数据的函数,可以考 虑使用基本数学函数中的INT函数。 8.3 循环结构 • 除了顺序结构、选择结构外,实际的程序中还常 常遇到需要重复执行的操作或代码段。在这种情 况下,就需要用到一种新的控制结构——循环结 构。如果程序的世界中缺少了这种结构,那么结 果将不可想象。比如上一节最后一个实例中所作 的5次猜测,如果没有循环结构,同样功能的代码 段,即“猜测-判断”,需要重复书写5次。这种 情况至少还知道要重复书写几次,在有的应用领 域,如数值领域的迭代求解,甚至连要重复的次 数都不清楚。循环结构的出现使得这一类问题变 得不再是任何问题。本节就将介绍循环结构的相 关内容。 8.3.1 基本的DO构造 • DO构造在Fortran 77和Fortran 90/95中都提供用以执行循环操 作,但是两个标准在DO构造的具体实现形式上是不同的。尽管这 样,两个标准下的各种DO循环都可以归纳为如下所示的DO构造一 般形式: • [构造名:] DO [标号][循环控制] •块 • 终止语句 • 其中,构造名选项只允许在Fortran 90/95标准中使用,用于标识 构造的起止范围;DO语句后的标号选项在Fortran 77和Fortran 90/95标准中同样适用,尽管带标号的DO构造是较老的语法形式; 循环控制用于控制循环的执行,提供循环停止或跳出循环的手段 等;中止语句用于标定循环构造的结束位置,并不是说循环执行 到该条语句就会停止执行,而是通过该条语句将流程返回到DO语 句。 8.3.2 无条件循环与DO语句 • 现实中的循环可以分为不带循环变量与带循环变量两种形 式,前者只能通过循环体中的条件判断等跳出循环体;后 者则能够通过循环变量来执行确定次数的重复操作。后者 也可以称为无条件的循环,它可以不通过条件判断来实现 循环的终止。 • 当需要执行的循环次数为已知时,使用DO语句来实现循环 比较方便。它由一个DO语句和循环体组成,在Fortran 90/95标准中的一般形式如下: • [构造名:]DO 循环变量 = 循环初值,循环终值[,循环增 量] • 循环体 • ENDDO [构造名] 8.3.3 条件循环与DO构造 • 在编程实践中,除了会遇到执行次数确定的循环外,还有 一种情况是执行次数未知的循环。这一类循环不能通过使 循环变量“增加”到循环终值的形式来终止,只能通过循 环体中的判断条件来控制程序的流程是否跳出循环构造。 因此,这种循环又被称为条件循环,在相应的循环构造中 不含醒 房刂票淞俊 • 1.条件循环实现方式一 • 2.条件循环实现方式二 • 除了将判断条件写在循环体内的方式(也称为直到型循环) 之外,还可以通过WHILE语句来实现当型循环。在Fortran 90/95标准中,DO WHILE语句被增加到循环构造方式中以支 持当型循环。 8.3.4 循环的署名 • 同其他的一些构造一样,循环也是可以命名的,命名的原则同变 量的命名原则完全一致。循环一旦命名,则在循环终止语句后必 须跟上循环的名字,以使编译程序明白那一个循环结构被封闭。 命名的循环在有多层嵌套或是DO构造较多的情况下会使各个循环 显得更加清晰。下面来看一段实际的例子。 • TEST0818.F90 • ! 署名循环范例 • PROGRAM TEST0818 • IMPLICIT NONE • • INTEGER :: I • • PRINT: DO I = 1, 2 • PRINT *, '第', I, '次循环…' • ENDDO PRINT • • END PROGRAM TEST0818 8.3.5 循环的嵌套 • 同IF构造和SELECT CASE构造类似,DO构造也允许在自身中 再嵌入其他的DO构造。在一个DO循环中又完整地包含另一 个DO循环的方法,称为DO循环的嵌套。循环嵌套中的各层 循环变量不允许重名。循环嵌套的层数可以不限,但是循 环嵌套的层次太多会使得各层循环不容易分辨。对此,可 以通过对循环进行命名以使循环嵌套的层次更清晰。 • 要注意,循环嵌套中的内循环应当完整地嵌套在外循环之 内,也就是说内循环是外循环中循环体的一部分,内外循 环不允许交叉。例如,如下形式的循环嵌套是合法的: • OUT: DO I = 1, 4 • IN: DO J = 1, 5 •…… • ENDDO IN • ENDDO OUT 8.3.6 DO循环规则 • 在使用DO循环时,需要注意循环的一些其它规则。比如,循环变 量可以在循环体中被引用,但不应当再被赋值,即使循环变量的 值保持不变。例如下面的写法都是不正确的,循环变量N和M不能 在循环体内被重新赋值: • DO N = 1, 10 •…… • N = N*2 •…… • ENDDO • DO M = 1, 10 •…… • M = M •…… • ENDDO 8.3.7 隐式DO循环 • 隐式DO循环实际上是一种带控制循环变量的DO循 环,但简化成只有DO循环的第一句,并且把关键 字DO隐去。隐式DO循环的一般形式如下: • I = m1, m2[, m3] • 其中,m1表示循环的初值;m2表示循环的终值; m3表示循环的增量。如果省略本项目,则默认为1。 • 隐式DO循环不是一种可以独立存在的语句。它只 能作为输入输出列表的一个组成部分,用来控制 重复读写的次数。它的应用形式如下: • (I/O列表, 循环变量名 = 循环初值, 循环终值[, 循环增值]) 8.4 循环的控制 • 在Fortran 90/95标准中,引入了两个控制循环执 行流程的语句EXIT和CYCLE。这两条语句实际上在 某些Fortran 77编译器中早已被当成了不成文的 标准之一了。 8.4.1 EXIT语句 • 在实际变成种,有许多实际问题是无法预先知道循环次数 的,比如一些数学和工程领域中的迭代算法。对于这类问 题,最常见的做法是给出一个判别条件。如果满足这个判 别条件就重复执行循环体,否则就退出循环。因此有条件 循环时循环的执行次数不是固定的。传统的做法是使用 GOTO语句来使流程跳出循环,但这种方法不符合结构化程 序设计的要求。针对这种情况,Fortran 90/95通过引入 EXIT语句来满足结构化程序设计的要求。 • EXIT语句的作用是停止循环并使流程控制退出循环结构, 因此又被称为出口语句。该语句的一般形式如下: • EXIT [DO构造名] 8.4.2 EXIT语句与条件循环 • 通过DO WHILE语句来实现循环猜测,在这条DO WHILE语句 中的逻辑判断表达式就是简单的逻辑真(TRUE)。这种用 法是允许的,它表示当型循环的执行条件一直满足,无须 进行判断。但是在使用时应该注意,在循环体内一定要提 供跳出循环的手段,否则循环会一直执行下去形成死循环。 下面的代码通过在判断语句中加入EXIT语句,跳出循环。 • IF(Rchar == Gchar) EXIT • 如果用户输入的字符等于系统随机得到的字符,则执行 EXIT语句退出循环。如果用户猜测的字符不正确,则会向 用户提示应该向哪个字符方向进行猜测。 8.4.3 EXIT语句与无条件循环 • 除了应用于条件循环中用作循环退出的手段外, EXIT语句还可以应用于无条件循环中。当EXIT语 句应用于无条件循环中时,如果循环变量大于循 环终值或是与EXIT语句配合的逻辑表达式为真都 会跳出循环的执行。这种应用通常用在无法预知 循环执行的次数并且不知道循环退出条件是否能 够满足的场合。比如,计算流体力学中求解流动 问题时,通常采用这种方法来提供双重的循环退 出机制。由于这类问题的复杂性会涉及到计算方 法、流动对象的网格好坏等因素,通常不知道该 问题是否能够收敛,也不知道需要计算多少步才 能收敛。 8.4.4 CYCLE语句 • CYCLE语句是另一种常用于循环的流程控制语句。同EXIT语 句不一样,CYCLE语句的作用不是流程跳出循环,而是使流 程重新回到循环的开头。该语句的一般形式为: • CYCLE [DO构造名] • 当循环执行到CYCLE语句时,它会使循环的流程跳过位于它 之后的那部分DO块,重新返回到循环的第一个可执行语句 开始执行。运用CYCLE语句,可以使循环在某一次的迭代过 程中不执行该语句后面的代码,使循环的应用更为灵活多 变。 • CYCLE语句与EXIT语句一样属于特定的DO构造。如果语句引 用了DO构造名,则它属于该构造,否则它属于所出现的最 内层DO构造。 8.5 再论GOTO语句 • GOTO语句是相当古老的流程控制语句,在Fortran 77时代是主要的流程控制语句。尽管该语句功能 强大,但是滥用该语句会造成整个程序的流程杂 乱无章,不符合结构化程序设计的要求。这种说 法并不意味着GOTO语句就不能使用,而是建议在 程序中慎用该语句。当然,某些GOTO语句的形 式,如计算GOTO语句和赋值GOTO语句,在Fortran 90/95标准中是被废除、不建议使用的语句。 8.5.1 无条件GOTO语句 • 该语句的一般形式如下: • GOTO label • 其中,label是本程序单元中可用的语句标号,必 须出现在可执行语句之前。比如 •…… • X = Y + 3. • GOTO 4 • 3 Y = Y+5. • 4 Z = X+Y •…… 8.5.2 计算GOTO语句 • 计算GOTO语句在Fortran 95标准中是一项被废除 的语法,这里只作简单介绍以便在阅读一些旧 Fortran程序时能够有所帮助。 • 计算GOTO语句的用途是根据表达式的值来确定程 序的控制流程转向一系列设定的分支目标中的一 项。这种GOTO语句的一般形式如下: • GOTO (标号列表)[, ]表达式 • 其中,标号列表由本程序单元中一系列可用的分 支目标语句的标号组成,标号之间通过逗号“,” 来分隔。同样的标号允许在标号列表中出现多次。 8.5.3 赋值GOTO语句 • 赋值GOTO语句在Fortran 90标准中是一种过时的语法,在 Fortran 95标准中则被废除。同无条件GOTO语句相比,赋 值GOTO语句后所跟的不是语句标号,而是表示语句标号的 整型变量。需要注意的是,这里的整型变量不允许通过赋 值符进行赋值,比如下面的赋值GOTO语句是错误的: •…… • I = 10 • GOTO I •…… • 赋值GOTO语句中的标号变量必须使用ASSIGN语句来赋值, 比如下面的形式就是正确的: •…… • ASSIGN 10 TO I • GOTO I •…… 8.5.4 用?还是不用? • 正如前面一节提到的,GOTO语句的滥用在一定程 度上决定了这一语句目前的处境。但是GOTO语句 自身并没有错误,错误在于程序员过分依赖于 GOTO语句的强大功能而没有注意到程序自身应该 具有的逻辑性。 • 因此,在新的Fortran标准中除了计算GOTO语句和 赋值GOTO语句这两种过时的语法被废除以外,无 条件GOTO语句仍然在新标准中保留了自己的一席 之地。在实际编程过程中,用不着因为害怕破坏 程序的结构而不敢去使用GOTO语句。只要在使用 中时刻牢记GOTO语句只在一个基本程序结构中使 用,程序流程的跳转仅限于基本结构之内。 8.6 程序结束、终止和暂停 • 在Fortran中,程序的结束、终止和暂停都由相应 的语句来完成。正确的了解和使用这些语句不仅 能使程序的结构更为清晰,还能提供额外的功能 以加强程序的交互性。本节将介绍Fortran中这三 种操作的对应语句。 8.6.1 程序结束(END) • 在Fortran中,END语句的作用主要有两点: • 结束本程序单位的运行; • 作为一个程序单位结束的标志。 • END语句只能出现在一个程序单元中的最后一行, 并且一个程序单元只能有一个END语句。在主程序 中,END语句的作用是使整个程序结束运行。在子 程序中,END语句一方面作为子程序结束的标志, 另一方面则使流程返回到调用程序中。 8.6.2 程序终止(STOP) • 在Fortran中,STOP语句的唯一作用就是终止程序 的运行。同END语句不同,STOP语句可以出现在程 序可执行语句中的任意位置,并且可以有多个。 程序在运行到STOP语句时,就会停止执行。子程 序中的STOP语句并不会使流程返回到调用程序, 而是直接停止整个程序的执行。 • 当一个程序中有多个STOP语句时,为了使用户能 够辨别是哪一条STOP语句停止了程序的运行,可 以在执行STOP语句的同时输出必要的信息。其一 般形式如下: • STOP [停止代码] 8.6.3 程序暂停(PAUSE) • PAUSE语句又叫暂停语句,这是一个Fortran 77的遗留产物。 在Fortran 90中不推荐使用该语句,在Fortran 95中则被 废止。这主要是由于该语句可能会造成程序执行的不可预 料性,例如在Compaq Visual Fortran中进行视窗程序开发 时,如果使用了PAUSE语句,程序并不会如预期那样暂停执 行,而是反复不断的弹出控制台窗口。 • PAUSE语句的作用是使程序暂时停止执行,而不是终止或结 束执行。在程序执行到该语句时,系统只是暂时将程序的 执行挂起来,等待用户的进一步指令。PAUSE语句在程序调 试时非常有用。比如一个大的计算任务是由几个小的子计 算任务依次执行来完成的,这就可以在每一个子计算任务 的末尾加上PAUSE语句,一段一段的进行调试。当所有的子 计算任务都调试完成、排除问题之后,再将所有的PAUSE语 句去掉。 第9章 Fortran中的数组 • 数组是Fortran语言中功能最为强大、运用最为灵 活的一种数据结构。数组(ARRAY)在科学和工程 计算中通常用来表示矩阵和向量。同一般的变量 声明相比,数组能够同时保存多个数据。它是一 种使用大规模数据的方法。配合Fortran语言中的 数组操作,可用于对大量不同的数据进行处理。 在存储结构上,数组占用一片连续的存储单元。 程序中通过数组索引来对数组元素、片断进行操 作。 9.1 数组的定义 • 要在程序中使用数组,需要首先在变量声明中进行数组定 义。数组定义规定了数组的维数和大小,以及数组所能保 存的数据类型。在程序中,通过数组引用来对数组、数组 元素或者数组片断进行操作。 • 数组是类型相同、种别一致的一组变量的有序集合。它可 以是整型、实型、双精度型、复型、逻辑型、字符型以及 自定义类型等中的任意一种。组成数组的每一个变量被称 为数组元素,并由唯一的下标来进行标识。数组定义说明 了数组所能保存的数据类型、数组的维数、维的范围和数 组的大小。本节主要介绍Fortran中数组定义的几种方式。 9.1.1 定义形式一 • 第一种数组定义形式的语法格式如下所示。 • 类型说明 [::] 数组名([下标下界:]下标上界 [,…])[,…] • 该定义形式通过类型说明来显式声明数组的数据 类型,并通过下标下界和下标上界来规定数组中 某一维的范围。下标下界和下标上界共同组成了 维说明符。当维说明符省略下标下界时,默认所 在维的下标从1开始。如下代码都是合法的数组定 义。 • REAL :: A(1:2,2:4) • INTEGER B(10) 9.1.2 定义形式二 • 第二种数组定义形式的语法格式如下所示。 • DIMENSION [::] 数组名([下标下界:]下标上界 [,…])[,…] • [类型说明 [::] 数组名[,…]] • 该定义形式通过DIMENSION语句来进行数组的定 义,通过下标下界和下标上界来规定数组中某一 维的范围。在第二行通过类型说明来显式声明数 组的数据类型。当省略类型说明时,采用默认的 “I-N”规则来对数组的数据类型进行定义。如下 代码合法的对数组进行了定义。 • DIMENSION :: A(10), B(2:11) • INTEGER :: A 9.1.3 定义形式三 • 第三种数组定义形式的语法格式如下所示。 • DIMENSION([下标下界:]下标上界[,…]) [::] 数组名[,…] • [类型说明 [::] 数组名[,…]] • 该定义形式通过DIMENSION语句直接说明了数组的 维数和维的范围。这种形式定义的数组全部具有 相同的维数和大小。如下代码表示了如何采用上 述形式进行数组的定义。 • DIMENSION(10, 4:10) :: A, B, N • INTEGER :: A • REAL(8) :: N 9.1.4 定义形式四 • 第四种数组定义形式的语法格式如下所示。 • [类型说明,]DIMENSION [::] 数组名([下标下 界:]下标上界[,…])[,…] • [类型说明,]DIMENSION([下标下界:]下标上界 [,…]) [::] 数组名[,…] • 该定义形式可以说是前三种定义形式的综合形式。 通过在DIMENSION语句前引入类型说明来显式的说 明数组的数据类型。下列代码演示了此种形式的 数组定义。 • REAL, DIMENSION :: I(10), M(10,5) • INTEGER, DIMENSION(10) :: A, C 9.1.5 数组定义的特点 • 上述数组定义的形式中,中括弧内的部分可有可无。数组定义语 句必须出现在所有可执行语句之前。除了上述基本的定义形式 外,在Fortran77中可以使用COMMON语句,在Fortran90中可以用 POINTER语句、ALLOCATABLE语句等对数组定义进行加强。 • 在前面所述的四种定义形式中,定义形式因简洁直观而常见于实 际使用中。此外,如下问题是在实际编程中应该注意的。 • 在前面所述的数组定义中,I(10)、M(10,5)、A、C等称为数组说 明符。在同一个说明语句中有多个数组说明符时,用逗号进行分 隔。 • 数组说明符中的I、M、A、C等是数组名,其取名规则与变量相同 并且不应与程序中的其他变量同名。在同一个程序单元中,一个 数组名只允许定义一次,不能重复定义。例如下面的数组定义是 错误的。 • INTEGER :: A(10), A(10,20) 9.2 数组的引用方式 • 数组经过定义之后,就可以在程序中使用了。在 Fortran77标准中,数组只允许在输入输出语句中 进行整体操作。在其他场合,只能对数组的元素 通过下标索引的方式逐个进行操作。到了 Fortran90标准,这一限制被大大放宽了。数组除 了能够进行整体操作以外,还能对数组中的片断 和数组的整体进行操作。这进一步增强了Fortran 语言在数值处理方面的能力。Fortran中数组的引 用方式可以概括为以下几种: 9.2.1 引用数组元素 • 引用数组元素的语法格式如下所示。 • 数组名(下标,……) • 这种引用方式通过下标索引来对数组中的每一个 元素进行操作。它是Fortran中最为传统的一种引 用方式。采用这种方式进行引用时,下标的值不 能超出数组定义时的下标上下界。目前市面上绝 大部分的Fortran编译器都会提供在编译时进行数 组下标越界检查的功能。而其他计算机语言的编 译器,如C/C++编译器往往不提供这样的功能,需 要程序员自行检查代码中是否存在数组越界的行 为。 9.2.2 引用数组整体 • 引用数组整体的语法格式如下所示。 • 数组名 • 这种引用方式通过数组名来对数组进行整体操作。这种引用方式是 Fortan90中新增的,大大提高了程序编写的灵活性和简单性。我们对前一 个例程TEST0901进行修改,通过整体引用来对数组进行赋值。 • TEST0902.F90 • ! 引用数组整体的范例 • PROGRAM TEST0902 • IMPLICIT NONE •! 变量定义 • REAL :: A, B(5,5) • READ(*, *)A • ! 数组整体引用 • B = A • END PROGRAM TEST0902 9.2.3 引用数组片断 • 引用数组片断的语法格式如下所示。 • 数组名(下标范围,……) • 在这种引用方式中,数组中的元素可以用过数组片断来进行引用。当需要给数组中的不同 片断的元素赋予不同数值时,这种引用方式非常方便。来看下面一个例子。 • TEST0903.F90 • ! 引用数组片断的范例 • PROGRAM TEST0903 • IMPLICIT NONE • ! 变量定义 • REAL :: A1, A2,A3,A4,A5,B(5,5) • READ(*, *)A1,A2,A3,A4,A5 • ! 数组片断的引用 • B(1,1:5) = A1 • B(2,1:5) = A2 • B(3,1:5) = A3 • B(4,1:5) = A4 • B(5,1:5) = A5 • END PROGRAM TEST0903 9.3 数组的存储 • 尽管在Fortran语言中,允许程序员声明维数高达 7维的数组来使用,但是计算机的内存却只是一维 的。所以不管声明的数组有几维,数组在内存中 都是以一维的方式来进行存储。数组中元素在计 算机内的存储顺序同时也被用作输入/输出时确定 其中的元素数据在进行操作时的先后顺序。 9.3.1 数组的存储结构 • Fortran中,一维数组在计算机内存中的存储是最简单的一种情况。 在逻辑结构上,一维数组可以看成是由一系列数组元素组成的一 个单列数据表。数组中每个元素的下标就确定了此元素在数据表 中的位置。下标越小,在数据表中的位置就越靠前。在计算机内 存中,一维数组占据一片连续的存储单元,单个元素在内存中的 位置就是其逻辑结构中的位置。 9.3.2 数组存储结构的应用 • 目前的计算机硬件体系结构决定了在读取大批量数据时,如果这一批 数据都位于临近的内存中时,读取操作会执行得较快。在编写程序 时,如果想要提高执行效率,就应该对数据在计算中的保存方式和读 取方式有一定的了解。只有了解了数据在计算内的存储结构,才能在 编写程序的时候做到有的放矢。这样编写出来的程序在数据的存取效 率上才能较高。 • 需要注意的是,尽管Fortran中的数组是按“列元素优先”的规则进 行存储的,但是C语言中的数组则是按照“行元素优先”的原则进行 存储,并且C语言中数组的下标下界固定是从0开始的。在编写相关的 程序时,应该注意到这一差别。Fortran语言中,使用DO循环进行高 效率数组操作的代码写法可能在C语言中恰恰是最低效的。比如下面 的程序段在Fortran中能够得到较好的执行效率: • DO J = 1, 5 • DO I = 1,3 • Sum = Sum + A(I, J) • ENDDO 9.4 数组的类型 • 根据数组在定义时的特征,比如数组的秩、数组 的形状和每一维的大小,可以将数组划分为好几 种类型。这些不同类型的数组在程序单元中如何 使用?在哪些程序单元中使用?有什么特点?本 节将针对这些问题进行逐一介绍。 9.4.1 显形数组 • 显形(Explicit-shape)数组是Fortran中最简单、 最容易理解的一种数组类型。顾名思义,这种类 型的数组在定义阶段就通过数组定义语句明确的 规定了所有特征,比如数组的秩、数组的维数、 每一维的长度和上下界。通过这种方式定义的数 组具有确定的形状和大小,在程序运行过程中不 允许再对数组的任何特征进行改变。需要注意的 是,在显形数组的维说明中,还允许使用整型变 量或整型表达式来定义维的上下界。这涉及到两 种特殊的显形数组,会在随后的小节中进行介绍。 9.4.2 特殊的显形数组——自动数组 • 自动数组(Automatic Array)是显形数组的一种 特殊形式,这种形式的显形数组只能是过程中的 局部变量。 • 使用自动数组时最好在过程中加以声明,并且数 组中至少有一维的上下界是不确定的整型变量或 整型表达式。在调用过程时,自动数组中不确定 的上下界首先通过整型变量或整型表达式求出。 这样,整型变量或整型表达式的值在过程中发生 的变化,就不会影响到数组中的上下界。 9.4.3 特殊的显形数组——可调数组 • 可调数组(Adjustable Array)也是显形数组的 一种特殊形式,这种类型的显形数组只能是过程 中的一个哑元。 • 可调数组中至少有一维的上下界不是常数,这个 维的上下界只有当过程被调用时才能最终确定。 并且该维的上下界表达式中的整型变量可以是通 过过程传递的哑元,也可以是通过COMMON语句中 传递的整型常量或变量。和自动数组类似,过程 内部对维界参数的赋值不会改变数组中该维的上 下界。 9.4.4 显形数组的不足 • 显形数组是数组应用的基础,其中的自动数组和可调数组能够提供非常灵活的数组应 用。比如自动数组在处理具体数量未知的大笔数组数据时,能够提供相当好的解决方 案。但是有一点需要注意,由于自动数组和可调数组都是通过过程来使用的,因此过 程的一些特点也会影响到这两种数组的使用。比如使用自动数组时,过程在计算机中 的堆栈限制会妨碍可使用的自动数组的大小。这种情况可以通过在执行TEST0906时, 将变量I设置成一个大数(比如100000)来观察到,此时屏幕上的打印信息如下: • Input the value of I: • 100000 • forrtl: severe (170): Program Exception - stack overflow • Image PC Routine Line Source • TEST0307.exe 004011DB Unknown Unknown Unknown • TEST0307.exe 0040110A Unknown Unknown Unknown • TEST0307.exe 0043FCD9 Unknown Unknown Unknown • TEST0307.exe 00428FF9 Unknown Unknown Unknown • kernel32.dll 7C816FD7 Unknown Unknown Unknown • • Incrementally linked image--PC correlation disabled. 9.4.5 假定形状数组 • 假定形状(Assumed-shape)数组是一种在过程中使用的特殊类型 数组,这种类型的数组借助过程中的哑元从实际传递到过程中的 数组获得自身的形状参数。假定形状数组的秩由数组定义中冒号 “:”的个数来决定,其一般形式如下: • 类型声明 数组名([下界]:[,[下界]:]...) • 如果在定义时不指定维的下界值,则默认这一维的下界值为1。维 的上界值等于过程调用中实参数组对应维的长度加上定义中规定 的下界值再减去1。假定形状数组与可调数组的区别非常细微:可 调数组是一种显型数组,在定义时必须指定维的上界(尽管这个 上界可以是变量或表达式);而假定形状数组在定义时是不能指 定维的上界的。来看这样一个代码段: • SUBROUTINE ASSUMEDSHAPE(A) • REAL A(:,:,:) •…… • END SUBROUTINE ASSUMEDSHAPE 9.4.6 假定大小数组 • 假定大小(Assumed-size)数组也是一种在过程 中使用的特殊类型数组的哑元,这种类型的数组 借助过程中的哑元从实际传递到过程中的数组来 获得自身的大小。 • 1.假定大小数组的定义:假定大小数组在声明 时,除了最后一维的上界以外,其它所有特征 (比如数组的秩、维的长度和维的上下界等)都 必须明确指定。声明假定大小数组的一般形式如 下: • 类型说明 数组名([维说明符,][维说明 符,]...[下界:]*) • 2.假定大小数组的应用 9.4.7 延迟形状数组 • 延迟形状(Deferred-shape)数组是Fortran 90/95标准中 才开始引入的特殊类型数组,这种类型的数组在声明时并 不制定数组的维界,具体的维界需要在程序执行过程中才 能确定。延迟形状数组的典型代表就是数组指针和可分配 数组。 • 声明延迟形状数组时,数组的秩由冒号“:”来确定,但每 一维的长度是未知的。数组的维界和形状在程序执行过程 中给延迟形状数组分配存储空间之后才能决定。可分配数 组可以通过ALLOCATABLE语句、DIMENSION语句、TARGET语 句或在类型声明中使用ALLOCATABLE属性来进行说明;而数 组指针则由POINT语句或在类型声明中使用POINTER属性来 进行说明。数组指针的边界和形状通过指针赋值语句指向 目标之后进行确定,或者通过ALLOCATE语句直接进行指针 的空间分配;而可分配数组的边界和形状则只能通过 ALLOCATABEL语句来进行指定。 9.5 数组的动态分配 • 所谓数组的动态分配就是指数组的大小、形状等 特征是在程序运行中动态的确定,而不是在程序 声明段就确定好了的。数组的动态分配能给程序 设计提供更大的灵活性。本节就将介绍有关数组 动态分配的有关内容。 9.5.1 自动数组与可分配数组 • 从存储状态来说,数组可以划分为静态数组和动 态数组两种。如果数组是静态的,那么在编译阶 段就会为数组分配好固定的储存空间,这些存储 空间在程序执行过程中会一直保留的,直到程序 退出时才会被释放。程序运行过程中,静态数组 的大小不会发生改变。静态数组的一个主要缺陷 在于,即使数组已经不再使用,仍然会占据分配 给它的内存空间,这就造成了系统资源的浪费。 如果计算机的内存资源有限,这会使得其他程序 的可用内存资源减少。最严重的情况是可用内存 资源不足,这将导致程序执行错误。 9.5.2 可分配数组的分配与释放 • 在实际的程序中,往往会碰到这样的问题:一些数组的大 小在程序执行之前并不知道具体的大小,只能在程序运行 的过程中才能确定。那么如何解决这类问题呢?一个办法 就是为程序声明一个足够大的数组,大到将数据一股脑全 装进去后还有富裕。但是这又会造成存储空间的浪费,在 过去386、486的时代,浪费宝贵的内存无疑就是犯罪。另 一种办法就是前面提到过的自动数组,这需要用到过程的 概念和良好的程序设计结构。如果碰到有些数组需要作为 全局变量在不同的过程中进行处理的情况,就只好采用开 一个大数组的办法来解决。 • 为了更有效的利用计算机中的内存,Fortran 90/95标准中 正是引入了可分配数组的概念。通过ALLOCATE语句可以动 态的创建可分配数组,使内存和对象可以在程序开始运行 之后才建立起相互联系。 9.5.3 可分配数组的应用实例 • 下面的代码实例演示了可分配数组在程序中的应 用,以加深对可分配数组的理解。 • (详细内容请参照本书) 9.6 数组赋值 • 当数组配置好内存空间后,可以通过赋值语句或 是数组构造器为数组中的元素进行赋值。Fortran 语言中,数组的赋值可以通过赋值语句、DATA语 句和数组构造器这三种手段来进行。 9.6.1 数组赋值语句 • 首先介绍数组赋值语句。数组赋值语句是Fortran 90/95标 准中新增加的数组赋值手段。数组赋值语句的基本形式为: • 数组对象 = value • 其中,数组对象代表数组名或数组片段,value表示数组表 达式或者标量。当value为数组表达式时,必须和数组对象 具有相同的形状(即维数相同、每维长度相同,但上下界 可以不同);当value大小为0或者是长度为0的字符型变量 时,则没有值赋给数组对象;当value为标量时,会把 value处理成与数组对象相同的形状,此时数组对象的每个 元素均等于标量value的值。数组表达式中允许使用“+”、 “-”、“*”、“/”、“**”等内部算术操作符。 9.6.2 数组构造器 • 数组构造器是由括号和斜线对之间的一系列数值组成,其 一般形式为: • 数组名 = (/取值列表/) • 其中,取值列表可以是标量,隐式DO循环或者任意秩的数 组。取值列表中所有数值的类型都应该相同,数值之间以 逗号分隔。如果取值列表中出现了数组,则它的值是按 “列元素优先”的规则来赋给目标数组变量。数组构造器 的标识“(/”和“/)”在书写时要注意,括弧和撇号之间 不能有空格。下面来看一些实例。 • MN = (/1, 3, 5, 7, 9/) ! 标量表示 • AB = (/B(2,1:5),B(3:7,7:9)/) ! 数组表示 • CC = (/(I, I=1,4)/) ! 隐 DO循环 • DE = (/10,A(2:7),(I,I=1,4),7/) ! 混合表示 9.6.3 DATA语句 • DATA语句从Fortran 77时代开始就已经用于数组的赋值,只适用 于数组初值的设置。本小节将介绍DATA语句进行数组赋值的基本 用法。 • 1.DATA语句赋值的特点 • 同前面所述的两种数组赋值语句不同的是,DATA语句允许直接对 数组进行部分赋值,但此时编译器一般会给出警告信息,提示赋 值数量不足。也就是说,在DATA语句中可以不要求数值的数量必 须与被赋值数组的数组元素个数相同。被赋值数组中的数组元素 按照“列元素优先”的原则“抢夺”数据段中的数据,一句话: 先到先得。 • 2.DATA语句的特殊性 • DATA语句是一种特殊的说明语句,与普通的说明语句有很大的不 同。普通的说明语句必须放在说明段中,也就是必须放在可执行 语句之前。但是DATA语句允许出现在程序单元结束语句END前的任 意位置。由于DATA语句是在编译期间就会执行的语句,因此,不 管程序中出现多少条DATA语句,也不管DATA语句出现在什么位 置,同一个变量在程序执行前只允许有一个值。这个值以出现在 程序中的最后一条DATA语句的赋值为准。这就是为什么在本小节 开始说DATA只适用于设置初值的缘故。 9.7 数组的输入输出 • 在Fortran语言中,数组的操作方式非常丰富。其 中,对数组的输入/输出操作即可以使用隐式DO循 环来指定要进行输入/输出的每一维数组元素的起 始位置、终止位置和步长增量,也可以直接给出 要进行输入/输出的数组名、数组元素以及数组片 段。下面将分别进行讲解。 9.7.1 一维数组的输入输出 • 一维数组的输入/输出操作是数组操作中最简单的 情况,也是二维、三维乃至更高维数组输入/输出 操作的基础。只要掌握了一维数组的输入/输出操 作方法,其他高维数组的输入/输出操作就很容易 理解。 • 由于一维数组在计算机内存中进行存储时是线性 排列的,因此输入/输出操作时,数据是按照数组 给出的下标值依次进行输入/输出的。在对一维数 组进行输入/输出操作时,即可以将整个数组作为 输入/输出操作的对象,也可以只输入/输出数组 中的一个元素、一个片断。 9.7.2 二维数组的输入输出 • 二维数组输入/输出操作的基础就是一维数组的输入/输出操作, 两者没有本质上的不同。数组元素输入/输出的顺序是按照前面提 到过的数组在计算机内存中的存储顺序来进行的。 • 由于Fortran语言中数组的存放顺序采用“列元素优先”的原则, 因此在对二维数组进行输入操作是,首先输入的数据被数组中的 第一列元素接收。比如要对一个的数组Array进行输入,输入语句 “READ*,A”按下列顺序将数据读入给数组中的每个元素: • Array(1,1)>Array(2,1)>Array(3,1)>Array(1,2)>Array(2,2)>Ar ray(3,2)>Array(1,3)>Array(2,3)>Array(3,3) • 按“列元素优先”的方式进行存贮的做法与数学上按行进行处理 的习惯不太不一致,在进行输入操作应该引起足够的注意。给数 组中各元素赋值时,应该先输入第一列元素的值,再输入第二列、 第三列、……上元素的值,这样才能确保计算机内接收到的矩阵 是正确的。 9.7.3 其它高维数组的输入输出 • 更高维数组的输入输出方式也是以一维数组的输入输出为 基础的。在输入时,总是最低维上的下标值变化最快。例 如一个2X2X2的三维数组Mx,在使用数组名进行系统默认的 输入操作时,读入数据的先后次序如下: • Mx(1,1,1)>Mx(2,1,1)>Mx(1,2,1)>Mx(2,2,1)>Mx(1,1,2)>M x(2,1,2)>Mx(1,2,2)>Mx(2,2,2) • 如果想要实现以“行元素优先”为规则进行输入,则需要 在输入语句中交换隐式DO循环内外层循环变量的方法来实 现。比如要实现数组Mx的“行元素优先”原则输入,则可 以使用下面的输入语句来实现: • READ *, (((Array(I, J, K), K=1,2), J=1,2), I=1,2) 9.8 数组的运算 • 数组之间的运算是Fortran语言独步数值计算领域的一大绝活。当其他计 算机语言还在使用DO循环、for循环或是其他形式的循环来为数组赋初值 时,Fortran早已通过数组的整体赋值完成了这一工作,并开始了对数据 的处理。当其他计算机语言好容易把一大票数据读入到计算机之后,又开 始用DO循环、for循环或是其他形式的循环将刚读入的两个巨型数组相加、 相减以得到结果时,Fortran早已通过数组的算术运算完成了这些工作开 始输出结果了。 • 数组间的运算是Fortran 90/95标准中有利于数值计算的新手段。在 Fortran 77时代,数组间的运算也只能通过循环的手段来实现。在新的 Fortran标准中,允许把整个数组或数组的一部分(即数组片段)作为一 个独立的对象进行相关的运算。前提是进行数组运算的两个独立对象应该 是大小相同、形状一致。在Fortran语言中,允许将数组或数组片断作为 运算对象的运算符包括: • 所有的算术运算符(包括“+”、“-”、“*”、“/”、“**”五种) • 所有的逻辑运算符(包括“.AND.”、“.OR.”、“.NOT.”、“.NEQV.”、 “.EQV. ”五种) • 所有的关系运算符(包括“.LT.(<)”、“.LE.(<=)”、“.EQ.(=)”、 “.NE.(/=)”“.GT.(>)”、“.GE.(>=)”六种) 9.8.1 算术运算 • 在数组的算术运算表达式中,允许出现的对象包 括数组、数组片断、数组元素和标量。不同对象 之间的运算有自身的特定规则和行为模式。 • 1.算术运算的规则 • 如果算术运算符两侧的运算对象都是数组或数组 片断,则要求两个数组或数组片断应该是大小相 同、形状一致的。运算在两个数组或数组片断对 应位置上的数组元素间进行,保存结果的数组或 数组片断也应该和运算对象大小相同、形状一致。 • 2.算术运算的行为模式 9.8.2 逻辑运算 • 在数组的逻辑运算表达式中,允许出现的对象包括数组、 数组片断、数组元素和标量。 • 如果逻辑运算符两侧的运算对象都是数组或数组片断,则 要求两个数组或数组片断应该是大小相同、形状一致的。 运算在两个数组或数组片断对应位置上的数组元素间进 行,保存结果的数组或数组片断也应该和运算对象大小相 同、形状一致。 • 如果逻辑运算符两侧的运算对象一个是数组或数组片断, 另一个是数组元素或标量,则运算在数组或数组片断与数 组元素或标量之间进行。保存结果的数组或数组片断应该 与参与运算的数组或数组片断大小相同、形状一致。 9.8.3 关系运算 • 数组之间的关系运算在运算对象、运算规律上与 数组的算术运算和逻辑运算没什么两样。假设数 组A用于保存运算结果,数组B是双目关系运算中 的一个运算对象,C是另一个运算对象(可以是数 组,也可以是可用于关系运算的其他标量),符 号“&”表示任意一种关系运算符。则语句 “A=B&C”类似于执行表所示的操作: 数组的关系运算操作 当C为数组时 当C为标量时 DO J = 1, N DO I = 1, M A(I, J) = B(I, J) & C(I, J) ENDDO ENDDO DO J = 1, N DO I = 1, M A(I, J) = B(I, J) & C ENDDO ENDDO 9.9 常用内在函数 • Fortran语言中的内在函数通常都可以接受数组作 为参数来进行运算,此外还有一些专用的函数适 用于处理数组所特有的运算。本节就将介绍这些 内在函数在数组领域的应用。 9.9.1 内部基本函数 • 在Fortran语言的数组表达式中,允许将数组作为 内部基本函数的参数。此时,内部基本函数的函 数值就是一个同参数数组形状相同的数组,它的 每个位置上的元素值就是被操作数组对应位置上 的数组元素取该基本函数所得的值。例如数组A和 B都是形状相同的一维数组,则语句B=SQRT(A)的 执行结果可以表示如下: 9.9.2 矩阵乘积函数 • 该函数的作用是执行数值型或逻辑型数组A与B的矩阵乘法。 函数的原型为: • C = MATMUL(A, B) • 使用时,数组A和B必须是秩为1或2(也就是一维或二维) 的数值型或逻辑型的有值数组,且数组A和B中至少有一个 的秩为2。传入矩阵乘积函数的数组A与B的类型必须相同。 • 数组A与B的矩阵乘积规则和结果与数学上的矩阵乘法定义 一致,也就是说数组A的最后一维的长度必须和数组B的第 一维的长度相同。结果数组C的秩和形状取决于参数数组的 秩和形状: • 如果A的形状为(n,m),B的形状为(m,k),则结果数组C的秩 为2,形状为(n,k)。 • 如果A的形状为(m),B的形状为(m,k),则结果数组C的秩为 1,形状为(k)。 • 如果A的形状为(n,m),B的形状为(m),则结果数组C的秩为 1,形状为(n)。 9.9.3 向量点乘函数 • 该函数的作用是执行数值型或逻辑型数组A与B的点积乘法。 函数的原型为: • C = DOT_PRODUCT(A,B) • 使用时,数组A和B必须是秩为1(即数学上所说的向量,也 即一维数组)的数值型或逻辑型的有值数组,且数组A与B 的类型必须相同。 • 一维数组A与B点乘的结果是标量,函数的点乘规则和结果 值与数学上的定义相同。如果一维数组A和B中有一个的长 度为0,且数组为数值型数组,则结果为0;如果数组为逻 辑型数组,则结果为.FALSE.。例如 DOT_PRODUCT((/1,2,3/),(/3,4,5/))的结果为26,计算过 程为(1 x 3)+(2 x 4)+(3 x 5))=26。 9.9.4 元素求和函数 • 元素求和函数属于数组规约函数中的一种。 • 数组规约函数是一组功能类似的数组函数的统称。 这组函数的主要作用就是沿着数组中的某一维, 对在屏蔽表达式中值为.TRUE.的所有数组元素进 行某种操作。这组函数包括SUM、PRODUCT、 MAXVAL、MINVAL、COUNT、ANY和ALL函数。本节将 主要介绍其中的两种。 • 元素求和函数的主要作用是沿着数组中的某一 维,对在屏蔽表达式中值为.TRUE.的所有元素求 和。函数的原型为: • C = SUM(A[,DIM][,MASK]) 9.9.5 元素连乘求积函数 • 该函数的主要作用是沿着数组中的某一维,对在屏蔽表达 式中值为.TRUE.的所有数组元素求连乘积。函数的原型为: • C = PRODUCT(A[,DIM][,MASK]) • 其中的注意事项和说明同元素求和函数。下面是一些实例。 • 比如数组A=(/2,4,6/),则PRODUCT(A)的值是48;又如 PRODUCT(B,MASK=B<0.0)表示对数组B中的所有小于0的元素 求连乘积。同元素求和函数一样,结果C是数组还是标量也 取决于函数中维的定义和被求积数组A的大小和形状。比如 数组A(2,3)=(/2,3,4,5,6,7/),则PRODUCT(A,DIM=1)的值 是[(2×3=6),(4×5=20),(6×7=42)];SUM(A,DIM=2)的值 是[(2×4×6=48),(3×5×7=105)]。 9.9.6 数组大小查询函数 • 该函数是数组查询函数中的一种。数组查询函数一组功能 类似于数组函数的统称。这组函数包括:SIZE、SHAPE、 ALLOCATED、LBOUND和UBOUND函数。这里只介绍其中的两种: 数组大小查询函数和数组形状查询函数。数组大小查询函 数的作用是求数组沿着某一维的长度或者数组元素的总数 目。函数原型为: • C = SIZE(A[,DIM]) • 其中A是被查询数组,可以是假定大小数组,但不能是未定 义的指针数组或未分配空间的可分配数组。当DIM等于1 时,表示查询数组有几行;当DIM等于2时,表示查询数组 有几列;当DIM被省略时,表示查询数组有多大(即有多少 个元素)。 9.9.7 数组形状查询函数 • 该函数的功能就是求数组或标量的形状。函数的 原型为: • C = SHAPE(A) • 其中,A表示被查询对象,可以是标量或数组,但 不能是假定大小数组、未定义的指针或未分配空 间的可分配数组;C保存查询结果,是一个一维整 型数组。 • 比如SHAPE(2)将返回一个零长度一维数组;如果 对数组B(-2:5,9:10)进行查询,则SHAPE(B)将返 回一维数组(8,2)。 9.9.8 数组合并函数 • 数组合并函数是数组构造函数中的一种。数组构造函数也 是一系列功能相似的函数的总称,它们用于从已有数组的 元素构造出新数组。这组函数包括:MERGE、PACK,UNPACK 和SPREAD函数。 • 数组合并函数的只要用途就是在屏蔽表达式的控制下,对 两个独立数组进行合并操作。该函数的原型为: • C = MERGE(TSOURCE,FSOURCE,MASK) • 其中,TSOURCE可以是任意类型的数组或标量,FSOURCE是 必须与TSOURCE具有相同的类型和类型参数的数组或标量。 屏蔽表达式MASK必须是逻辑型数组;若MASK值为真,则结 果是TSOURCE,若MASK值为假,则结果是FSOURCE。 9.9.9 数组压缩函数: • 该函数的作用就是在屏蔽表达式的控制下,将数组压缩成向量数 组。数组的原型为: • C = PACK(A,MASK[,VECTOR]) • A表示被压缩对象,可是任意类型的数组;屏蔽表达式MASK必须是 逻辑型数组,并且与数组A相容(也就是形状相同);VECTOR是可 选参数,必须为向量数组,并且与数组A具有相同的类型和类型参 数。 • 结果C是秩为1的数组(就是一维数组),其类型和类型参数与数 组A相同。若VECTOR存在,则结果C的大小等于VECTOR的大小,否 则其大小是使屏蔽表达式MASK值为真的元素的个数;若屏蔽表达 式MASK为标量并且值为真,则结果C的大小与数组A相同。结果C中 的值按数组中的元素位置排序,数组A中的第i个元素对应于屏蔽 表达式MASK的第i个为真元素。若VECTOR存在,且大小大n于符合 条件的数组A中的元素个数t,则结果C中第i个元素值为 VECTOR(i),i=t+1,…,n。 9.9.10 数组形状扩展和重构形函数 • 这是一个由两个函数组成的函数族,包括SPREAD 函数与RESHAPE函数,用于完成数组形状重构和扩 展的任务。 • SPREAD函数的主要功能就是将数组沿着某一维的 方向拷贝规定次数后扩展成一个新的数组。函数 的原型为: • C = SPREAD(A,DIM,NCOPIES) • 其中,A为被拷贝对象,可以是标量或任意类型的 数组。当DIM等于1时,表示沿着第一维下标变化 的方向扩展,也称为向下扩展;当DIM等于2时, 表示沿着第二维下标变化方向扩展,也称为向右 扩展。NCOPIES用于指定拷贝的次数。 9.9.11 数组转置函数 • 数组转置函数是数组运算函数中的一种。数组运 算函数是数组函数中同矩阵运算相关的一组函数 的总称,这组函数包括:TRANSPOSE、EOSHIFT和 CSHIFT三个函数。数组转置函数的用途就是对秩 为2的数组(就是二维数组)进行转置操作。函数 的原型为: • C = TRANSPOSE(MATRIX) • 其中,数组MATRIX必须是一个二维数组。转置后 的结果数组C的形状正好与数组MATRIX的形状相反。 也就是说MATRIX(n,m)转置后的结果为C(m,n)。 9.9.12 去端移动函数 • 该函数的作用是对秩为1的数组作去端移位处理, 或沿着某一维对秩大于1的数组在所有秩为1的完 整数组片段上作去端移位处理。函数的原型为: • C = EOSHIFT(A,SHIFT[,BOUNDARY][,DIM]) • 其中,A为被进行去端移位处理的数组。SHIFT表 示移动的位数,必须为整数;当SHIFT为正时,表 示去端左移,当SHIFT为负时,表示去端右移。在 数组或数组片段的一端被移出的元素被丢弃,并 在另一端移入相同数量的BOUNDARY的值。DIM表示 要进行去端移位处理的数组的维,默认为1。不同 的片段可以有不同的BOUNDARY值,并可在不同的 方向上移动不同的位数。 9.9.13 循环替换函数 • 该函数的作用是将秩为1的数组的所有元素或高维 数组的指定维上的元素进行循环移动。在一端上 移走的元素被插到另一端。函数的原型为: • C = CSHIFT(A,SHIFT[,DIM]) • 其中,A为被操作数组。SHIFT为正值时被移向左 端,负值时则移向右端。DIM可以指定要进行操作 的数组的维,默认值为1。 • 比如数组A=[1,2,3,4,5,6],则CSHIFT(A,SHIFT=2) 的结果是[3,4,5,6,1,2];而CSHIFT(A,SHIFT=-2) 的结果则[5,6,1,2,3,4]。 9.9.14 最大值元素定位函数 • 该函数是两个数组定位函数之一,另一个不用说也知道是 最小值元素定位函数(MINLOC)。函数的原型为: • C = MAXLOC(A[,DIM][,MASK]) • 函数根据屏蔽表达式MASK的真值条件确定数组A中的所有元 素或沿某一维DIM所有元素中第一个最大值元素出现的位置。 结果C的形式取决于数组A的秩:当秩为1时,C为标量;当 秩不为1时,C为一维数组。 • 例如MAXLOC(/1,8,8,7/)的值为[2]。 • 再如数组A(3,4)的形式为: ⎥ ⎥ ⎥ ⎦ ⎤ ⎢ ⎢ ⎢ ⎣ ⎡ −−− − − 5541 6213 2304 9.10 Fortran90/95的数组操作语句 • Fortran 90/95中提供了许多新的数组操作语句, 例如FORALL、WHERE语句等。这些语句大大提高了 Fortran语言中的数组操作特性,使其在数值计算 领域的优势中得到进一步加强。本小节将介绍这 两种新的数组操作语句的特性和用法。 9.10.1 WHERE语句和WHERE构造 • 在Fortran 90/95中提供了一种新的屏蔽数组操作语句 WHERE,该语句可用于从数组中提取出部分内容进行设置。 实质上,WHERE语句是一种带判断条件的数组操作语句,也 就是说该语句只对那些符合条件要求的数组元素进行操作。 在使用上,可以分为语句形式和构造形式两种。 • 1.WHERE语句 • WHERE语句的一般形式为: • WHERE(屏蔽表达式) 赋值语句 • 2.WHERE构造 • 除了上面这种语句声明形式的用法外,还可以使用WHERE构 造。 • 3.WHERE构造的嵌套 • 同IF构造相似,WHERE构造也允许进行嵌套。 9.10.2 FORALL语句 • FORALL语句也是Fortran 90/95标准中新增的数组操作语句,是数 组屏蔽赋值功能(WHERE语句和WHERE构造)的一对一元素的推广。 从对数组的作用形式来看,该语句同隐式DO循环操作数组的过程 类似,但在功能上更为强大。 • 1.FORALL语句 • FORALL语句的一般形式为: • FORALL(循环三元下标[,循环三元下标]…[,屏蔽表达式]) 赋值语 句 • 2.FORALL构造 • 除了前面介绍的FORALL语句外,FORALL也能像IF构造和WHERE构造 一样以构造的形式进行实用。FORALL构造的一般形式为: • [构造名:] FORALL(循环三元下标[,循环三元下标]…[,屏蔽表达 式]) • [块] • END FORALL [构造名] 第10章 Fortran程序单元 • 一个Fortran程序中通常不是只由一个主程序组 成,而是由几个按某种方式划分的不同程序单元 来共同组成。尽管Fortran程序中允许只有主程序 而没有子程序,但绝不允许只有子程序而没有主 程序。在Fortran中,程序的执行总是从主程序开 始的。 • Fortran中的程序单元可以大体划分为主程序、子 程序两种,其中子程序又可以进一步划分为函数 子程序、子例行子程序和数据块子程序。数据块 子程序通常用于实现变量的初始化赋值,函数子 程序和子例行子程序在用途上基本是一致的,但 是也有许多不同之处。本章将详细介绍Fortran中 的程序单元和它们的基本用法。 10.1 主程序 • 顾名思义,主程序是一个实际程序中的主体,其 他类型的程序单元都是以某种方式来辅助主程序 的执行。在Fortran语言中,一个程序的执行始终 是从主程序的第一条可执行语句开始的,所以每 个完整的Fortran程序都必须有且只允许有一个主 程序。主程序定义的一般语法形式如下: • [PROGRAM [程序名]] • [说明部分] • [可执行部分] • [CONTAINS • 内部过程] • END [PROGRAM[程序名]] 10.2 语句函数 • 语句函数通过一句代码定义来实现某种特定的处 理功能,它是Fortran 77时代的遗留产物。严格 来说,语句函数不属于程序单元的范畴。但是在 实际应用中,语句函数以其灵活的应用、小巧的 结构在程序中发挥着重要的作用。 • 在实际的编程过程中,程序员往往会遇到这种情 况:一些简单的函数会在一个程序单元中的不同 地方重复用到,而Fortran系统并不提供这种内部 函数;如果采用函数子程序的形式来描述这些简 单的函数又会觉得没有这种必要。例如,要求解 函数的值,将其编写成函数子程序可以顺利解决。 但是,Fortran语言提供了一种更为简单的手段— —语句函数。 10.2.1 语句函数的定义 • 在Fortran中定义一个语句函数的形式如下: • fun ([d-arg [, d-arg] ...]) = expr • 1.Fun 2.d-arg 3.Expr 4.语句函数示例 5.需要注意的问题: 在使用语句函数进行编程时,下面一些问题是需要引起注意的: • 语句函数通常在函数比较简单,能够用一条语句(包括换行)就能进行定 义时才使用; • 语句函数是一种非执行语句,需要放置在所有可执行语句之前和相关的类 型说明语句之后; • 语句函数的作用范围仅限于定义它的程序单元之内,不允许跨程序单元进 行语句函数的调用; • 语句函数不能作为子程序调用时的实参,也不允许在EXTERNAL语句中出 现; • 语句函数中出现的虚参必须是变量名,不能是常量、表达式或是数组元 素; • 语句函数通过表达式得到的函数值的类型必须与函数名的类型一致。 10.2.2 语句函数的引用 • 语句函数在完成定义后,就可以在程序单元中进 行引用了。实际上,在前一小节的例程中已经演 示了语句函数引用的一般方式。本小节将对语句 函数的引用方式进行具体的说明。 • 语句函数的引用方式与Fortran中内部函数的引用 方式完全一致,就是用程序中定义的实参替换掉 语句函数定义中的虚参。实参必须是与虚参类型 相同的常量、变量或表达式。 10.3 函数子程序 • 函数子程序和子例行子程序是子程序的两种常用 基本形式。它们的共同特征就是作为数据处理过 程的集合。但是这两种子程序也不完全相同,函 数子程序会返回一个函数值,且通常不会改变哑 元的数值。因此,函数子程序更像是数学上的一 个函数。而子例行子程序通常用于完成一项更为 复杂的任务,通过哑元或者其他手段返回几个结 果,哑元的数值通常会在程序的执行过程中改变。 10.3.1 定义函数子程序 • 下面给出函数子程序的一般形式为: • [prefix] FUNCTION name ([d-arg-list]) [RESULT (r-name)] • ... • END [FUNCTION name] • 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。 • FUNCTION HEX(n) • IMPLICIT NONE • • 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= ' ' • • DO j=8,1,-1 • nn = n/16 • HEX(j:j) = H(n - 16*nn) • IF(nn == 0) EXIT • n = nn • END DO • • END FUNCTION 10.3.4 函数子程序示例——分形 • 在数学上有一个特殊的分支——分形(fractal),所谓分形是 Mandelbrot将自然界的一些特殊复杂图形(如海岸线、树叶外形、 雪花结晶类型等)进行数学理想化后提出的一种概念,其核心思 想是图形的任意细小部分都与图形的整体具有自相似性,这种图 形的维数不是整数,而是分数维。分形的一个典型例子就是Koch 曲线,它具有雪花的外形,可以通过对一段直线反复进行某一简 单的操作而得到。把这个过程用数学语言来描述,就是在复空间 内定义的一种简单迭代过程,它是一个图形的缩小映射,从而产 生自相似曲线。 10.4 子例行子程序 • 同函数子程序相比,子例行子程序通常用于完成 更为复杂的任务。子例行子程序接受外界传入的 参数并对其进行处理,子例行程序名不会用来返 回处理结果。形象一点来说,函数子程序像检验 机,它不改变参数的值但会告诉外界一个检测结 果;而子例行子程序更像一个加工机器,外界来 的参数经过它的加工会以新的形象出现。本节主 要介绍子例行子程序的相关知识。 10.4.1 定义子例行子程序 • 子例行子程序同函数子程序非常相似,但是子例 行子程序不会有返回值。这种形式的子程序是以 SUBROURTINE语句开始,END语句结束的过程。其 一般语法形式如下: • [前缀] SUBROUTINE子程序名 [([哑元列表])] •…… • END [SUBROUTINE[子程序名]] • 1.哑元列表 • 2.前缀 • 3.子程序名 • 4.END语句 10.4.2 子例行子程序示例 • 下面直接来看一段例子,这段代码依次读入三个 实数,并按它们的大小重新开始排序。 • 程序的执行结果如下: • 请输入三个实数: • 1.345 2.71828 2.71827 • 三个实数的先后次序如下: • 2.718280 2.718270 1.345000 • 调用子例行子程序时的实元必须是与哑元类型相 同的变量、数组、数组元素和常数。当用CALL语 句进行调用时,哑元和实元才按哑元列表中的顺 序一一对应,取得同一数值。 10.5 子程序的多入口点和多折返点 • 尽管子程序中不允许直接定义其他的子程序,但 是在Fortran 77时代,可以通过特殊的方式在同 一个子程序中定义多个不同的过程入口。通过调 用不同的过程定义来实现调用同一个子程序中的 不同执行段。除了提供多入口点外,Fortran 77 时代也提供特殊的多折返点来实现特定条件的子 程序调用返回方式。 10.5.1 ENTRY语句与多入口点 • 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.6 Fortran 90/95中的特殊子程序类型 • 在Fortran 90/95标准中,除了继续对前述的一般 子程序类型提供支持外,还新增了三种特殊的子 程序类型。这三种子程序类型就是前述章节中曾 经提到过的RECURSIVE、PURE和ELEMENTAL三种属 性。RECURSIVE属性允许过程进行自身调用,也就 是常说的递归调用;PURE和ELEMENTAL属性都用于 数组的并行处理。 10.6.1 RECURSIVE属性 • 在Fortran 90/95标准之前,Fortran中的子程序 是不允许进行自身调用的。在新标准中,Fortran 子程序开始允许进行自身调用,也就是经常在编 程中听到的“递归”。能够进行递归调用的一个 前提条件就是递归过程在被调用时,其中的局部 变量会使用不同的内存地址,以便在完成递归后 能够依次统计不同内存地址上的结果。 • 1.递归函数子程序 • 2.递归子例行子程序 10.6.2 PURE属性 • 在函数子程序或是子例行子程序的定义语句前添加PURE语句,将 使子程序具有PURE属性。一般来说,并不需要使用这种属性,它 通常适用于并行计算并在使用上有较多的限制。 • 具有PURE属性的子程序,其参数必须是只读的,即INTENT(IN)。 • 具有PURE属性的子程序,其参数都必须有赋值属性。 • 具有PURE属性的子程序,其中的变量不允许具有SAVE属性。 • 具有PURE属性的子程序,其包含的内部过程也必须具有PURE属性。 • 具有PURE属性的子程序,不能够使用STOP以及输入输出相关语 句,如READ、WRITE等。 • 具有PURE属性的子程序,只能够读取而不能改变全局变量的值。 10.6.3 ELEMENTAL属性 • ELEMENTAL属性与PURE属性非常相似,只不过它是 一个针对数组的应用。在具有ELEMENTAL属性的过 程中,不允许出现数组参数。该属性主要用于配 合Fortran 90/95中对于数组的整体操作。 • 程序的执行结果如下: • 1.000000 3.000000 5.000000 7.000000 9.000000 • 0.5403023 -1.714717 0.6342880 1.994638 -2.733391 10.7 数据块程序单元 • 由于COMMON语句中的变量不能够在子程序或主程序中通过 DATA语句来直接设置初始值,需要在一个统一的程序单元 中进行数据的初始化工作。这种统一的程序单元就是数据 块程序单元。 • 数据块子程序单元是一种为有名公用块中的变量定义初始 值的程序单元。它只允许包含变量声明和变量初始值,不 可以包含可执行语句。数据块程序单元是一种落后的程序 设计手段,在新的Fortran 90/95标准中已经有新的模块程 序单元可以完全提供数据块的所有功能。但是一些早期的 大型Fortran程序往往会使用这种程序单元来进行变量的初 始化工作,因此有必要对这种过时的语法进行简单的介绍。 10.7.1 定义数据块子程序 • 数据块子程序的一般形式如下: • BLOCK DATA [块数据名] • [说明部分] • END [BLOCK DATA [块数据名]] • 数据块子程序中的变量一般通过DATA语句来进行 初始化。公共块中命名的变量只能在数据块子程 序单元或某个过程中初始化一次。更好的编程方 法是使用模块程序单元而不是数据块单元来进行 全局变量的声明和变量的初始化工作。 10.7.2 使用数据块子程序 • 数据块子程序在整个程序结构中是一个独立的单 元,不能出现在其他的程序单元之中。同时,数 据块子程序也不需要进行显示的引用,编译程序 会在编译阶段根据数据块子程序的定义为其中的 变量做好赋初值的工作。 10.7.3 数据块子程序示例 • 下面来看一段数据块程序单元的代码实例。这段 代码没有什么实际的用途,主要用于说明数据块 在实际编程中如何应用。 • 程序的运行结果非常简单,如下所示: • This is a demo of BLOCK DATA • 1.000000 -34.00000 0.7800000 3.141593 • 1.00000000000000 2.00000000000000 2.00000000000000 • 2.00000000000000 1.00000000000000 • -1 -2 -2 -2 -1 10.8 子程序的参数 • 函数子程序和子例行子程序在有些时候都会涉及 到大量外部传递的参数。这些参数在传递过程中 的行为如何?有些什么特性?在过程中如何发挥 作用?这就是本节将要介绍的基本内容。 10.8.1 参数传递规则 • 子程序中的哑元在与实元进行哑实结合时的一条 基本规则就是对应位置上的数据要类型正确。参 数类型如果不一致,则很可能发生不可预料的结 果。由于Fortran语言在进行参数传递时采用的是 传地址的方式,即传递变量所占的第一个地址。 进行参数传递的哑元和实元会用自身的数据类型 规则来解读同一片存储单元,一旦数据类型不一 致就很容易发生解读错误的问题。 • 1.参数传递错误示例 • 2.类型不匹配解决方法 10.8.2 子程序的接口 • 在前一节的最后一个例程中,介绍了一种新的程 序单元——接口。通过ITERFACE语句可以向调用 程序单元说明过程的某些信息。 • 1.显示接口 • 2.隐式接口 • 3.接口块 • 4.过程接口块 10.8.3 接口块使用情况 • 在实际编程中,接口块并不是每个主调程序都具有的。如果仅使用Fortran 77语言编 写子程序,则无需在主调程序单元中编写接口块;但是如果使用Fortran 90/95中提 供的一些现代化手段来编写程序,通常需要在主调程序单元中写入调用程序的接口 块,否则在编译过程中很容易出错。此外,Fortran 90/95中不提倡使用COMMON语句 进行程序单元间的数据传递和共享,该语句的功能已经由模块中的接口块代替。确切 一点,如果遇到下列情况时,在主调程序中必须声明被调过程的接口块。 • (1)如果外部过程具有以下特征: • 过程的哑元有可选择属性。 • 过程的哑元是假定形数组、指针变量、目标变量。 • 过程的结果是数组或指针。 • 对于字符型函数过程的结果,其长度不是常数,也非假定长度。 • (2)如果调用过程时出现下列情况: • 使用了变元关键字(如范例TEST1009)。 • 使用类属名来进行调用。 • 使用超载赋值号(对于子程序)。 • 使用超载操作符(对于函数)。 • 在要求纯过程的上下文中。 • (3)如果过程前缀关键词是ELEMENTAL。 10.8.4 INTENT属性应用 • 在Fortran语言中,虚实结合是在不同程序单元之间进行数值传递 的主要手段。例如,主程序中实元A与子程序中的哑元X结合,就 是将实元A在内存中的地址传递给哑元X,也就是将主程序中A的值 传递给子程序中的X,该值可供子程序运算;反之,如果子程序中 的变量Y在子程序执行完后有值M,则Y与实元R结合后会使主调程 序单元中的实元变量R的值也变成M。 • 在Fortran 77时代,在编写程序时无法确切地说明过程中哑元的 目的。过程中的哑元到底是用来将数据传入到过程中的,还是用 来将数据传出到主调程序单元中,或者是两种功能都兼而有之。 这个概念是含糊的。在进行调用时只能由程序员自行记住过程中 各个哑元的性质。进入到Fortran 90/95时代,为了避免当过程内 部变量的值发生变化后返回到主调程序单元时可能造成的混淆情 况,在过程的变量类型定义中,可以为哑元指定INTENT属性(字 面信息就是意图属性)。哑元按照其在参数传递过程中的作用可 以分为输入输出两用、仅用于输入和仅用于输出。 10.8.5 关键字变元 • 关键字变元其实已经在前面提到过了,这里单独进行更详 细一点的讲解。一般来说,哑实结合必须遵循三个一致的 原则,否则会出现错误。所谓三个一致,是指:哑元与实 元的位置一致;哑元与实元的个数一致;哑元与实元的类 型一致。 • 上述要求需要程序员记住每个哑元的名称及位置,在书写 或阅读过程中的实元表时要对其中每个表达式追溯到它原 来的哑元是什么,非常不方便。针对这一问题,Fortran 90/95中通过三种方法来放宽这三个一致的原则: • 用关键字变元放宽位置一致; • 用可选择变元放宽个数一致; • 用类属过程放宽类型一致。 10.8.6 可选择变元与OPTIONAL属性 • 在调用的三个一致原则中,实元与哑元个数一致 是另一个比较严格的要求。但在某些过程中,虽 然哑元列表中列出了好几个哑元,但在实际调用 时不一定每次都需要全部用到。对于这种情况, Fortran 90/95标准中允许只对哑元表中的部分哑 元进行哑实结合,另一部分哑元则按需要进行有 选择的结合,这部分哑元又被称为可选择变元。 例如Fortran语言中的内在数组函数SUM,它的完 整函数及哑元表如下: • SUM(ARRAY,DIM,MASK) 10.8.7 哑元改名 • 广泛的通用性是过程的一大优点。一旦针对某个通用处理 操作的过程被编好,求解具体问题的主程序就可以对它进 行调用。但是在应用于不同的目的时,具体问题的物理名 称可能是不同的。为了加强程序的可读性与可维护性,在 不同的场合使用某一个过程时,需要将哑元名称改为与该 领域械奈锢砻 埔恢隆TFortran 90/95中,允许改变过程 中变元的名称。变元名称的改变是在接口块中进行的,所 以在主调程序中需要写出相应的接口块。 • 例如上面求多边形边长的子程序,如果调用时想要将表示 边长的哑元名A、B、C和D改为物理意义明确的名称Upper、 Down、Left和Right,只需在主调程序中编写相应的接口 块,在接口块的哑元表中使用新的哑元名称就可以了。 10.8.8 INTRINSIC属性 • 与EXTERNAL语句或属性说明的实元是外部过程相 对应,INTRINSIC语句或属性用来说明实元实际上 是内在过程。说明一个对象名具有INTRINSIC属性 可以有两种方法,一种就是在类型说明语句中加 入INTRINSIC属性,具体的形式如下: • 类型说明语句, INTRINSIC :: 内在函数名[,内在 函数名]… • 另一种方法是直接使用INTRINSIC语句进行说明, 具体的形式如下: • INTRINSIC :: 内在过程名[,内在过程名]… 10.8.9 数组作为参数 • 除了将单独的变量作为过程的参数以外,还可以将数组作 为过程的参数进行传递。由于数组在内存中会占用一片连 续的存储单元,因此在传递数组参数时,实际上是传递数 组元素中的某一个内存地址。 • 1.数组传递示例1 • 掌握上述原则可以在程序设计中加以巧妙的应用。 • 2.数组传递示例2 • 3.字符串变量传递 • 字符串变量在作为实元进行参数传递时,可以像数组一样 不必特别声明字符串的长度。在进行传递时,也是将字符 串中第一个字符在内存中的物理地址传递到过程之中。 (详细内容请参照本书) 10.8.10 过程作为参数 • 在Fortran语言中,能够进行哑实结合的数据除了 变量、数组、数组元素、字符等之外,还允许将 函数子程序名或子例行子程序作为参数来进行传 递。 • 1.内部函数作为参数进行传递 • 2.外部函数作为参数传递 • 3.子例行子程序作为参数传递 10.9 局部变量和SAVE属性 • 在过程中除了通过哑实结合方式传递进来的参数 外,还有其他一些在过程中需要使用的变量。这 些变量在过程中具有的特性、作用范围就是本节 将要介绍的内容。 10.9.1 变量的作用范围 • 函数子程序或是子例行子程序中的变量(不包含 通过哑实结合方式传进来的参数)都有自身的 “生存周期”。它们只能在子程序执行的过程中 保持活力,当子程序执行完成后,这些变量就随 着子程序的退出而消亡,它们本身保存的数据通 常也会随之而消失。 • 正因为上述原因,仅在子程序执行过程中有效的 变量也被称为局部变量,它们的“生命周期”又 被称为局部变量的作用范围。 10.9.2 SAVE属性 • 前一小节提到了,在过程中,局部变量取值在过程被调用 结束后有可能变为不确定的,因此当过程被再次调用时, 局部变量的取值在不同编译器下可能是不同的。为了避免 这种情况的出现,在Fortran 77中可以使用SAVE语句,在 Fortran 90/95中则可以在变量的类型声明中为变量声明具 有SAVE属性。在Fortran中说明一个局部变量在程序运行后 保留原来的值可以通过两种方法来实现,一种就是在类型 声明中添加SAVE属性,形式如下: • 类型说明语句, SAVE [,其它属性] :: 变量名表 • 或者直接使用SAVE语句进行说明,形式如下: • SAVE [变量名表] 第11章 高级输出与输入 • 在第3章中,介绍了简单的输入输出操作(也称为 表控输入输出、直接列表输入输出)语句READ、 WRITE和PRINT。这些输入输出操作语句简单易学、 使用方便。但是如果想要使输入输出的数据更为 美观、易读易用或是想要实现一些特殊的效果, 这些简单输入输出语句就显得力不从心了。在本 章中,将介绍同高级输入输出相关的语句设置、 不同种类的格式编辑符等内容。通过本章的介 绍,就可以使程序在输入输出这方面显得更加专 业。 11.1 输入输出语句的格式化设置 • 在第3章中,提到过简单输入输出语句同高级输入输出语句在形式 上没有太大差别。高级输入输出语句之所以高级,主要因为其在 于句说明中添加了丰富的格式控制说明项。正是这些格式控制说 明项使输入输出语句在进行数据的输入输出操作时显得丰富多彩。 本节将介绍同输入输出语句相关的一些详细设置。 • 在第3章中,已经介绍了三种常用的简单输入输出语句:WRITE语 句、PRINT语句和READ语句。实际上高级输入输出语句也是这三个 语句。只是在使用高级输入输出操作时,需要设置更多的输入输 出控制选项。而简单输入输出语句无需设置这些控制选项,只要 用星号“*”就可以表示系统默认的输入输出操作方式了。 • 要在输入输出语句中使用高级的输入输出选项,就要使用专门的 语句来定义格式的形式。这个语句就是FORMAT语句,也称为格式 说明语句。该语句的语法形式为: • FORMAT (format-list) 11.2 输入输出语句与格式语句 • 在前一小节中,详细介绍了FORMAY语句的相关知 识。在介绍的过程中,提到了FORMAT语句必须是 有标号的,以便在输入输出语句中进行引用。在 这一节中,就要介绍在输入输出语句中如何引用 FORMAT语句。当FORMAT语句定义完全并编上标号 后,就可以在输入输出语句中通过引用标号的形 式来引用格式说明语句。三种输入输出语句引用 格式说明语句的形式罗列如下所述。 11.2.1 WRITE语句引用格式说明语句 • 在向外部设备输出时,语法形式如下: • WRITE(设备号, [FMT=]格式说明语句标号) [变量 列表] • 语句中,设备号表示要在其上输出数据的设备, 当设备号为星号“*”时,表示向默认的设备输 出;关键字段“FMT=”用来显示说明其后所跟的 标号为格式说明语句的标号,当WRITE语句说明项 中只含有一项时,“FMT=”可以省略;语句中允 许变量列表为空,此时WRITE语句的作用是输出一 个空白行。 11.2.2 PRINT语句引用格式说明语句 • PRINT语句只能向计算机的默认设备(即屏幕)上 输出数据。因此PRINT语句引用格式说明语句的形 式中不含有设备号这样的参数。语句的语法形式 如下: • PRINT 格式说明语句标号[, 变量列表] • 需要注意的是,PRINT语句中在引用格式说明语句 标号时,不允许使用关键字段“FMT=”。这和 WRITE语句有一个重要的区别。语句中允许变量列 表为空,作用与WRITE语句相同。 11.2.3 READ语句引用格式说明语句 • 在从外部设备中输入时,语法形式如下: • READ(设备号, [FMT=]格式说明语句标号) [变量 列表] • 语句中,设备号表示要在其上输入数据的设备, 当设备号为星号“*”时,表示从默认的设备(一 般是键盘)输入;关键字段“FMT=”用来显示说 明其后所跟的标号为格式说明语句的标号,当 WRITE语句说明项中只含有一项时,“FMT=”可以 省略;语句中允许变量列表为空,此时READ语句 将等待输入,指导用户键入回车键。 11.2.4 第一个输入输出综合应用 • 下面来看一段代码,这段代码综合应用了上述三种输入输出语句和FORMAT语句的交互作用。 • TEST1101.F90 • ! WRITE & FORMAT 语句的范例 • PROGRAM TEST1101 • IMPLICIT NONE • ! 变量定义 • INTEGER :: I, J, K • REAL :: A, B, C • ! 可执行段 • WRITE(*, *)'Input:' • READ (*, 100)I, J, K • READ(*, 200)A, B, C • WRITE(*, *)'Output:' • WRITE(*, 300) I, J, K • PRINT 400, A, B, C • ! 格式说明 • 100 FORMAT(1X, 3(I3, 1X)) • 200 FORMAT(1X, 3(F6.2, 1X)) • 300 FORMAT(1X, 'I=', I3, 'J=', I3, 'K=', I3) • 400 FORMAT(1X, 'A=', F6.2, 'B=', F6.2, 'C=', F6.2) • • END PROGRAM TEST1101 11.3 格式编辑符概述 • 用户在指定输出格式时,需要特定的方式向系统 说明格式的“相貌”。在Fortran中,描述格式 “相貌”的工作由“格式编辑符”(或“编辑描 述符”)来完成。格式编辑符的作用就是将数据 进行类似书报编辑对文字进行编辑排版一样的处 理,使数据以更美观的形式进行显示。在Fortran 中,格式编辑符按照用途可以分为数据格式编辑 符、控制格式编辑符和字符串格式编辑符三大类。 下面将对这三类编辑符的使用和功能进行讲解。 11.4 数据格式编辑符 • 顾名思义,数据格式编辑符主要针对程序中的整 型、实型、复型、逻辑型和字符型数据的输入输 出格式控制。下面将对其中最常用的几种编辑符 的作用和用法进行讲解。 11.4.1 I编辑符 • I编辑符适用于整型数据的输入输出,其一般形式为: • Iw[.m] • I是英文单词“Integer”的第一个字母,表示“整型数编 辑符”。 • w用来指示以w个字符的宽度来输出数据(通常称一个数据 所占的宽度为“字段宽度”),负数的符号也包含在字段 宽度内。如果要输出的数据实际宽度超出了w规定的宽度, 则不输出有效数据,而在该字段宽度范围内用星号“*”填 充。 • m用来指示至少需要输出m个字符宽度的数字。如果输出数 据的实际宽度小于m,则会在数据前面不足部分用0填充; 如果输出数据的实际宽度超过m,则按输出数据的实际宽度 进行输出(但不能超过w)。 11.4.2 F编辑符 • F编辑符适用于实数的小数形式输出,其一般形式为: • Fw.d • F是英文单词“Fixed point number”的首字母,表示“浮 点数编辑符”。 • w仍然表示要输出的“字段宽度”,包含一个小数点和负数 的负号。如果要输出的数据实际宽度超出了w规定的宽度, 则不输出有效数据,而在该字段宽度范围内用星号“*”填 充。 • d表示要输出数据的小数位数。如果要输出的实际数据的小 数位数小于d,则会在小数后不足的部分补充0;如果要输 出的实际数据的小数位数大于d,则会将实际数据中多余的 小数部分按四舍五入规则去掉。 11.4.3 E编辑符 • E编辑符用于输出指数形式的实数,其一般形式为: • Ew.d[Ee] • E是英文单词“Exponent”的首字母,表示“指数编辑符”。 • w还是表示要输出的“字段宽度”,包含指数部分所占的4个字符 的宽度和负数的负号。如果要输出的数据实际宽度超出了w规定的 宽度,则在该字段宽度范围内用星号“*”填充;如果输出的数据 实际宽度小于w规定的宽度,则在输出数据的前面用空格填充。 • d表示要输出数据的小数位数。小数部分的位数可以由公式w≥d+7 来确定,公式中的“7”表示一个小数点、小数点前的0、一个负 号和指数部分所占的4位。如果实际数据在指数形式下的小数位数 大于d,则多出的小数部分按四舍五入规则进行舍入;如果实际数 在指数形式下的小数位数小于d,则不足的小数部分用0进行填充。 • e表示指数部分中指数所占的位数。 11.4.4 D编辑符 • D编辑符适用于双精度数据的输出。其一般形式为: • Dw.d • D是英文单词“Double Precision”的首字母,表示“双精 度编辑符”。 • w仍然表示输出数据所占的字段宽度;d表示指数部分所占 的位数。具体的含义同E编辑符。 • 在使用方法上,D编辑符与E编辑符相似。只是把字母“E” 换成“D”,在数据输出时,指数部分的字母“E”用“D” 来代替。实际上,F编辑符也可用于双精度数据的输出,和 用于实型数据输出相似。但此时可能会由于不能确切估计 实际数据的大小而出现“大数印错,小数印丢”的情况。 11.4.5 A编辑符 • E编辑符用于输出指数形式的实数,其一般形式为: • Ew.d[Ee] • E是英文单词“Exponent”的首字母,表示“指数编辑符”。 • w还是表示要输出的“字段宽度”,包含指数部分所占的4 个字符的宽度和负数的负号。如果要输出的数据实际宽度 超出了w规定的宽度,则在该字段宽度范围内用星号“*” 填充;如果输出的数据实际宽度小于w规定的宽度,则在输 出数据的前面用空格填充。 • d表示要输出数据的小数位数。小数部分的位数可以由公式 w≥d+7来确定,公式中的“7”表示一个小数点、小数点前 的0、一个负号和指数部分所占的4位。如果实际数据在指 数形式下的小数位数大于d,则多出的小数部分按四舍五入 规则进行舍入;如果实际数在指数形式下的小数位数小于 d,则不足的小数部分用0进行填充。 • e表示指数部分中指数所占的位数。 11.4.6 L编辑符 • L编辑符适用于逻辑型数据的输出。其一般形式为: •Lw • L是英文单词“Logical”的首字母,表示“逻辑 编辑符”。 • w表示输出的逻辑型数据所占的字段宽度。由于逻 辑型数据在输出时只显示一个字符,即.TRUE.打 印为“T”,.FALSE.打印为“F”。因此,当w大 于1时,字符的左端用空格进行填充。 11.4.7 G编辑符 • 既然单独使用F编辑符和E编辑符都有这样或那样的缺点, 那么有没有一种编辑符足够“聪明”,可以自行判别哪种 数应该用F编辑符进行输出还是用E编辑符进行输出呢? • Fortran提供了这样一种“聪明”的编辑符,即G编辑符。 该编辑符对F编辑符和E编辑符的长处进行了综合,能够根 据要输出的实数大小来决定用何种格式进行输出,即F型格 式和E型格式。当输出的数值过大或过小时会自动采用E型 格式,当输出的数值能够用小数形式表达时则用F型格式。 G编辑符的一般形式为: • Gw.d[Ee] • G是英文单词“General”的首字母,表示“通用编辑符”。 • w仍然表示输出数据所占的字段宽度;d表示指数部分所占 的位数;e表示指数部分数字的位数。具体的含义参考E编 辑符。 11.4.8 B、O、Z编辑符 • 二进制(B)、八进制(O)和十六进制(Z)编辑符是 Fortran 90标准中新增的编辑描述符,用于整数、实数、 字符和逻辑量的输出。其一般形式为: • Bw[.m] • Ow[.m] • Zw[.m] • B、O和Z分别表示“二进制编辑符”、“八进制编辑符”和 “十六进制编辑符”。 • w表示输出数据的字段宽度;m表示需要输出的最少数字位 数,缺省值为1。如果实际的输出数据宽度少于指定的字段 宽度,则数据的左端用空格填充。但对于二进制数,如果 以0填补可读性会更好一些。例如00010101显示了l0101所 有的8位,此时可以令m=w的方法来强迫数据的开始以0填补。 11.4.9 EN、ES编辑符 • 工程计数法(EN)和科学计数法(ES)也是Fortran 90标准中新 增的编辑描述符。两种编辑符的的一般形式为: • ENw.d[Ee] • ESw.d[Ee] • 其中E是英文单词“Exponent”的首字母,N是英文单词 “Engineering”的第二个字母,S是英文单词“Scientific”的 首字母。 • EN和ES编辑符中的w、d、e与E编辑符中的基本类似,可以参考E编 辑符中的相关描述。梁柱编辑符与E编辑符的区别在于: • 采用EN编辑符输出数据时,数据的非指数部分的绝对值一定在1到 1000的范围内(除非数据的数值为0),且指数可以被3整除。包 括指数部分和负号,整个数据的输出字段宽度是w个字符,小数点 后d个字符,指数宽度e是可选的。 • 采用ES编辑符输出数据时,数据的非指数部分的绝对值一定在l到 10的范围内(除非数据的数值为0),而非E编辑符的0到1。 11.5 控制格式编辑描述符 • 控制格式描述编辑符在格式语句中的作用是确定 文本的显示方式,比如数据在所在行的什么位置 进行输出、统计记录中剩余的字符数目、是否输 出数据的加号等。下面分别对常见控制格式编辑 描述符进行介绍。 11.5.1 X编辑符 • X编辑符是控制格式编辑描述符中最常使用的一种。 该编辑符用来在输出数据时产生空格。前面的数 据格式编辑描述符在输出数据时,数据之间没有 空格。为了避免读数困难,前面的例程在输出 时,尽可能每行只输出一个数据。有了X编辑符之 后,数据的输出就更为方便了。X编辑符的一般形 式为: •nX • 其中n表示要插入的空格数量。Fortran 77标准中 允许n为负数,但是Fortran 7的子集以及很多 Fortran 90/95编译系统,例如Compaq Visual Fortran、gFortran和G95,并不支持这种用法。 11.5.2 纵向走纸控制符 • 在介绍X编辑符时,提到了格式语句中第一项如果是1X,则 在打印设备上可以作为纵向走纸控制符。那么什么是纵向 走纸控制符呢? • Fortran中规定:把格式记录中的信息传送到打印设备上 (如打印机或终端)时,格式说明中的第一个字符用作纵 向间隔控制标志,称为纵向走纸控制符。格式说明中的第 一个字符不再被打印出来,而从格式说明中的第二个字符 开始打印。 纵向走纸控制符及功能 格式说明的首字符 纵向走纸控制功能 常用形式 (空格) 移到下一行开头 1X, ‘‘ 0(数字0) 移到下面第二行开头 ‘0’ 1(数字1) 移到下一页第一行开头 ‘1’ +(加号) 移到当前行开头 ‘+’ -(负号) 移到下一行开头 ‘-’ 11.5.3 斜杠编辑符 • 斜杠“/”编辑符的作用是结束本笔记录的输出并开始下一笔记录的输出, 其一般形式为: • [r]/ • 其中r为重复系数,该系数必须是一个正的整形常数。如果有两个连续的斜 杠,相当于在输出记录中增加一个空行输出;如果在格式编辑符的最后出现 斜杠,也要再输出一个空行。用n个连续的斜杠,可以达到输出n-1个空行的 效果。下面的代码演示斜杠编辑符的使用效果。 • TEST1112.F90 • ! /编辑符的范例 • PROGRAM TEST1112 • IMPLICIT NONE • WRITE(*, ‘(“ I=”,I3,/,” J=”,I3,/,/,” K=”,I3,/)’)1, 3, 5 • WRITE(*, ‘(“ L=”,I3,/,” M=”,I3,/,” N=”,I3)’)2, 4, 6 • END PROGRAM TEST1112 11.5.4 T,TL和TR编辑符 • T、TL和TR编辑符也称为位置编辑符,其作用从其名字就可 以知道适用于控制输出项的输出位置的。这三个编辑符指 出将要输出到记录上的下一个字符的位置。它们的一般形 式为: •Tn • TLn • TRn • 其中n是非零的正整数。 • T编辑符指明记录上的下一个字符输出到本行第n个字符的 位置上。对于行式打印输出,由于记录中的第一个字符被 用作纵向走纸控制符,因此Tn的实际作用是将输出位置定 位在打印记录的第n-1个字符上。在这个位置之前如果没有 其他字符输出,则用空格填充。 11.5.5 冒号编辑符 • 冒号“:”编辑符的作用是当I/O列表中没有更多的数据项时,使 格式控制结束。冒号编辑符常常用于FORMAT语句中没有要输出的 数据项时结束输出。来看下面的一段代码。 • TEST1114.F90 • ! :编辑符的范例 • PROGRAM TEST1114 • IMPLICIT NONE • PRINT 1,3 • PRINT 2,13 • 1 FORMAT (' I=',I2,' J=',I2) • 2 FORMAT (' K=',I2,:,' L=',I2) • END PROGRAM TEST1114 11.5.6 P编辑符 • P编辑符也称为比例因子编辑符,可用来设置比例 因子以改变小数点位置。P编辑符用于实数的编辑 描述符,如F、E和G编辑符。编辑符的作用范围延 续到下一个比例因子的设置处。P编辑符的一般形 式为: •kP • 其中k是一个有符号整数(为正时可省略正号), 用于指定小数点向左或向右移动几位。k的取值范 围在-128至127之间。在每一个输入输出语句开始 时,比例因子都会被初始化为0。输出时,正k向 右移,负k向左移(输入时正好相反)。 11.5.7 SP,SS,S编辑符 • SP,SS和S编辑符的作用是在数字的输出字段中确定是否添 加加号“+”。SP编辑符为其后所有正数的输出添加加号 “+”;SS编辑符则用于取消SP编辑符的作用;S编辑符重 新储存SS使其后的正数不输出加号。下面的代码段演示了 这三个编辑符的作用效果。 • i = 251 • WRITE (*, 100) i, i, i, i, i • 100 FORMAT (I5, SP, I5, SS, I5, SP, I5, S, I5) • 程序将打印以下结果: • 251 +251 251 +251 251 11.5.8 BN,BZ编辑符 • BN和BZ编辑符也称为空格编辑符,其作用在于确 定输入时的空格所代表的意义。BN编辑符通知系 统将输入时没有数据的字节处理成“无意义”, 即输入字段宽度内的空格什么也不代表,仅仅是 占位符;BZ编辑符则通知系统将输入时没有数据 的字节全部处理为“0”,即输入字段宽度内的空 格代表“0”。 11.6 字符串编辑描述符 • 字符串编辑符主要用于字符常量的格式化输出。 常见的字符串编辑描述符有两种,即撇号编辑符 和H编辑符。在使用上,H编辑符在Fortran 90标 准中已经被废弃,在Fortran 95标准中则进一步 被废除。下面分别对这两种编辑符进行介绍。 11.6.1 撇号编辑符 • 撇号编辑符(单撇号“'”或双撇号“"”)用来 插入所需的字符串,其一般形式为: • “要插入的字符” • ‘要插入的字符’ • 如果要输出的字符中包含撇号,则应该使用两个 连续的撇号来代表一个被输出的撇号。 11.6.2 H编辑符 • H编辑符是Fortran 77等老标准的遗留产物,在Fortran 90/95标准中已经相继被废弃和废除。但是,一些较老的 Fortran程序中仍可能会遇到这种编辑符,这里作一简单介 绍。H编辑符用来输出字符常量,其一般形式为: • nH<字符串> • 其中n为字符串中的字符个数。H编辑符在作用上与撇号编 辑符相似,但使用上不甚方便。例如下面的两句代码的作 用是完全一致的: • WRITE(*, ‘(‘The value of A is’, F10.5)’) A • WRITE(*, ‘(17HThe value of A is, F10.5)’) A • 使用H编辑符时必须准确的确定字符串中的字符数量,否则 很容易产生编译错误。例如下面的代码段就会产生编译错 误: • WRITE(*, ‘(18HThe value of A is, F10.5)’) A 11.7 编译器扩展的格式编辑描述符 • 除了Fortran 77/90/95标准中规定的标准格式编 辑描述符外,一些Fortran编译器还会提供一些扩 展的格式编辑描述符。在有些情况下,这些扩展 的格式编辑描述符会使编写的应用程序增色不少。 本小节将介绍两种Compaq Visual Fortan编译器 提供的两种扩展格式编辑描述符。 11.7.1 反斜杠“\”编辑符和美元“$”编辑符 • 反斜杠“\”编辑符和美元“$”编辑符在格式输入输出语 句中的作用相同:都是在输出一个记录行后取消回车符, 接着输出的记录会紧接在前一个记录的后面位于同一行。 • 这两个编辑符常用于输出的字符串与输入数据需要显示于 屏幕同一行的情形。这两种编辑符在使用方式上完全一 样,只要放在格式说明列表的最后就可以了。例如下面的 一段代码: • WRITE(*, 100) • 100 FORMAT (' ENTER RADIUS VALUE: ',$) • READ(*, *) RADIUS • 该段代码首先会在屏幕的第一行显示如下内容: • ENTER RADIUS VALUE: 11.7.2 可变格式编辑符 • 在前面的格式说明语句中,控制编辑描述符中多是常数。 例如整型变量对应的I编辑符中,规定的字段宽度是固定 的,如果数据的实际长度没有那么宽,则多出的部分就只 能用空格填充。这就使数据的输出不太美观。 • 在Compaq Visual Fortran中提供了可变格式编辑符来解决 这类问题,即用尖括号括起来的数值表达式来表示可变的 格式。该编辑符的一般形式如下: • <数值表达式> • 其中的数值表达式可以是常量表达式,也可以是变量表达 式。可变格式编辑符通常用作I编辑符中的可变字段宽度定 义、可变重复系数等。 11.8 I/O列表 • I/O列表也叫做输入/输出列表,它罗列了需要进 行输入/输出操作的所有变量。在Fortran中,针 对I/O列表有一些特殊的用法和语句,如这里介绍 的NAMELIST语句。本节将主要介绍同I/O列表相关 的知识。 11.8.1 NAMELIST语句 • NAMELIST语句是Fortran 90/95标准中正式收录的一种特殊 输入/输出方法。其实早在Fortran 77时代,一些编译器就 已经开始支持这种输入输出用法。但是,Fortran 77时代 的NAMELIST语句没有统一的标准,各家编译器厂商大多各 行其道,怎么方便怎么来。这种情况直到Fortran 90/95时 代才得到了改观。在新标准中,NAMELIST语句作为一种正 式的标准语句,开始有了统一的使用格式。 • NAMELIST语句也称为名称列表语句,其作用就是将一组变 量同一个列表组名相关联。该语句将一组相关的变量封装 在一起,在对这些变量进行输入/输出操作时,只需要确定 在输入/输出语句中确定用哪一个NAMELIST就可以了。也就 是说这个列表组名可以在输入/输出语句直接被引用,其中 所封装全部变量会依次被输入/输出相应的数据。 11.8.2 I/O列表实体 • 在使用输入/输出语句(如READ、WRITE、PRINT语 句)进行输入输出操作时,需要知道如何进行数 据传递和传递什么数据的信息。其中如何进行数 据传递已经在前面的小节中进行了较为详细的介 绍,而传递什么数据则是由I/O列表(io-list) 中列出的将要进行输入/输出操作的项来确定的。 也就是说,I/O列表提供了将要进行输入/输出操 作的数据的相关信息。其实在前面的章节中早就 已经遇到过I/O列表,只是没有专门列出来进行说 明。本小节专门将其罗列出来,将I/O列表实体的 有关知识进行一个简单的总结。 第12章 文件操作 • 在前面的章节中,大部分的程序在运行时,总是从键盘上 输入数据,程序的输出也几乎总是显示在计算屏幕上。对 于一些小程序,输入的数据不多、数据结构也不复杂,采 用这种方式是可行的。但是,对于一些大型的应用,例如 CFD(Computational Fluid Dynamic,计算流体力学)、 CAE(Computer Aided Engineering,计算机辅助工程) 等,都涉及到几百兆甚至上G的数据输入与输出。在这种情 况下,如果仍然采用终端输入输出的方式就很难想象了。 另一方面,使用终端输入输出方式时,程序在运行中可以 通过屏幕查看到输出的结果,一旦退出程序,此时要想再 次查看输出的结果就很困难了,除非再次运行程序。使用 文件就可以避免上述问题,同时文件还能避免重复处理, 保存起来也很方便。本章将介绍文件的基本功能和主要操 作。 12.1 文件与逻辑设备 • 在Fortran中,文件和逻辑设备总是成对出现的。 文件总是同特定的逻辑设备相关联,不管这个文 件是外部文件还是内部文件。文件同逻辑设备的 连接是通过设备描述符来进行的,外部文件的设 备描述符是数字,而内部文件的设备描述符则是 字符。但是有一点要注意,逻辑设备的概念是大 于文件的。逻辑设备不仅仅同文件相关联,而且 同计算机的外部设备也相关联。本节将介绍文件 和逻辑设备的相关内容。 12.1.1 逻辑设备 • 逻辑设备是与文件操作密切相关的重要概念。在Fortran语 言中对文件和外部设备的操作都要通过逻辑设备才能进行。 在对文件和外部设备进行操作之前,都要把它们连接到相 应的逻辑设备上。逻辑设备通过设备描述符与相应的文件 或外部设备相关联。 • 在Fortran语言中,内部文件的设备描述符和外部文件的设 备描述符是不同的:内部文件通过一个字符型变量或其它 变量名来进行描述;外部文件则使用OPEN语句打开文件时 的数字(也称为设备号)作为文件的设备描述符,或是用 默认的设备号(例如前面经常用到的星号“*”)作为文件 的设备描述符。 12.1.2 外部文件 • 在Fortran中,文件又分为外部文件和内部文件。如果文件 的保存介质是计算机上的内存,则称这种类型的文件为内 部文件。如果将内存中的数据记录到磁盘文件或输入/输出 到其他的外部设备(如打印机、显示器、键盘)上时,则 称为外部文件。 • 在Fortran语言中,同一个外部文件相关联的设备描述符必 须是—个正整数(整型常量、整型变量或整型表达式)或 是星号“*”。设备描述符的数值范围为0到2,147,483,640。 例如下面的代码段将外部文件external.dat与设备描述符 10连结起来,并往其中书写数据。 • OPEN(UNIT = 10, FILE = ‘external.dat’) • WRITE(10, ‘(A)’) ‘The data:’ 12.1.3 内部文件 • 保存在计算机内存中的数据也可以像硬盘上的文件一样进行操作。 在Fortran语言中,把同设备描述符连接、能够像外部文件一样进 行输入输出操作的保存在一块内存中的数据称为内部文件。能够 同内部文件进行连接的设备描述符只能是字符串或是字符数组。 • 在实际的编程实践中,常会遇到两种基本类型的内部文件。这两 类内部文件的基本情况如下: • 第一类内部文件是以单个存储空间为操作单位的,它可以是一个 字符型变量、字符型数组元素或者单个的非字符型数组元素。其 中的非字符型数组元素的记录长度至少应该与要写入数据的字段 宽度一致,否则会在进行写入操作时因为空间不足而发生运行错 误。这是在使用内部文件时最容易鱿值拇砦蟆 • 第二类内部文件是以多个连续存储空间为操作单位的,它可以是 一个字符型数组、基于字符的派生数据类型或是非字符型数组。 其中的非字符型数组的记录排列顺序和记录的长度至少应该与要 输入数据的字段宽度保持一致。 12.2 外部文件分类 • 外部文件按照文件中数据的保存结构,可以分为 有格式文件、无格式文件和二进制文件。按照文 件中数据的存取方式,可以分为顺序存取文件和 直接存取文件。本节将简要介绍这几种文件的特 点,至于其中涉及到的一些语句操作会在后面的 小节中相继介绍。 12.2.1 有格式文件 • 在格式化文件中,数据内容的记录是以ASCII码字符的方式 进行的,在日常的计算机应用中经常遇到的“文本文件” 就属于这一类文件。 • 格式化文件中的每一条记录都是以ASCII码中的回车符(CR) 加换行符(LF)来结束的。使用一般的文本编辑软件打开 有格式文件就可以直接看到其中的内容,即存放在文件中 的数字就是平时所看到的数字,字符就是平时所看到的字 符。而用文本编辑软件打开无格式文件或二进制文件时, 看到的则是一些十六进制的字符。因此如果要使文件中的 内容可以被人直接炊  詈檬褂糜懈袷轿募  • 在实际编程中,可以使用OPEN语句来创建或打开一个有格 式文件。在使用OPEN语句创建文件时,如果省略FORM参 数,则默认建立的文件为有格式文件,或是将FORM参数设 置成“FORM=’FORMATTED’”的形式也可以建立有格式文 件。 12.2.2 无格式文件 • 无格式文件由物理块组成的一系列记录,所存储的每一个记录都 保存了一系列的数据,这些数据的存放方式与它们在内存中的存 放非常相似。因此,无格式文件中同类型的数据如果种别参数相 同,则在文件中的记录长度也是一样的。这种数据存放方式使无 格式文件在进行输入输出操作时只需要做很少的数据转换工作。 由于去掉了格式控制,同存储相同信息的有格式文件相比,无格 式文件在使用数据信息时所做的处理更简洁更迅速,文件体积也 比较小。基于同样的原因,如果无格式文件中存放着数字,用户 使用文本编辑软件打开这些文件时将无法看到这些数字。但是, 对于其中保存的字符型数据,则可以通过文本编辑软件进行查看。 • 使用OPEN语句可以创建或打开一个无格式文件。在使用OPEN语句 创建文件时,通过将FORM参数设置成“FORM=’UBFORMATTED’” 的形式就可以常见一个无格式直接存取文件。 12.2.3 编译器支持的二进制文件 • 二进制文件是处理最快、最简洁的一种文件,也是最紧凑 的存储格式,适合于大批量数据的存储。同无格式文件相 比,二进制文件中所包含的记录就只是数据,其他所有的 格式控制字符(如回车符、换行符)都不会出现在文件中。 在进行二进制文件的读取操作时,变量和数据的读取顺序、 类型、种别必须和输出的变量和数据完全一致,否则会发 生错误的数据读取。 • 在Fortran标准中并不直接支持这种类型的文件,能够支持 这类文件格式的编译器大多是计算机厂商的自行扩展。例 如在Compaq Fortran中,可以使用OPEN语句来创建和打开 一个二进制文件。使用OPEN语句创建文件时,只需将FORM 参数设置成“FORM=’BINARY’”的形式就可以创建一个二 进制文件。 12.2.4 顺序存取文件 • 存放在顺序存取文件中的数据必须一个记录接一个记录地 按顺序被访问(除非使用REWIND或BACKSPACE语句来改变读 取的位置)。打个比方来说,如果程序中需要读写第N条记 录,则必须已经对前面的N-1条记录进行过读写操作。 • 在输入输出操作中,有些操作只能在顺序存取文件中才可 以使用。这些操作包括非推进式的输入输出、直接列表输 入输出和名称列表输入输出。内部文件也必须是顺序文件。 键盘、显示器和打印机等要求顺序访问的外部设备也必须 以顺序文件的方式进行连接。 • 采用OPEN语句的默认设置来打开或创建的文件都是顺序文 件,当然也可以将OPEN语句中的ACCESS参数设置成 “ACCESS=’SEQUENTIAL’”的形式来显式说明要创建的顺 序文件。 12.2.5 直接存取文件 • 存放在直接存取文件中的记录可以以任意顺序进行读写操作。这 种文件中的记录从1开始进行连续编号,所有记录的长度都是一致 的,它通过OPEN语句中的RECL参数来描述。 • 在直接存取文件中对记录进行存取是通过指定要访问的记录号来 实现的。因此,直接存取文件通常用于需要对数据进行随机访问 的应用场合,一个最常见的应用就是数据库。 • 采用OPEN语句可以创建或打开一个直接存取文件。在OPEN语句中 通过将ACCESS参数设置成“ACCESS=’DIRECT’”的形式就可以向 系统说明创建或打开的文件时直接存取文件。 • 直接存取文件中的每个记录必须具有相同的长度。如果实际输出 的记录长度不等,则要取所有输出记录中最大的长度作为每个记 录的长度。如果要使用一个已经存在的直接存取文件,则在OPEN 语句的RECL参数中说明的记录长度必须与原文件中的实际记录长 度一致。使用中要特别注意,尾随的空格符会占用一个字节,回 车符、换行符不计入记录的长度。 12.3 文件操作语句 • 前面介绍了Fortran中外部文件的分类和这几类文 件的基本特点。从本节开始,将开始介绍同具体 的文件操作有关的内容。在介绍文件操作之前, 有必要先行介绍文件操作常用的语句。在熟悉了 文件操作语句之后,才能更好的理解不同的文件 存取操作是如何实现的。 12.3.1 操作语句概述 • 在Fortran语言中,同文件操作有关的语句包括输入输出语 句、文件连接语句、文件查询语句和文件定位语句等几 类,通过这些语句可以完成文件的连接与关闭、文件记录 的输入与输出、文件状态的查询和文件记录指针的定位等 操作。通过这些语句,Fortran程序能够自由的操作大多数 类型的文件。下面给出各类操作所包含的具体操作语句。 • 同文件连接和关闭相关的语句包括:OPEN语句、CLOSE语句。 • 同文件查询相关的语句包括:INQUIRE语句。 • 同文件定位相关的语句包括:BACKSPACE语句、ENDFILE语 句、REWIND语句。 • 同文件输入和输出相关的语句包括:READ语句、WRITE语句。 12.3.2 OPEN语句 • 在Fortran语言中,OPEN语句将设备描述符和具体的外部或 内部文件连接起来,可用于建立一个新文件并使其与一个 设备描述符相连,或者改变某一个连接的属性。OPEN语句 有着丰富的参数选项,能够对文件的各种性质进行指定, 是Fortran语言中最为复杂的一种语句。熟悉和掌握OPEN语 句中各个参数项的意义和作用,是进行文件操作的基础。 OPEN语句的一般形式如下: • OPEN ([UNIT = ]io-unit [, FILE = name] [, ERR = label] [, IOSTAT = i-var], slist) • 语句中括号内的部分就是OPEN语句的参数项,也称为说明 项。包括设备号说明符、文件名说明符、错误处理说明符、 I/O状态说明符和属性说明符slist,其中属性说明符又包 括了一系列文件属性说明。 12.3.3 CLOSE语句 • CLOSE语句可以理解成OPEN语句的“逆语句”,它 用于解除设备号与文件之间的连接状态,又称关 闭文件。CLOSE语句的参数选项也较为丰富,提供 的功能也较多。CLOSE语句的一般形式为: • CLOSE([UNIT = ]io-unit [,STATUS = p] [,ERR=label] [,IOSTAT=i-var]) • 语句中括号内的部分就是CLOSE语句的参数项,也 称为说明项。包括设备号说明符、文件状态说明 符、错误处理说明符、I/O状态说明符。 12.3.4 OPEN语句和CLOSE语句示例 • 下面给出一段代码,来演示OPEN语句和CLOSE语句 以及其中的一些常用关键字在程序中的应用。 • (详细内容参照本书) 12.3.5 READ和WRITE语句 • READ和WRITE语句在高级输入与输出一章中已经介 绍过了,但还不够详细。在进行文件的写入或读 取操作时,READ和WRITE语句通过设定不同的参数 内容,可以实现有格式、无格式、二进制、直接 或是顺序的数据存取方式。 • READ和WRITE语句的完整形式几乎完全一致,控制 的参数类型也相当。下面列出这两个语句的通用 形式: • READ/WRITE (eunit, format [, nml-group] [, rec] [, advance] [, size] [, iostat] [, err] [, end] [, eor]) [io-list] 12.3.6 REWIND语句 • REWIND语句也被称为反绕语句,其作用是使与指定设备号连接的 文件位置指针指向文件的开头,通常用于顺序文件的读出操作。 • REWIND语句的一般形式如下: • REWIND ([UNIT=]io-unit [, ERR=label] [, IOSTAT=i-var]) • 其中,括号内的部分称为控制说明符,用于控制REWIND语句的行 为方式。下面分别对各个说明项进行解释。 • 1.设备号说明符:设备号说明符用来指出要进行操作的文件的设 备号。其语法形式如下: • [UNIT = ]io-unit • 2.错误处理说明符:错误处理说明符用于指定在REWIND语句执行 出错时将要运行的语句,其语法形式为: • ERR = label • 3.I/O状态说明符:I/O状态说明符指定语句执行操作的状态,其 一般形式为: • IOSTAT = i-var 12.3.7 BACKSPACE语句 • BACKSPACE语句也称为回退语句,其作用是使与指定设备号连接的 文件位置指针后退一个记录的位置,一般用于顺序文件的存取操 作。BACKSPACE语句的一般形式如下: • BACKSPACE ([UNIT=]io-unit [, ERR=label] [, IOSTAT=i-var]) • 其中,括号内的部分称为控制说明符,用于控制BACKSPACE语句的 行为方式。下面分别对各个说明项进行解释。 • 1.设备号说明符:设备号说明符用来指出要进行操作的文件的设 备号。其语法形式如下: • [UNIT = ]io-unit • 2.错误处理说明符:错误处理说明符用于指定在BACKSPACE语句 执行出错时将要运行的语句,其语法形式为: • ERR = label • 3.I/O状态说明符:I/O状态说明符指定语句执行操作的状态,其 一般形式为: • IOSTAT = i-var 12.3.8 ENDFILE语句 • ENDFILE语句的作用是在顺序存取文件中写入一条文件结束记录并 将文件的位置指针定位在该记录之后。ENDFILE语句的一般形式如 下: • ENDFILE ([UNIT=]io-unit [, ERR=label] [, IOSTAT=i-var]) • 其中,括号内的部分称为控制说明符,用于控制ENDFILE语句的行 为方式。下面分别对各个说明项进行解释。 • 1.设备号说明符:设备号说明符用来指出要进行操作的文件的设 备号。其语法形式如下: • [UNIT = ]io-unit • 2.错误处理说明符:错误处理说明符用于指定在BACKSPACE语句 执行出错时将要运行的语句,其语法形式为: • ERR = label • 3.I/O状态说明符:I/O状态说明符指定语句执行操作的状态,其 一般形式为: • IOSTAT = i-var 12.3.9 INQUIRE语句 • INQUIRE语句又称为文件查询语句,其作用就是获得指定文 件的特定属性的相关信息。要查询的文件可以通过文件名 来指定,也可以通过与文件相连的设备号来指定。INQUIRE 语句的一般形式为: • INQUIRE (FILE = name[or UNIT = io-unit] [, ERR = label] [, IOSTAT = i-var], slist) • 语句中括号内的部分就是INQUIRE语句的参数项,也称为查 询项。包括设备号说明符或文件名说明符、错误处理说明 符、I/O状态说明符和属性查询符slist,其中属性查询符 又包括一系列文件属性查询。文件属性中有相当一部分已 经在OPEN语句中解释过了,在INQUIRE语句中它们大多有新 的定义。 12.4 文件存取 • 前面介绍了Fortran中外部文件的分类和这几类文 件的基本特点以及同文件操作相关的各类语句。 从本节开始,将开始介绍如何操作不同类型文件。 在本节中,首先会用一小段篇幅来介绍文件的构 成,即文件是由什么构成的、组织结构又是什么 样的。在对这两个问题有了一个清晰的概念后, 再来讨论具体不同类型的文件操作。 12.4.1 文件的构成——记录 • 在本章的第二节中,已经介绍了外部文件的基本分类情况。 介绍中已经提到了不少概念,如记录、格式、存取方式等 等,但并没有具体给出这些概念的含义。本节就将补上这 一课。 • 首先是记录。文件都是由一个个记录组成的。所谓记录是 指数字或字符的序列,一个记录就是一系列数字或字符组 成的一个集合。在进行存取操作时,基本的操作单位就是 记录。一个记录会被看作一个整体,其中的数字或字符会 被一次性读出或写入,而不管具体有几个数字、有几个字 符。 • 根据数字或字符在记录中保存的方式或保存的内容,可以 将记录分成三种形式,即格式化记录、非格式化记录和文 件结束记录。 12.4.2 记录的组合 • 在讲解了记录的相关概念后,接下来的问题就是记录的组 合问题了。也就是说,记录与记录之间是以什么方式来排 列的呢?这就涉及到文件的结构问题了。 • 在Fortran支持的文件中,有两类文件结构,即顺序存取和 直接存取。在顺序文件中,各个记录依次排列,记录的长 度可长可短。读取这种类型的文件时,计算机或者需要知 道每条记录的长度(无格式文件的作法),或者需要知道 正在读取的记录在哪儿结束(有格式文件的作法)。而对 于直接存取文件,各个记录的长度都是一定的,计算机只 要知道该读取第几条记录就可以了,不再需要知道每条记 录的长度信息。 12.5 有格式顺序存取文件 • 所谓有格式顺序存取文件是指由按顺序排列在一 起的一系列有格式记录组成的文件。图是有格式 顺序文件的组织结构示意图。 12.5.1 有格式顺序存取文件的创建 • 程序首先需要用户输入用户名,并将用户名字作为数据文 件的名字。在提示了数据的输入方式后,程序会打开数据 文件开始记录用户的输入。在第17行,程序使用READ语句 来单独执行一次输入操作,用于初始化循环判据Name。如 果Name的输入值为“END”,则程序不会进行数据记录,而 是直接关闭已经打开的文件;否则,程序进入一个当型循 环(DO WHILE语句)之中,不断读取用户的输入,直到用 户键入“END”。 • 为了实现上述形式的操作,在WRITE语句的格式说明符中使 用了冒号“:”编辑描述符。在高级输入与输出一章中曾介 绍过这种编辑描述符,其作用就是在输入数据数量少于与 格式说明符中的数据数量时使后继的格式说明无效。这就 使用户只输入“END”后就可以不再进行数据输入成为可能。 需要注意,程序在第19行代码: • WRITE(*, '(I5,":",$)') I 12.5.2 有格式顺序存取文件的查询 • 对于有格式顺序存取文件而言,也可以实现对文 件内容的查询,前提是文件中有足够的信息提供 查询操作并且明确的知道文件的保存格式。下面 的例子就将演示有格式顺序存取文件的查询实例。 这个程序由三个部分组成:数据文件建立子程 序,用于建立数据文件;数据文件查询子程序, 实现文件查询功能;主程序,用于管理两个子程 序。程序会采用单一程序结构,两个子程序会以 内部过程的形式在主程序单元中定义。 12.6 有格式直接存取文件 • 在有格式直接存取文件中,所有记录的长度都相 同并且可以以任意顺序进行读写。如图是有格式 直接存取文件的组织结构示意图。 12.6.1 简单有格式直接存取文件的建立 • 下面一段短小的代码可以加深对有格式直接存取文件组织结构的 理解。 • TEST1210.F90 • ! 有格式直接存取文件的范例 • PROGRAM TEST1210 • IMPLICIT NONE • ! 文件创建 • OPEN(UNIT = 10, FILE = 'FDF.DAT', FORM = 'FORMATTED', ACCESS = 'DIRECT', RECL=10) • WRITE(10,'(A8)', REC=1) 'RECORD 1' • WRITE(10,'(A7,I1)', REC=3) 'RECORD ', 3 • CLOSE(10) • END PROGRAM TEST1210 12.6.2 复杂有格式直接存取文件的建立 • 下面给出一段功能更复杂的使用有格式直接存取文件的创建实例。 这是程序TEST1208中子程序单元CreateFile的有格式直接存取版 本,但运动项目换成了男子篮球。程序主要用于记录一支参赛队 伍在整个比赛期间的比赛情况,记录的数据由以下几部分组成: 胜负情况、具体比分、得分王、篮板王和助攻王。 12.6.3 有格式直接存取文件的查询 • 下面再给出用于查询上述文件内容的代码,它示范了有格式直接 存取文件的读取操作。 • 在进行有格式直接存取文件的操作时,要注意以下几点: • OPEN语句中的“ACCESS=’DIRECT’”和“RECL=”两个参数说明 项不能省略。 • 当“ACCESS=’DIRECT’”参数说明项存在时,OPEN语句默认是以 “FORM=’UNFORMATTED’”方式来打开文件的。因此在进行有格 式直接存取文件的操作时,一定要加上“FORM=’FORMATTED’” 这个参数说明项。 • 在对有格式直接存取文件进行存取操作时,一定要对READ和WRITE 语句进行格式说明,并且不允许用星号“*”来代替格式说明。 • 在对有格式直接存取文件进行读取操作时,需要使用“IOSTAT=” 选项或是“ERR=”选项来捕获操作中可能出现的错误。END、EOF、 EOR等关键字不允许在直接存取文件中使用。如果对文件中并不存 在的记录号进行了访问,则IOSTAT会出现异常,即值不为0。可以 籍此对用户的错误输入进行适当的处理。 12.7 无格式顺序存取文件 • 无格式顺序存取文件与有格式顺序存取文件在文件的组织结构上 没什么区别。这类文件中的记录长度也可以不同,编译系统在每 个记录的首尾各使用一个字节来说明记录的长度。记录和首尾的 保留字节共同被称为一个逻辑记录。在形成文件时,所有的逻辑 记录首尾相连,中间不存在用作记录分隔符的回车符和换行符。 系统则借助保留字节的数值来判断每一条记录在文件中的起始位 置和终止位置,控制数据的存取操作。 12.8 无格式直接存取文件 • 无格式直接存取文件与有格式直接存取文件在文件组织结构上没什么不同。 无格式直接存取文件也是由一系列长度相等的记录顺序组成的,但是记录 在文件中是以无格式的方式进行保存的。文件可以以任意顺序读写记录, 记录的长度由OPEN语句中的“RECL=”选项指定。记录与记录之间没有记 录分隔符或其他表示记录结构的字节。由于记录的长度都由“RECL=”选 项指定,同有格式直接存取文件相比,无格式直接存取文件在存储相同内 容的数据文件时,在存储空间上不会有减少,但数据的存储精度可以提高。 图是无格式直接存储文件的组织结构示意图。 12.8.1 无格式直接存取文件的创建 • 同样以程序TEST1210中的存储操作为例,来演示 无格式直接存取文件的结构并比较两者在存储具 体记录时的异同。 12.8.2 无格式直接存取文件与同类有格式文件的区别 • 在文件的操作上,无格式直接存取文件与有格式 的同类文件没有什么区别。 • (详细内容请参照本书) 12.9 二进制顺序存取文件 • 二进制文件以一类特殊的文件。标准Fortran并不支持此类 文件的应用,但是这类文件在存储空间或存储精度上有着 别的类型文件所不具备的优势。因此,仍然有必要对此进 行介绍。在Compaq Fortran中提供了对此类文件进行操作 的手段:通过将“FORM=”参数段声明为 “FORM=’BINARY’”的形式,就可以对二进制文件进行存 取操作。 • 二进制顺序存取文件同其他格式的顺序存取文件在组织结 构上没什么区别,都是由一系列按一定顺序排列起来的记 录组成的。但是,二进制文件记录中的数据是以数据在计 算机内部的保存形式,即二进制的形式直接保存在记录中 的。系统也不会在记录之间使用分隔符(如回车符、换行 符等)来区分不同的记录,也不会使用保留字节来说明记 录之间的联系,更没有说明文件结构用的特殊字节。文件 中记录的长度可以是不相等的。 12.9.1 二进制文件的创建 • 下面来看一段实际的例子,代码会生成一个简短 的二进制直接存取文件。为了检验一些控制符在 文件中的作用,程序在写入数据的过程中特意调 用了空WRITE语句,试图写入回车和换行符。 12.9.2 二进制顺序存储文件的查询 • 下面用二进制顺序存储文件的格式来改写程序 TEST1208,以便加深对这种文件存取操作的理解。 • (详细内容请参照本书) 12.10 二进制直接存取文件 • 二进制直接存取文件与前面提到的两种直接存取文件在文件组织 结构上是不同的,这就需要在实际操作过程中时刻注意。 • 二进制直接存取文件也存储一系列二进制数记录,它们也可以按 任何顺序进行访问。与二进制顺序存取文件不同的是,这些记录 的长度是相等的,由OPEN语句中的“RECL=”选项指定。在二进制 直接存取文件中可以写入部分记录,记录中末使用的部分将以未 定义数据填充;也可以将一条有多余数据的记录写入到两条记录 中,这样的操作在别的直接存取文件中是不允许的。 • 在二进制直接存取文件中可以使用一条读或写的语句来读写多于 一条的记录,而这样的操作在其他类型的直接存取文件中将引发 错误。这一特性虽然极大的提高了二进制直接存取文件的操作灵 活性,但也要在进行二进制直接存取文件的操作时需要加倍小 心,否则很容易出错。在其他类型的苯哟嫒∥件中所能进行的一 切操作在二进制直接存取文件中都是合法的。此外,在二进制直 接存取文件中任何未使用的记录空间都将保持为未定义状态,而 不会用空格去填充。 12.10.1 二进制直接存取文件的创建 • 下面来看一段二进制直接存取文件的创建实例。 • TEST1218.F90 • ! 二进制直接存取文件的创建 • PROGRAM TEST1218 • IMPLICIT NONE • ! 文件创建 • OPEN(UNIT = 10,FILE = 'BDF.DAT', FORM = 'BINARY', ACCESS = 'DIRECT', RECL = 10) • WRITE(10, REC=1) 'abcdefghijklmno' • WRITE(10, REC=3) 4,5 • WRITE(10, REC=4) 'pq' • CLOSE(10) • END PROGRAM TEST1218 12.10.2 二进制直接存取文件的读取 • 接下来的代码将演示二进制直接存取文件的读取 操作。操作的文件对象就是刚才的BDF.DAT文件。 12.11 硬件设备的使用 • 在Fortran语言中,除了可以将 键盘、屏幕、外部磁盘、内存等 作为输入输出设备之外,还允许 对其他物理设备进行读写操作。 • —般的输入输出语句都是针对标 准设备进行操作的。如果想对除 键盘、显示器等标准设备以外的 其他物理设备进行读写操作,就 需要在OPEN语句中将文件名说明 符中的name用物理设备名来代 替,这样就可以像操作普通文件 一样对物理设备进行读写操作了。 表 常见设备及其设备名 设备名 说明 CON 控制台(即屏幕,标准输出设备) PRN 打印机 COMl 1#串行通信端口 COM2 2#串行通信端口 COM3 3#串行通信端口 COM4 4#串行通信端口 LPTl 1#并行通信端口 LPT2 2#并行通信端口 LPT3 3#并行通信端口 LPT4 4#并行通信端口 NUL 空(NLTLL)设备。放弃输出,不包含任何 输入 AUX 1#串行通信端口 LINE1 1#串行通信端口 USER1 标准输出 ERRl 标准错误 CONOUT$ 标准输出 CONIN$ 标准输入 第13章 Fortran指针 • 指针是现代程序设计语言中一个非常重要的概 念,指针的应用能够大大加强程序语言的功能。 Fortran 90以前的Fortran标准中没有提供指针这 种数据类型,在Fortran 90标准中对这种现象进 行了重大的改进,引入了指针的概念。 • 但是值得注意的是,标准Fortran中的指针与C语 言中的指针并不完全相同。标准Fortran中的指针 并不代表一个变量在内部存储单元中的地址,而 是代表这个变量的别名。实质上标准Fortran中的 指针相当于C语言中的引用。本章将介绍Fortran 指针的概念与应用。 13.1 指针的基本概念 • 在没有引入指针概念之前,Fortran中对变量的使 用都是通过变量的名字直接进行的,程序中每个 变量的名字都必须是惟一的。那么同一个变量是 否可以取不同的名字呢?Fortran 90/95中的指 针,正是用来给变量起别名的。通过指针,同一 个变量存储单元可以通过多个变量名来进行访问。 13.1.1 定义指针 • Fortran语言中,要将一个变量名定义成指针,可以采用如 下的语法形式来进行: • 类型说明, POINTER :: 指针变量名列表 • 其中,类型说明用来说明指针变量可以指向的目标变量类 型,目标变量类型既可以是Fortran中提供的内部数据类型。 如INTEGER、REAL等,也可以是用户自行定义的派生数据类 型。不管适合哪种数据类型,目标变量都必须在程序的声 明阶段被赋予TARGET属性。 • 属性关键字POINTER说明被定义的变量具有指针属性,能够 用于指向类型说明中规定类型的变量。 • 指针变量名列表由若干个指针变量名组成,变量名之间用 逗号“,”进行分隔。指针变量名的命名规则同普通变量是 一致的,但不允许与其他的变量同名。 13.1.2 定义指针范例 • 下面是一些合法的指针变量定义范例: • INTEGER, POINTER :: Alp, A 2p(:) • REAL, POINTER :: Mp, Np(:, :) • COMPLEX, POINTER :: Cp • CHARACTER, POINTER :: Sp • TYPE(Personal_Information) :: Tp • 在上面的范例中,第一行语句声明了一个整型指针变量A1p和一个 整型指针数组A2p;第二行则声明了一个实型指针变量Mp和一个二 维的实型指针数组Np;第三行则声明了一个复型指针数组Cp;第 四行则声明了一个字符型指针变量Sp;最后一行则声明了一个自 定义数据类型的指针变量Tp。 • 在实际编程中,也可以将类型说明与指针说明分成两行来书写, 即先说明变量的类型,然后再定义变量的指针属性。例如下面的 语句同样定义了两个整型的指针变量A1p和A2p: • INTEGER :: A1p, A2p(:) • POINTER :: Alp, A2p 13.2 指针的基本应用 • 在Fortran语言中,指针能够与之进行联合的变量 既可以是其他非指针变量,也可以是通过动态内 存分配得到的一块内存。 13.2.1 指针的基本用途 • 下面通过一个具体的实例来说明指针变量最基本 的用途,即与其他非指针变量进行联合从而操纵 相应存储空间中的数据。 • (详细内容请参照本书) 13.2.2 指针与变量的关系 • 接下来的程序代码演示了Fortran中指针与变量之间的相互 关系。在前两次操作中,将变量X的值分别设置为10和3, 打印的结果显示指针P所保存的值也随着变量X的值而变化。 在最后一次操作中,则直接将指针P的值设置为35,这相当 于将P指向内存地址的内容设置为35,结果显示与指针联合 的变量X的值也变成了35。在Compaq Visual Fortran中, 可以使用LOC内部函数来获得变量的内存地址,因此在程序 的最后,通过LOC语句来查看两个变量在内存中的地址,结 果发现指针P与变量X共享了同一块内存单元。实际上,在 Fortran语言中,指针一旦与某个变量建立起了联合,则该 指针就会与这个变量共享同一块存储空间,而不会像其他 高级程序设计语言那样单独开辟一块存储单元来保存变量 的地址。这就好像药品,除了有化学上的名称外,还可以 有商品名称。 13.2.3 动态分配和释放指针空间 • 指针除了指向非指针变量外,还可以通过动态内存分配来得到一块内存。来看下面的 例子: • TEST1303.F90 • ! 动态分配指针空间 • PROGRAM TEST1303 • IMPLICIT NONE • ! 变量定义 • REAL, POINTER :: A • INTEGER :: Err • ! 为指针分配存储空间 • ALLOCATE(A, STAT=Err) • IF(0 /= Err) THEN • PRINT *, ‘分配指针的存储空间失败!’ • STOP • ENDIF • A = 3.1415926 • PRINT *, P • END PROGRAM TEST1303 13.2.4 指针状态的查询和更改 • 在使用一个指针之前,可以通过ASSOCIATED内部函数来查 询指针当前的状态。语句的使用形式如下: • ASSOCIATED(pointer[, target]) • 该内部函数用于检查指针是否设置了指向,返回值为逻辑 型变量。如果函数只含有一个指针参数,则会检查该指针 是否已经建立联合;如果函数含有两个参数,则会检查第 一个指针参数是否与第二个目标参数联合。 • 除了查询函数,Fortran语言中还提供NULL空置函数来确保 指针指向一个不能使用的内存地址,从而保证ASSOCIATED 能够正确判断出指针还没有与任何目标建立联合。语句的 使用形式如下: • NULL() 13.2.5 指针分享存储空间 • 在使用指针时,除了建立一对一的联合之外,还 可以同时用多个指针指向同一个变量。下面就来 看一个实例: 13.3 指针与数组 • 指针除了能够声明成单一变量的形式外,还可以 声明成数组的形式,称为指针数组。声明成数组 的指针可以有两种基本的使用方法: • 将指针指向一个数组、数组片断或数组元素; • 配置一定的内存空间来使用。 13.3.1 指针指向数组 • 利用指针来指向一个数组,能动态地分配数组空间,给编 程带来极大的方便。由于FORTRAN语言在使用数组时必须在 可执行语句之前进行定义,而一般定义数组的方法是在声 明阶段就指定数组的维界,确定数组存储空间的大小。但 在编写实际的程序时,通常很难事先确定所需数组的大小。 如果定义的数组空间较小很可能无法满足运行中的需要, 一种解决办法就是将数组定义得足够大,但这样做通常会 浪费计算机的存储空间。通过动态定义,可以根据当时实 际所需存储空间的大小来申请,既避免了存储空间的浪 费,又能够满足程序运行的需要。 • 1.定义指针数组 • 2.指针数组的秩 13.3.2 动态分配指针数组 • 指针数组除了上面提到的这种用法外,还可以通过 ALLOCATE语句来动态分配内存空间进行使用。也就是说, 指针数组也可以当作可变大小的数组来使用。 • 在数组一章中也曾经提到过可分配数组的概念和用途,指 针数组与可分配数组非常相似。那么这两者之间有什么区 别呢?可分配数组在程序中有自己的生存周期,它仅存在 于声明它的过程中,过程执行完毕后会由系统自动收回它 所占用的临时存储空间。但是指针数组在声明它的过程执 行完毕后,不会由系统自动回收它所占用的存储空间,程 序员必须自行通过DEALLOCATE语句来释放掉指针数组所占 用的存储空间。如果程序员没有在过程中使用DEALLOCATE 语句来释放存储空间,则指针数组占用的存储空间只能等 待整个程序执行完毕后由系统来回收。 13.4 指针与过程 • 指针也可以作为参数传递进过程中(包括函数子 程序和子例行子程序),并可以作为函数的结果 返回主调程序。在使用指针作为过程的相关参数 时,需要注意以下一些问题: • 当使用指针作为参数传递进过程时,在主调过程 中必须对被调函数进行显式的接口说明,即必须 编写INTERFACE接口; • 指针参数在声明时不需要使用INTENT属性进行说 明; • 如果函数的返回值为指针,也需要对该函数进行 显式的接口说明,即必须编写INTERFACE接口。 13.4.1 指针与过程的示例 • 下面来看一段实际的实例代码。 • (详细内容请参照本书) • 在上面的程序中,分别演示了函数子程序与子例 行子程序中使用指针数组的方式。函数GetMax会 将输入的指针数组中的最小值找出来,函数的虚 参类型及返回值都是指针,因此需要在主调过程 中显式的说明函数的调用接口。如果不在主调过 程中说明函数的调用接口,则在程序的运行过程 中很可能发生参数传递不正确的情况。同样的道 理也适用于子例行子程序,在主调程序也需要显 式声明调用接口。 13.4.2 在指针与过程中使用模块 • 在实际使用中,通常会直接编写模块来避免在主 调程序中重复书写涉及到指针过程的调用接口。 在主调过程中直接引用模块就相当于声明了指针 过程的调用接口。下面的代码将TEST1310改写成 了模块的形式,需要注意的是,如果模块与主程 序编写在同一个源代码文件中时,则模块的代码 必须出现在引用它的过程之前,否则会出现错 误;如果模块与调用过程不在同一个源代码文件 中,则不存在这种问题,编译程序会自动确定模 块目标文件的位置。 13.5 指针的简单应用 • 在一些实际应用中,使用指针能够非常快速的交 换数据。对于一些需要移动大量存储单元的应用 来说,指针是非常理想的工具。例如一些派生数 据类型的应用,使用指针可以避免移动大量的存 储单元,从而优化程序的执行速度。 13.6 指针与链表概述 • 指针除了可以间接的使用变量,以及作为指针数组来加以 使用外,另一个重要的用途就是使数据在计算机中以链接 方式进行存储。例如“串行结构”、“树形结构”等。 • 链式存储在计算中最重要的一项应用就是动态使用内存。 在编写程序中时常会遇到一些预先无法确切知道存储空间 大小的情况,以前的处理方法通常就是声明一个很大的存 储空间来保存数据。这种做法在程序运行时,其中大量的 存储空间是被浪费的。链式存储能够较好的解决这一问 题,它可以根据需要向计算机“申请”合适的内存来使用。 链式存储中最简单的应用就是链表。下面介绍关于链表的 一些基本知识。 13.7 单向链表 • 在计算机的早期应用中,节约内存是程序设计中一个非常重要的 方面。因为当时的计算机内部存储空间有限,这就要求程序必须 短小精悍才能在狭窄的舞台施展拳脚。尽管目前计算机的硬件发 展相当快,目前主流配置个人计算机的内存容量一般也在2GB左 右,但是应用程序对内存的需求也是与日俱增。而且目前的操作 系统大多支持多任务操作,即计算机能够同时运行多个程序。操 作系统能够运行的程序个数,通常取决于计算机内存的大小。单 个程序占用的内存越少,能够同时执行的程序就越多(当然程序 的运行效率不会高)。因此,节约内存的使用仍然是有必要的。 • 在数据的存储方面,顺序存储和链式存储是两种最基本的存储结 构。在顺序存储中,每个存储结点只保存与存储元素本身相关的 信息,元素之间的逻辑关系只需通过数组下标的值就可以确定下 来。例如在一个顺序表中,如果某一个元素存储在对应的数组中 的下标值为N,则在逻辑上比它靠前的一个元素在对应的数组中的 下标是N一1,比它靠后的一个元素就是数组中下标为N+1的元素。 13.7.1 单向链表的基本概念 • 先来看一个简单的例子,这个例子用于建立单向链表的概念,实际使用中不会采用下面的方式来建 立和使用链表。 • TEST1313.F90 • ! 单向链表的基本概念 • PROGRAM TEST1313 • IMPLICIT NONE • ! 链表节点定义 • TYPE Link • INTEGER :: Number • TYPE(Link), POINTER :: Next • END TYPE • TYPE(Link), TARGET :: P1, P2, P3 • ! 节点赋值 • P1%Number = 1 • P1%Next => P2 • P2%Number = 3 • P2%Next => P3 • P3%Number = 5 • P3%Next =>NULL() • ! 打印节点值 • PRINT *, P1%Number • PRINT *, P1%Next%Number • PRINT *, P1%Next%Next%Number • • END PROGRAM TEST1313 13.7.2 节点的定义 • 在链表中,节点是用于存放数据的基本单位,它 通常是一个派生数据类型。在单向链表中,节点 包含两个数据部分:一个部分称为数据域,用于 存储要保存的数据;另一个部分称为指针域,用 来保存链表中下一个节点的内存地址。可以用下 面的一般形式来表示单向链表中的节点: • TYPE Link • 数据域 • TYPE(Link), POINTER :: Next • END TYPE 13.7.3 节点的引用 • 链表建立好之后,就可以通过节点的引用来访问 节点数据域中的内容。在实例程序中,使用了一 种很原始的方法来访问各个节点: • PRINT *, P1%Number • PRINT *, P1%Next%Number • PRINT *, P1%Next%Next%Number • 由于程序中已经进行了P1%Next=>P2的设置,程序 中如果引用P1%Next就相当于引用节点P2。因此, 打印语句中的P1%Next%Number就相当于使用 P2%Number一样。 • 同样的道理,对P1%Next%Next%Number的引用就相 当于对P2%Next%Number或是P3%Number的引用。 13.7.4 链表的建立 • 上面的代码中,链表的建立方式并不“聪明”。 程序中的链表只有三个节点,编写起程序来似乎 并不麻烦,如果需要建立的链表有成百上千个节 点呢?实际上,现实编程中不会用前面的方式来 建立链表。前面示范程序的链表建立方式中,每 一个节点都有自己的名字,引用时完全可以直接 通过名字来进行,体现不出链表在使用上的优势。 真正建立链表时,通常只需要定义链表中第一个 节点的名字就可以了,在建立过程中通过指针就 可以建立起整个链表。 13.8 双向链表和环状链表 • 除了单向链表,在日常编程中还会经常用到双向链表和环 状链表。单向链表在使用中,只能按照链表建立时的顺 序,一个节点接一个节点的读下去,无法逆向进行访问。 双向链表和环状链表就不会存在这种问题。 • 双向链表和环状链表的节点定义上完全相同,区别就在于 首末两个节点的定义上。双向链表和环状链表的节点比单 向链表的节点多出一个指针域,用于指出前一个节点在内 存中的地址。这两种形式的链表节点可以用下面的形式来 表示: • TYPE Link • TYPE(Link), POINTER :: Previous • 数据域 • TYPE(Link), POINTER :: Next • END TYPE 13.8.1 双向链表的建立 • 在建立链表的其余节点时,需要清楚的知道上一条数据和下一条 数据的位置。上面的程序在创建链表的循环中,利用了如下的语 句来建立当前节点与上一节点和下一节点的逻辑关系: • DO J = 2, N • ALLOCATE(P%Next, STAT=Err) • IF(0 /= Err) THEN • PRINT *, '内存分配失败!' • P%Next => NULL() • EXIT • ENDIF • P%Next%Previous => P • P => P%Next • WRITE(P%Name, '("第", I1, "个节点")') J • P%Number = J • ENDDO 13.8.2 环状链表的建立 • 单向链表和双向链表都是有头有尾的,环状链表 则是一种无头无尾的特殊链表。来看下面的环状 链表实例,这是前面双向链表的一个改进程序。 链表使用的节点同前一个程序中定义的节点完全 相同。下面给出环状链表的程序源代码。 • (详细内容请参照本书) 13.9 链表的基本操作 • 在前面的小节中介绍了几种链表的基本概念及其 建立方式和简单的引用方式,除此之外,链表的 使用还包括其他一些基本的操作。这些操作包括 链表节点的插入、删除、查找、更新和遍历等, 本节将介绍链表建立后在链表中插入、删除节点 等基本操作的具体实现方法。 13.9.1 插入及删除操作 • 使用链表的一个突出优点就是可以根据需要随时 往链表中插入或者删除节点。如果类似的操作使 用数组来完成,就会显得相当的麻烦:如果插入 新的元素后数组中元素的数量超过了原始定义的 数组大小,则必须使用动态内存分配才能实现类 似操作;删除操作尽管不会造成数组元素超出数 组的存储范围,但是会造成存储空间的浪费。此 外,使用数组来完成节点的插入在效率上较低, 例如要在具有N个数组元素数组的第M个元素的位 置处插入一个数据,则需要移动N-M个数组元素的 位置。N-M的值越大,效率就越低。同样的道理, 要在数组中删除一个节点,也需要移动大量的数 组元素。 13.9.2 插入及删除操作实例 • 来看一段双向链表的插入和删除代码的实例,首 先给出的是定义了双向链表节点定义和相关操作 的模块单元。 • 在从链表中删除数据时需要注意,如果删除操作 处理不当,很容易形成无法使用的内存单元。在 进行删除操作时,首先要做的就是对要删除的节 点信息进行必要的复制。在程序中,这一工作是 通过如下的代码来实现的: • Prev => P%Prev • Next => P%Next 13.9.3 链表空间的释放 • 通过ALLOCATE语句动态分配得来的内存在不需要时最好使 用DEALLOCATE语句来解除对内存的占用,这是保证系统能 够充分利用内存资源的良好编程习惯。在释放链表占用的 内存空间时,通常只需要知道链表的表头就可以顺次释放 掉所有通过动态内存分配得来的存储空间。 • 在进行链表的释放时,通常需要使用一个辅助指针。因为 一旦当前节点的存储空间被释放而又没有使用辅助指针来 记录下一个节点的位置时,释放操作将无法继续进行下去。 有了辅助指针之后,每一个要删除的节点所保存的指向信 息都会由辅助指针来标明。最后需要强调一下, DEALLOCATE语句只能用来释放通过ALLOCATE语句动态分配 得来的存储空间,例如具有POINTER、ALLOCATABLE属性的 变量或是数组等。 13.9.4 遍历、查找及更新 • 遍历、查找和更新是链表操作中较为常见的类型。 所谓遍历就是将链表从头到尾的访问一遍;而查 找往往是以遍历的方法为基础,在链表中寻找符 合要求的节点;更新则用于替换链表中符合要求 的节点的数据域。下面以一个班级成员成绩系统 的建立为例,分别给出这几种链表操作的程序源 代码。 • 1.遍历操作 • 2.查找操作 • 3.更新操作 • 4.三种操作的综合应用 第14章 基本数值算法举例 • 数值计算是Fortran语言的强项,也是Fortran语 言发明者的初衷。本节主要介绍在计算机程序设 计语言学习中经常遇到的一些基本数值算法。目 的在于加深对Fortran语言的理解和分析,解决问 题的一般思路,并希望通过这些例程介绍一些代 码编写方面的技巧。 14.1 常用数值积分算法 • 积分是大学数学中最基础的部分,求积分最好的办法是通 过找到积分函数来进行。但有些时候,积分函数并不容易 找到。在这种情况下,计算机就派上用场了。通过数值计 算的方法,就可以很容易的得出所求积分的近似结果。 • 假设被积函数的一般形式为,则定义在区间[a,b]上的定 积分的几何意义就是曲线与直线、、所围图形的面积。在 求解的时候,可以将区间[a,b]划分为n个子区间,每个区 间的宽度就是h=(b-a)/n。这样,原来的一大块面积就被划 分为若干个小面积,只要这些小的子区间的面积能够求出 来,那么最后将这些子区间的面积相加,就可以近似得到 所求积分的值。子区间划分得越小(即n越大),同理论上 的积分值的近似程度就越高。 14.1.1 矩形法 • 矩形的面积等于底与高的乘积。因此,第i个小矩 形的面积可以表示为: • 在设计矩形法积分计算程序前,先来做一个规划。 假设最后的程序由三部分组成:一个主程序,用 于管理各个子程序(其中包括矩形法积分子程 序);一个具体的积分数值计算子程序,用于实 现矩形法的积分计算(或者还有别的方法);在 实际计算中,可能碰到的都是一些离散的数据, 很少有仅仅是简单函数的情况,因此还可能需要 一个读取离散数据的子程序。 ))1(( hiaFhSi ⋅−+⋅= 14.1.2 梯形法 • 梯形法的基本思想和矩形法没什么不同,只是在 求解子区间的面积时,使用梯形而不是矩形来进 行近似处理。积分过程中,第i个小区间的面积可 以用梯形面积公式求得: hhiaFhiaFSi ⋅⋅++⋅−+= 2 )())1(( 14.1.3 Simpson法 • Simpson(辛普森)法的基本思路就是用一段抛物 线来代替子区间内原来的曲线。这样就用一系列 的二抛物线段来对原来的曲线进行拟合,计算这 些抛物线所在子区间的面积,最后就可以得到所 需要的积分值。 14.1.4 数值积分计算子程序的应用 • 有了前面介绍的三个数值积分计算子程序之后,可以考虑将其放入同一个模块中。当需要调用其中 的某一个子程序时,只要引用该模块就可以了。由于积分计算子程序已经在前面详细给出了,这里 只给出模块程序单元的代码结构: • MODULE INTEGRAL • IMPLICIT NONE • CONTAINS • REAL FUNCTION Rectangle_Integral(FX, H) •…… • END FUNCTION Rectangle_Integral • REAL FUNCTION Trape_Integral(FX, H) •…… • END FUNCTION Trape_Integral • REAL FUNCTION Simpson_Integral(FX, H) •…… • END FUNCTION Simpson_Integral • END MODULE INTEGRAL 14.2 简单线性代数问题 • 线性代数也是数值计算中经常会遇到的一类问题。 在线性代数问题中,会遇到很多同数组操作有关 的问题。通过这一节的学习,希望读者能够进一 步掌握有关数组方面的程序设计方法和一些基本 的技巧。 14.2.1 Gauss消去法求解线性方程组 • 一般来说,工科大学本科高年级和研究生低年级 课程中都还设有数值计算的相关课程。在数值计 算中,线性方程组的求解是最基本的要求。在进 行线性方程组的求解中,会遇到大量处理整个数 组或数组片段的问题。以求解有n个方程的齐次线 性方程组为例,要求解的方程组得一般形式如下: nnnnnn nn nn bxaxaxa bxaxaxa bxaxaxa =+++ =+++ =+++ L MM L L 2211 22222121 11212111 14.2.2 Gauss Jordan消去法求解线性方程组 • 除了上面介绍的基本高斯消去法外,还有一种高 斯消去法的改进方法:Gauss Jordan消去法。该 方法的基本思路与高斯消去法完全一样,但在消 元这一步做的更为彻底。Gauss Jordan消元后的 系数矩阵就是一个主对角矩阵,只有主对角线上 的元素才有值。做完这一步后,回代的过程更为 简单。 • Gauss Jordan方法涉及到矩阵的上下三角化操作。 通过对矩阵的上下三角化操作来使方程的系数矩 阵变为对角化矩阵,即矩阵中除了对角线上的元 素外,其余的元素都为零。 14.3 非线性函数求解 • 本节主要介绍非线性函数的基本数值解法。对于一元二次 或三次方程,已经有求根公式可供使用。但是对于更高次 数的方程而言,推导理论上的求根公式并不容易。反过 来,借助数值方法就可以很容易的得到这些高次非线性方 程的数值解。 • 假设非线性函数的一般形式为,则在特定区间[a,b]上求 解的几何意义就是找曲线与直线的交点。求解的基本思路 是一致的,就是通过迭代来不断逼近要找的交点。但是在 具体的实现方法上各种方法是有区别的。根据迭代方式细 节的不同,可以分为四种基本的迭代方法:最简单的一种 方法就是迭代法,也可以称为简单迭代;一种是牛顿迭代 法;一种是二分法;一种是弦截法。 14.3.1 迭代法 14.3.2 牛顿迭代法 • 同样是求一元非线性函数 的根,牛顿迭代法 (简称牛顿法)则不需要对原始的公式进行变 形,可以直接使用到程序中,但需要求出原函数 的一阶倒数作为辅助函数。该方法的基本想法是 通过曲线上任意一点的切线来逼近函数的根, 0)( =xF 14.3.3 二分法 • 二分法是一种相当巧妙和简单的非线性函数求解方法。这种方法 不仅应用在非线性函数的求解上,而且还在其他领域有着广泛的 用途,是一种通用的基础搜索算法。 • 二分法求解非线性函数的前提是要确定一个区间[a,b]。在这个区 间内,非线性函数应该有一个实根。在此基础上,二分法才能有 效地进行工作。如果在给定区间[a,b]内,非线性函数都没有实 根,那么求解就无从说起。 14.3.4 弦截法 • 弦截法(也称为割线法,英文原名为Secant)的 基本思想和二分法是一致的。只不过搜索区间不 再简单的划分成两半,而是由原区间两个端点的 连线与x轴的交点来决定。弦截法的思路可以简单 的用图来表示。 14.3.5 非线性函数求解子程序的应用 • 现在可以将上面介绍的4种不同的非线性函数求解 子程序统统打包进一个模块程序单元中。由于具 体的计算子程序代码在前面已经详细给出,因此 下面只给出模块程序单元的结构,其中的具体函 数可以自行添加。 14.4 常微分方程的基本解法 • 常微分方程是科学研究和工程实践经常遇到的问 题,也是数值分析中一个重要的应用领域。在实 际应用中,一些偏微分方程在最后也会通过某种 形式转化成常微分方程来进行求解,例如计算流 体力学中的一些算法。 • 本节主要介绍一种常用常微分方程的数直解法— —龙格库塔法。假设要求解的常微分方程的一般 形式如下: ),( xyfdx dy = 14.5 插值与拟合 • 插值与拟合是数值分析中极为重要的一个方面, 它在科学研究和工程分析领域有着非常广泛的应 用。 14.5.1 拉格朗日多项式插值方法 • 插值的方法虽然多种多样,但是基本的思想都是一致的——即构 造各种类型的插值多项式。例如已知在个自变量上的函数值,则 惟一存在一个不超过次的多项式,它在这个自变量上具有与函数 相同的值。但是在实际应用中,构造一个次数较高的插值多项式 并不实用,而较多地采用低次插值多项式进行分段插值。 • 在众多的插值方法中,拉格朗日(Lagrange)多项式插值是实际 使用中最为常见的一种。假设有n个离散的数据点,则采用拉格朗 日多项式插值方法生成通过这n个离散数据的多项式函数如下: n nnnn n n n n n yxxxxxx xxxxxx yxxxxxx xxxxxx yxxxxxx xxxxxxxf )())(( )())(( )())(( )())(( )())(( )())(()( 121 121 2 23212 31 1 13121 32 − − −−− −−− + +−−− −−− +−−− −−−= L L L L L L L 14.5.2 牛顿前向插值方法 • 在插值方法中,牛顿前向插值可以解决拉格朗日 方法中遇到的效率问题。牛顿前向插值同样可以 得到一个通过n个离散数据的多项式,但在进行插 值时它会事先创建插值过程中会用到的差分表格 以加快插值的计算速度。牛顿前向插值方法中用 来生成插值多项式的公式如下: 0 0 )( fn sxf n k n Δ⎟⎟ ⎠ ⎞ ⎜⎜ ⎝ ⎛= ∑ = 第15章 文件处理举例 • 文件处理和应用在有些大量数据需要进行处理的 问题中往往是不可避免的。灵活应用文件及其相 关操作能够使一些问题得到更好的处理。希望本 节的例子能够加深对文件操作的理解。 15.1 临时文件的应用 • 临时文件是一类特殊的文件,这种文件在使用完之后会被系统删 除。在某些应用背景下,这种文件能够帮助大量数据进行临时处 理。本节主要利用临时文件来存储需要的数据,并将保存在临时 文件中的数据最终保存在正式的数据文件中。 • 本节的例程首先利用随机函数生成一系列整数并将其保存在一个 无格式文件中,然后从这些数据中找出所有的非素数并从原文件 中删除。最后文件中的素数应该保持其在原文件中的排列顺序, 并且需要将文件中所有的素数都打印出来以供用户阅读。这一过 程可以通过下面的步骤来实现。 • 从原文件中读取一个数据到变量Prime中; • 如果Prime是一个素数,就将该数存入一个临时的无名文件; • 重复上述两个步骤,直到原文件中的数据全部被处理完; • 将临时无名文件中数据放回原文件。 15.1.1 主程序 • 首先给出的是主程序,主程序模块中需要管理各 个功能子程序并打印必要的显示信息。 • (详细内容请参照本书) 15.1.2 随机数生成子程序 • 下面给出随机数生成子程序的代码。 • SUBROUTINE Random_File(Filename, Number) • IMPLICIT NONE •! 变量定义 • CHARACTER(256) :: Filename • INTEGER :: Number • INTEGER :: I • REAL :: Random • ! 保存随机数文件 • OPEN(10, FILE=Filename, FORM='UNFORMATTED') • DO I = 1, Number • CALL RANDOM_NUMBER(Random) • Number = INT(100*Random) • WRITE(10) Number • ENDDO • CLOSE(10) • • END SUBROUTINE Random_File 15.1.3 素数挑选和文件重写子程序 • 下面介绍本节范例中的核心部分:素数挑选和文 件重写子程序。 • 在这段代码中,涉及到了较多的文件操作。这些 操作在文件一章中已经讲述过,下面再解释一下 用法以加深理解。子程序首先将临时文件与设备 号20相连,接下来将无格式的原始数据文件与设 备号10相连。接着,通过一个无循环变量的DO循 环配合READ语句中的END关键字来获取文件结束时 所读取的数据总数,并将其保存在整型变量 Number中。获得文件中的数据数量后,通过 REWIND语句将原始数据文件中的读取位置指针重 新射到文件的开头。 15.1.4 素数判断子程序 • 最后给出素数判断子程序,这是一个非常简单的程序。只需要按照素数的 定义照做就可以了,即只能被1和它自己整除的数就是素数。 • LOGICAL FUNCTION IsPrimeNumber(Number) • IMPLICIT NONE •! 变量定义 • INTEGER :: Number • INTEGER :: I •! 素数判断 • DO I = 2, Number - 1 • IF(0 == MOD(Number, I)) THEN • IsPrimeNumber = .FALSE. • RETURN • ENDIF • ENDDO • IsPrimeNumber = .TRUE. • RETURN • • END FUNCTION IsPrimeNumber 15.2 多个文件的处理 • 经常会遇到这一类问题:同一个实验会进行好几 次,从而获得好几批数据;接下来的工作就是将 这些数据文件合并组织成一个新的数据文件,以 便在作图软件(如Tecplot等)中绘出不同批次数 据的图像进行直观的比较。这就涉及到多个文件 的处理,本节将介绍简单的多个文件的合并问题。 • 对多个文件进行处理,关键是需要清楚的知道各 个文件是以何种形式进行保存的。也就是说,只 有对文件保存的内容有一个清晰的认识和了解, 才能正确地读出想要的数据并将其放置到新文件 中需要的位置。 第16章 搜索与排序 • 搜索与排序是计算机程序设计中经常会遇到的一 类实际问题。搜索一词,简单来说就是从一堆事 物中找出需要的东西。对计算机而言,这堆事物 就是计算机要处理的数据。而排序就是将一堆物 品按照一定的规则排列起来。一般来说,搜索的 目的往往就是为了排序。本节将介绍程序设计中 会用到的一些常见搜索和排序算法。 16.1 搜索算法 • 搜索是为了从一堆数据中找出符合要求的数据, 计算机能够按照人们的要求不知疲倦的去搜索符 合的数据。常见的搜索算法包括简单的顺序搜索、 二分搜索及散列搜索。 16.1.1 顺序搜索 • 顺序搜索是最简单的一种搜索算法,简单来说, 就是将数据依次拿出来查找看是否有符合要求的 结果。例如对于一组数据(n1,n2,n3,…,nn),想要 确定其中是否有数据m,则顺序搜索的做法如下: • 先看第一个数据n1,如果n1=m则找到了所要找的 数据,否则继续搜索; • 接着看第二个数据n2,如果n2=m则找到了所要找 的数据,否则继续搜索; • ……; • 来看最后一个数据nn,如果nn=m则找到了要找的 数据,否则打印没有搜索到合适的数据。 16.1.2 二分搜索 • 顺序搜索思路简单,但效率不高。二分法是一种常见的高 效、通用的搜索算法,但是必须配合排序好的数据才能正 常的使用。如果将要搜索的数据使用数组进行保存并按照 从小到大的顺序进行排列,则二分法的基本步骤如下: • 取数组的中间值M与要搜索的数据G对比,如果M=G则完成了 搜索。如果M>G,在数据进行了排序的情况下,则要搜索的 数据G一定在数组的下半部分;如果M
还剩406页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

haozl07

贡献于2011-10-14

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf