科学网

 找回密码
  注册

tag 标签: Fortran

相关帖子

版块 作者 回复/查看 最后发表

没有相关内容

相关日志

[转载]Compiling Fortran Under Linux
GoogleMIT 2012-2-23 16:46
本文引自UBUNTU LINUX Fortran 编程中相关文件后缀 .a 静态库 (archive) .f , .for , .FOR .ftn *, .f90 *, .f95 *, .f03 * Fortran源代码(不需编译预处理) .F , .fpp , .FPP .FTN *, .F90 *, .F95* , .F03 * Fortran源代码(需要编译预处理) .r Fortran源代码(需要RatFor编译预处理) .o 对象文件 .s 汇编语言代码 .so 动态库 其中,标 * 的后缀名是gfortran的文件后缀,g77不能识别。 单个源文件生成可执行程序 传统的 Fortran 程序(也就是以 Fortran 77 为代表的)只能用大写字符书写,而且每行前六个字符为特定用途所保留。第一列为字符 C 或 * 所保留,用来表征整行都是注释。第二列到第六列是为标号预留的。代码从第七列开始,到72列结束(73列及以后将被直接忽略,可作注释)。下面是示例程序 采用的是传统的 Fortran 格式: C helloworld.f C PROGRAM HELLOWORLD WRITE ( * , 10 ) 10 FORMAT ( 'hello, world' ) END PROGRAM HELLOWORLD 编译器 gfortran 并不要求所有代码都大写──程序中任何关键词都可以用小写字母。下面的命令将该程序编译成可执行文件: $ gfortran helloworld.f -o helloworld 注意到:gfortran 默认会将 .f, .for, .fpp, .ftn, .F, .FOR, .FPP 和 .FTN 结尾的文件作为固定格式处理,而将.f90, .f95, .f03, .F90, .F95 和 .F03 结尾的文件作为自由格式来处理。如果我们将上面程序文件重命名为 helloworld.f90,那么我们必须手动指定其为固定格式: $ mv helloworld.f helloworld.f90 $ gfortran helloworld.f90 -ffixed-form -o helloworld Fortran 90及以后的标准允许并鼓励用自由的格式书写 Fortran 代码。注释以感叹号(!)开始直到行尾。先前的程序采用自由格式重写如下,其中语句、标号都可从任一列开始: ! helloworldff.f90 ! Program Helloworld write ( * , 10 ) 10 format ( 'hello, world' ) end Program Helloworld 后缀名为 .f90,故 gfortran 将其作为自由格式处理 $ gfortran helloworldff.f90 -o helloworldff 同样,如果将程序重命名为传统后缀名,那么要通过在命令行中加入选项 -ffree-form 进行编译,如下: $ mv helloworldff.f90 helloworldff.for $ gfortran -ffree-form helloworldff.for -o helloworldff 由于两种格式的具有很大的区别,程序书写是只能选择其中的一种格式进行书写。注意:遵守后缀约定是很重要的。 多个源文件生成可执行程序 命令 gfortran 可将多个 fortran 源码文件编译链接成为一个单一的可执行程序。下面列出了一个保存在文件 caller.f 中的简单程序的主体部分,它调用一个函数并显示出结果: C caller.f C PROGRAM CALLER I = Iaverageof ( 10 , 20 , 83 ) WRITE ( * , 10 ) 'Average=' , I 10 FORMAT ( A,I5 ) END PROGRAM CALLER 名为 Iaverage 函数定义在另一个独立的源文件中,如下: C called.f C INTEGER FUNCTION Iaverageof ( i,j,k ) Iaverageof = ( i + j + k ) / 3 RETURN END FUNCTION Iaverageof 通过下面的语句这两个源码文件可被编译链接成一个名为 caller 的可执行程序: $ gfortran caller.f called.f -o caller 同样的结果可由下面的命令序列得到──先将每一个源码文件编译成对象文件,而后将对象文件链接为可执行程序: $ gfortran -c caller.f -o caller.o $ gfortran -c called.f -o called.o $ gfortran caller.o called.o -o caller 生成汇编代码 选项 -S 指示编译器 gfortran 生成汇编语言代码然後结束。要得到我们本文先前的 helloworld.f 例子的汇编代码,只需输入以下命令: $ gfortran -S helloworld.f 生成的汇编语言文件名为 helloworld.s。汇编语言的具体形式依赖于编译器的目标平台。 编译预处理 编译以 .F, .fpp, .FPP, .FTN, .F90, .F95 和 .F03 结尾的文件时,在它真正编译之前需要预处理。预处理器原本是为协助 C 语言工作所设计的。下面的例子是一个自由格式的 Fortran 程序,它通过预处理器将一个函数包含进主程序: ! evenup.F90 ! #define ROUNDUP #include "iruefunc.h" ! program evenup do 300 i = 11 , 22 j = irue ( i ) write ( * , 10 ) i,j 300 continue 10 format ( I5,I5 ) end program evenup 函数 irue() 的源代码保存在文件 iruefunc.h 中,根据宏 ROUNDUP 所定义的值的不同将产生不同的编译结果。该函数将任何一个奇数近似为一个偶数。默认情况下,它向下取近似,但是当 ROUNDUP 被定义时,该函数将向上取近似而得到一个偶数。ireu() 的函数体如下: integer function irue ( i ) k = i / 2 k = k * 2 if ( i . EQ . k ) then irue = i else #ifdef ROUNDUP irue = i + 1 #else irue = i - 1 #endif end if end function irue 下面的命令将该程序编译成可执行文件: $ gfortran evenup.F90 -o evenup 采用自由格式写程序以利用预处理器不是必须的。固定格式程序也可进行编译预处理,下面的程序也是有效的: C adder.F C #define SEVEN 7 #define NINE 9 C program adder isum = SEVEN + NINE write ( * , 10 ) isum 10 format ( I5 ) end program adder 下面的命令将该程序编译成可执行文件: $ gfortran adder.F -o adder 理解gfortran是gcc的前端 像 g++ 一样,gfortran 也只是设置过 Fortran 程序所需基本环境的 gcc 的一个前端。本文一开始的例子我们可以通过下面 gcc 的命令来编译: $ gcc helloworld.f -o helloworld -lgfortran -lgfortranbegin 库文件 libgfortranbegin.a (通过命令行选项 -lgfortranbegin 被调用) 包含运行和终止一个 Fortran 程序所必须的开始和退出代码。库文件 libgfortran.a 包含 Fortran 底层的输入输出等所需要的运行函数。当运行 gfortran 时,会自动链接这两个库。这和下面的命令是等价的: $ gfortran helloworld.f -o helloworld 当我们运行 gfortran 时,实际上运行并不是这个编译器,而是编译器驱动器。该驱动器解析命令行中所给出的选项,然后才调用真正的编译器,汇编器和链接器。默认情况下,编译器驱 动器根据命令行中给定的文件的后缀决定它自己下一步的动作:一个名为 foo.c 将传递给 C 编译器,而名为 foo.f95 的文件将传递给 Fortran 95 的编译器,等等。 理解了这一点,我们就可以知道 gcc helloworld.f 将自动调用 fortran 的编译器。只不过我们要为链接器指定必要的库。 理解了这一点,我们可以知道 gfortran helloworld.c 可以编译一个 c 程序,gfortran helloworld.cpp -lstdc++ 编译的是一个 C++ 程序。 特别:在Ubuntu下,NTFS,FAT格式分区的任何文件都没有可执行权限!也就是说,必须把代码放在自己的Home目录,然后编译运行才能成功。
个人分类: PROGRAM|611 次阅读|0 个评论
[转载]gfortran,g77,g95 Introductions
GoogleMIT 2012-2-23 16:35
Gcc Fortran Intro 出自Ubuntu中文 在 GCC 4.0 之前, g77 是 GCC 的一部分;此后, gfortran 是 GCC 的一部分。 g95 是一个基于 GCC 的 Fortran 编译器,它不是 GCC 的一部分。 g77介绍 g77 是 Fortran77 的编译器。它对 Fortran 77 标准提供完备的支持,并支持 Fortran 90 和 95 的部分特性。 由于 Fortran 77 标准在数值计算中的影响力,g77 可能是应用最广的Fortran编译器。 在 GCC 4.0 之前,g77 是 GCC 的一部分,但现在,g77 已经停止开发。 g77为何不再被支持 : gcc-4.0 改变了 gcc 中所有语言的前端界面。由于缺少志愿者和公司来更新 g77 到 gcc-4.0 的架构,因此它被废弃了。不同于 g77,gfortran 项目处于活跃开发期,因此它 取代了 g77 的位置。 这是一篇 g77使用入门 注意 :从8.10 开始,Ubuntu 软件仓库中不再包含 g77 软件包 gfortran介绍 GNU 的 Fortran 95 编译器,支持Fortran95和一部分Fortran2003的功能。 取代 g77 集成在 GCC 4.0 及以後版本中 这是一篇 gfortran使用入门 关于g95 gfortran 不是 g95 : gfortran 是一个 Fortran 95 的编译器,它是 GCC 的一部分。 g95 是另一个 Fortran 95 的编译器,它是一个基于 GCC 的编译器。 历史 : Andrew Vaught 在 2000 年上半年创建了 g95──一个使用 GCC 做后端的开放源代码的 Fortran 95 编译器。在随后的两年里,这是一个多人协作的项目,但是 2002 年下半年 Andrew Vaught 决定单独开发 g95。2003 年 1 月,gfortran 项目创建,它建立在当时 GPL 授权的 g95 源码的基础上,目的是允许协同开发并与 GCC 代码集成。 从那时起,Andrew 一个人在持续地开发 g95,g95 与 gfortran 的差别也越来越大。因此,gfortran 项目组也无法为 g95 提供支持或建议。
个人分类: PROGRAM|666 次阅读|0 个评论
修改6Sv4.1源码,生成查找表LUT文件
热度 1 wishmo 2011-12-16 10:41
逐像元大气校正,常预先计算查找表(LUT,LookUp Tabel),6S大气辐射传输模式也可以用来计算LUT。但6S源程序输出信息多,且浮点数输出精度低,不利于提取关键信息生成LUT,本文描述了怎样修改6S源码以生成LUT。 首先确定LUT内容要素。 阅读MODIS M?D04 气溶胶产品生成算法文档(NASA相关网页),归纳了以下查找表要素: 1) 太阳天顶角 观测天顶角 太阳方位角观测方位角之差(相对方位角) 散射角 2) 大气模式 3) 气溶胶模式 4) 550nm气溶胶光学厚度 5) 波段号 6) 大气透过率 7) atm. intrin. ref.(个人理解,这是大气程辐射换算后的反射率,用于校正计算) 8) total sca. (总散射,包括rayleigh散射和气溶胶散射,用于校正计算) 9) 表观反射率 10) 校正后的地表反射率 11) xa xb xc参数(物理意义不明,用于校正计算) 其次,阅读源码,明确LUT各要素在6S源码中的变量名。 6S大气校正计算源码(Excel验证中采用此公式) rog=rapp/tgasm rog=(rog-ainr(1,1)/tgasm)/sutott/sdtott rog=rog/(1.+rog*sast) xa=pi*sb/xmus/seb/tgasm/sutott/sdtott xb=srotot/sutott/sdtott/tgasm xc=sast 由计算源码确定需输出的变量。下面是输出LUT要素的Fortran77代码示例,建议放在源码main.f中stop一句之前。 write(*,*) asol,phi0,avis,phiv,adif,phi,idatm,iaer,v,taer55, 1 iwave,tgasm,ainr(1,1),sutott*sdtott,rapp,rog,xa,xb,xc 其中,asol是太阳天顶角,phi0是太阳方位角,avis是观测天顶角,phiv是观测方位角,adif是散射角,phi是相对方位角,idatm是大气模式号,iaer是气溶胶模式号,v是水平能见度,taer55是550nm气溶胶光学厚度,iwave表示波段号,tgasm表示大气透过率,ainr(1,1)是大气本身的反射率(姑且这么理解),sutott*sdtott表示总散射,rapp是表观反射率,rog是校正后的地表反射率,xa,xb,xc是校正计算参数。Fortran77每行长度不能超过80个字符,续前行需在特定位置指明(示例中的iwave前的1即表示续行)。 该示例源码没有指定任何输出样式。浮点数会按默认样式输出,小数点后的位数比较多,精度较好。挑选一个查找表文件用Excel验证后表明,excel公式计算值与6S输出值之间最大误差小于1E-7。说明方法是可行的。 再次,编译源码,编写Shell脚本: 编译环境:OpenSUSE操作系统 g95编译器,版本未知。 编译命令:g95 *.f -o sixs(在BRDF相关代码处可能有几个warning,本文不涉及BRDF,故暂不修改调试。在Windows下用f77编译,无warning,编译通过) 生成LUT的bash脚本getLUT.sh: function LUTCalc(){ #{42,44,48,25,27,30} IBand=$1 echo "Band ${IBand} is running" for Iref in {0.1,0.5} do fstat=${IBand}"_"${Iref}.res echo "asol,phi0,avis,phiv,adif,phi,idatm,iaer,v,taer55, iwave,tgasm,ainr,tott,rapp,rog,xa,xb,xc" ${fstat} for SolarZ in {0,15,30,45,75} do for SolarAz in {0,45,90,135} do for ViewZ in {0,15,30,45,75} do for Iaer in {1,2,3,5,6,7} do for Idatm in {1,2,3,4,5,6} do for IAod in {0.1,0.2,0.5,1.0}; do # Run sixs and output ./sixs EOF | tail -n 1 ${fstat} 0 $SolarZ $SolarAz $ViewZ 0 3 15 $Idatm $Iaer 0 $IAod -0 -1000 ${IBand} 0 0 0 0.0 -$Iref EOF done done done done done done done } function getLUT() { echo "LUT is Calcing" for iwave in {42,44,48,25,27,30} { LUTCalc ${iwave} } } 最好晚上计算早晨看结果,如果CPU给力的话,几个小时后就可以得到结果。 下面是生成的LUT示例: asol,phi0,avis,phiv,adif,phi,idatm,iaer,v,taer55, iwave,tgasm,ainr,tott,rapp,rog,xa,xb,xc 0.0000000 0.0000000 0.0000000 0.0000000 180.00000 0.0000000 1 1 63.664398 0.10000000 25 0.98984975 6.89081103E-02 0.80637234 0.10000000 3.87301818E-02 1.98730151E-03 8.71319696E-02 0.14777245 0.0000000 0.0000000 0.0000000 0.0000000 180.00000 0.0000000 1 1 26.739149 0.20000000 25 0.98984975 7.75793791E-02 0.76532620 0.10000000 2.94530466E-02 2.09388486E-03 0.10336593 0.16389242 0.0000000 0.0000000 0.0000000 0.0000000 180.00000 0.0000000 1 1 8.4940033 0.50000000 25 0.98984975 0.10173188 0.64870018 0.10000000 -2.69861287E-03 2.47033220E-03 0.15994170 0.20088956 0.0000000 0.0000000 0.0000000 0.0000000 180.00000 0.0000000 1 1 3.5674956 1.0000000 25 0.98984975 0.13688390 0.48083964 0.10000000 -7.89646432E-02 3.33272247E-03 0.29038188 0.24035060 ……
个人分类: 6S|3934 次阅读|1 个评论
intel visual fortran 备忘
Daniel1985 2011-11-19 22:11
1 在vs2008如何设置可以显示行号? 菜单-工具-选项在新窗口中左面树菜单中展开“文本编辑器”,找到子项“所有语言”在右面的面板中 显示的行号前面打勾 2 运行之后,dos窗口一闪而过的解决 stop的上一行加pause, 这样就能按回车以后再结束程序了 (注意不能在stop之后)。 比如程序: program main implicit none write(*,*) Hello! pausestop end 其实这是因为直接用F5 start debugging的原因 如果用ctrl+F5 start without debugging就不会出现上述的问题。 3 声明语句中的双冒号 加两个冒号是新的语法。 Integer :: iA , iB 这样子与 Integer iA , iB 没有任何区别 但是当变量声明包含修饰词,比如 Allocatable , Private , Public , Parameter , Intent , Optional 等的时候,两个冒号就非常有必要了,它表示修饰词结束了。 比如: Integer,Allocatable,Public :: iA( : ) , iB( : ) 就不能省略两个冒号。 推荐你定义变量时都采用 两个冒号的 新写法。 有了两个冒号你在声明是可以直接符值
个人分类: ABAQUS/PYTHON/FORTRAN|5526 次阅读|0 个评论
【Fortran】format语句中如何指定重复数目的格式描述符
热度 1 ddbb12 2011-10-12 09:37
好久没有写过与fortran程序相关的东西了。Fortran语言作为至今仍在研究界有很大的市场,因其语法简单,规则较少,使用灵活,的确是吾辈求解方程、数值模拟的一大神器。具体的关于Fortran语言的介绍和基本规则,可参考很多的参考书和搜索引擎。笔者本人学习的是 《 Fortran 95 程序设计》彭国伦 ,可参考本博书评。 昨天想到了这样一个问题: 例如,有一个可变数组(allocatable array)是N维的,dimesion :: a(N), 如果N=10,那么可以用 write(*,100)(a(i)i=1,10) 100 format(10(I3)) 之类的代码来进行输出操作。 但是,如果一旦N发生变化,那么只有修改format语句才能正确输出。而且,为了保持所有平台的兼容性,应该要求每次N发生变化的时候,都应该修改代码。这有失一般性。 那么 有没有可能在fortran的format语句中用变量来表示描述符的个数, 或者说, 有没有可能将format写成: format(N(I3))之类的格式呢? 实际上这个问题可以有多种解决途径: 1、 据说在CVF下可以使用一个尖括号来写成如nI3,从而来表示可变的重复次数。CVF的手册上也是如此说的,但是由于没有环境,因此没有做过测试。 2、可先设计一个不限长度的字符串,然后通过显示循环,将要输出的a(i)的每个元素都以内部文件的形式写入该字符串中。最后只需要输出该字符串即可。 3、实际上最后解决问题的方法是这样的,只需要将format中这个要重复描述符前面的这个数字设计成一个比待输出的元素个数的大的整数即可。该方法已经在gfortran下调试通过,没有任何错误。 当然,这种方法也有缺陷,如果这些重复输出元素不是输出列表中的最后一项就会出现格式错乱。、 话说,格式这种事情本来就很容易的错乱的。人们总是希望输出的东西好看,整齐,有规律,所以才有了格式这个玩意。Anyway,也算是个小问题。
个人分类: 计算机|12911 次阅读|1 个评论
[转载]Writing Matlab MEX files using Fortran 90 / 95
Amedee 2011-6-10 19:45
Writing Matlab MEX files using Fortran 90 / 95 (and the Intel Fortran Compiler) (Update: This text refers primarily to Matlab 6.5 (aka R13) on Linux. The new Matlab 7 introduces some serious problems with non-GNU compilers due to throwing C++ exceptions. Read on below for details.) The Mathworks do not officially support Fortran 90 / 95, but thanks to people like Benjamin Barrowes who wrote Matlab2FMex and the people at NAGWare it is easy to figure out how to write your MEX files in Fortran 95 without using the awkward mxCopy-functions. Lately, I also found this page which describes in detail all three possible ways of writing MEX files entirely in Fortran. However, of these three ways, two make a local copy of the data which is less than optimal when dealing with big data sets. The trick is to declare the pointer you get from mxGetPr as an integer pointer and pass it along with the array dimensions to a subroutine which expects a double precision array. Until now this works for me without any problems (Linux w/ Intel Compiler), but note that this procedure may not work with every compiler or on any platform. I may also add that this procedure does NOT work with the F programming language as it forbids passing "wrong" arguments to subroutines. First, we will have to explicitly define the used Matlab API functions, at least those who have a return value: module mexf90 interface function mxGetPr(pm) integer,pointer :: mxGetPr integer :: pm end function mxGetPr function mxGetM(pm) integer :: mxGetM,pm end function mxGetM function mxGetN(pm) integer :: mxGetN,pm end function mxGetN function mxCreateDoubleMatrix(m,n,type) integer :: m,n,type,mxCreateDoubleMatrix end function mxCreateDoubleMatrix function mxGetScalar(pm) integer :: pm double precision :: mxGetScalar end function mxGetScalar end interface end module mexf90 Just save this file under the name "mexf90.f90" and compile it. (For those unfamiliar with modules: you must prevent the compiler from also linking the file - this is usually done with the "-c" switch.) This module contains only the API functions we need, but adding further functions is straightforward. You may also click here to obtain a module file with more function definitions. Now the actual function, consisting of the subroutine "mexFunction" (the gateway to Matlab) and an example subroutine "scalarMult" which multiplies the matrix A with a scalar "alpha" and returns the result in matrix B. The comments should make clear how these functions work: subroutine mexFunction(nlhs, plhs, nrhs, prhs) use mexf90 ! API function definitions implicit none integer, intent(in) :: nlhs, nrhs integer, intent(in), dimension(*) :: prhs integer, intent(out), dimension(*) :: plhs integer :: m,n integer, pointer :: A,B ! These are the pointers to the matrix data double precision :: alpha ! Check input arguments if(nrhs /= 2) then call mexErrMsgTxt('Function requires two input arguments.'); end if ! Get data and size of the input matrix A = mxGetPr(prhs(1)) m = mxGetM(prhs(1)) n = mxGetN(prhs(1)) ! Get scalar value alpha = mxGetScalar(prhs(2)) ! Create output matrix plhs(1) = mxCreateDoubleMatrix(m,n,0) B = mxGetPr(plhs(1)) ! Call subroutine for multiplication call scalarMult(A,B,alpha,m,n) end subroutine mexFunction subroutine scalarMult(A,B,alpha,m,n) implicit none integer :: m,n double precision :: alpha ! Now define the matrices with the actual data type and dimension double precision, dimension(m,n) :: A,B ! Now do something useful... B = alpha * A end subroutine scalarMult For more complex functions it is a bit tedious to pass the dimensions along with the data pointers - if someone finds a better solution, please let me know! The Intel Fortran Compiler for Linux is free for non-commercial use, but you probably already know that... These are the relevant parts in the mexopts.sh file for MEXing the above file with the IFC FC='ifort' FFLAGS='-fPIC -u -w95 -warn all' FLIBS='$RPATH $MLIBS -L/opt/intel_fc_80/lib -lm' FOPTIMFLAGS='-O3' FDEBUGFLAGS='-g' LD='icc' LDFLAGS='-pthread -shared' LDOPTIMFLAGS='$FOPTIMFLAGS' LDDEBUGFLAGS='-g' As you can see, I do not use any map-files as that didn't work for me. It is important to note that linking with "ifort" will usually NOT work, the above file uses the Intel C++ Compiler "icc" instead. If you do not have icc installed, the GNU compiler "g++" should also work. If you also want to use functions like "print", e.g. for debugging purposes (mexprintf does not allow format strings in Fortran...), you will also have to link "ifcore", but you should remove that for the release version of your code. If you get error messages about unresolved symbols when executing the mex-file, make sure that the Intel libraries are scanned by ldconfig or put them into your LD_LIBRARY_PATH before starting Matlab. If you get an error message saying that __MAIN couldn't be found, check again that you are NOT linking with ifort. If you still have problems with unresolved symbols, try using the "-static-libcxa" switch. Now happy MEXing! Oh, and also check out LAPACK95, incredibly useful for porting M-files to MEX. And Matran looks very promising, although I didn't use it yet. Update: Problems with Matlab 7 (aka R14) The new Matlab 7 now uses C++ exceptions in library functions like mexErrMsgTxt. As Matlab was built on Linux with the GNU compiler, this breaks practically all other compilers which are unable to correctly propagate these exceptions - including the Intel Fortran Compiler (and also Ocaml, by the way...). (A sidemark: the Intel C Compiler (icc) will usually work as long as you always compile with "-Kc++"). This means, as soon as a Fortran MEX file compiled with the Intel Fortran Compiler calls mexErrMsgTxt, the complete Matlab session will abort and return to the shell, as there is no handler to catch the exception. I doubt that there exists an easy solution to this problem, as the implementation of exception handling in C++ differs between different compilers. Thus, if you want to write your files entirely in Fortran 95 and you want to use the IFC, don't call mexErrMsgTxt or your session will abort. If you have to use mexErrMsgTxt, you are for now forced to use a GNU Fortran compiler. Although Fortran and C don't "know" exception handling, the GNU C and Fortran compilers have a switch "-fexceptions" which includes code to correctly propagate these exceptions. For Fortran 95 you can use either g95 or GNU Fortran 95, both work for creating Matlab MEX files as long as you compile with "-fexceptions". However, both compilers are not yet stable, but they are making rapid progress recently.
个人分类: 程序设计|3501 次阅读|0 个评论
[转载]转自:编程爱好者论坛 Fortran区的一篇置顶文章
gibbscat 2011-6-3 11:15
最近,好几个人问到公共块出错的问题,都是出于一个原因,特在这里把老贴整理一下供大家参考。 问题: 20924057 Compiling Fortran... D:\FRAME2D.FOR D:\FRAME2D.FOR(314) : Warning: Because of COMMON, the alignment of object is inconsistent with its type COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6)--------------------------^ D:\FRAME2D.FOR(314) : Warning: Because of COMMON, the alignment of object is inconsistent with its type COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6) ---------------------------^ 解答: 这是老Fortran的规则。看你的公共语句: COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6),SF(6),T(3,3) 根据程序的隐含规则,LM(6),ND一共是7个整型数,每个整型数4个字节,共28个字节,不是8字节的整倍数。紧跟着是双精度的ASA(6,6),每个数是8个字节,可是ASA(1,1)不能从8字节整倍数位置开始存储,这就是the alignment of object is inconsistent with its type 的意思。 对于警告错误,可以忽略,一般不致于影响执行结果。如果要改的话,有几种办法: 1)在公共语句中,把双精度数放在前边,整形数跟在后边; 2)在ASA()前插一个整型变量(哪怕是没用的),用来占用4个字节,以使得后面的双精度数可以从8字节整数倍位置开始存储。 准则:作为好的编程习惯,建议在公共块中,把实型变量排在前边,把整型数据放在后边,就不会有对位不整的错误! 仅供参考。 补充:(05.06.01) 公共块不是用堆栈实现的,是内存中的一段连续存储的数据。 按照Fortran的规定,当读取双精度数据时,总是假定前面的数据长度是双精度数字节长度(8个字节)的整数倍。 对于本例,ASA(1,1)从第29个字节开始存放8个字节;可是读取的时候,要从第33个字节(28不是8的倍数,32是28之后最小的8的倍数)开始读入8个字节,这就是定位(alignment)错误。 所以F90之后不提倡用公共块共享数据,而可用更为灵活的module来代替公共块共享数据。 公共块是过时的语言功能
个人分类: 未分类|2505 次阅读|0 个评论
fortran中有关指针的用法总结
热度 2 HL2004 2011-6-2 16:58
原来在C和C++ 中指针这一章让我痛苦死,后来发现在实际中如此有用,现在又在学Fortran,发现这两门语言的指针还是有很多不同的地方,为了便于自己学习和理解,特将有关fortran指针的用法写下来,以便随时查阅。它最简单的应用可以用来保存变量,或者是动态使用内存,或者是用于数据结构. 1、标准fortran指针并不代表变了在内存中的地址,而是代表这个变量的别名,相当与C中的引用。通过指针,同一个变量存储单元可以通过多个变量名来访问。当指针被声明后,程序并不会立即给它分配存储空间,而是要等到通过allocate语句进行动态内存分配或者通过指针赋值语句于一个目标变量联合起来后才会分配。目标变量都必须在程序的声明阶段被赋予target属性。 2、C++里面指针是一个独立的变量,它的作用就是为了保持它所指向的变量在内存中的地址,而fortran的指针则是共享了与它联合的变量的存储空间。当指针执行重新设置时,应考虑对原来分配的内存进行必要的处理,处理的方法就是将其交给其他的指针的管理,或者通过deallocated释放掉,否则会在计算机中形成一块已经被配置却无法进行操作的被丢弃内存,即所谓的指针悬挂问题。指针的状态可以通过ASSOCIATED(pointer,target)来检查指针释放设置了指向,返回值为逻辑型变量。还可通过NULLIFY来解除指针当前的联合状态,并使其指向不能使用的内存地址。 3、指针数组,它用来指向数组、数组片段或者数组元素或者用来连续分配内存空间。声明指针数组时冰箱将数组声明称延迟数组形式,但指针P指向数组中的第一个元素时,必须写成如下形式,p=N(1:1),而不允许写成p=n(1)。指针数组与目标数组建立联合时,必须具有相同的秩。以为指针数组也可以指向二维或多位目标数组中的某一位或某一维的某一片段,但是多位指针数组不允许指向比自己维数低的目标数组。 4、在写module的时候,如果输出为指针,并且此指针指向为一个在子程序的一个值,需要把这个值的定义放在module下面,不能放在子程序下面,最好加上private属性即可,如果不这样,主程序就不能读取这个指针值。 5、指针也可以作为参数传递近过程中(包括函数子程序和子例行程序),并可以作为函数的结果返回主调程序,但是需要注意,当使用指针作为参数传递进过程时,在主调过程中必须对被调函数进行显式得得接口说明,指针参数在声明时不需要使用intent属性进行说明,如果函数的返回值是指针,也需对该函数进行显式的接口说明。 6、指针数组与可分配数组非常相似,但是可分配数组仅存在于声明它的过程中,过程执行完毕后由系统自动收回它所占的存储空间,而指针数组必须通过deallocate语句来释放存储空间。
30040 次阅读|2 个评论
[转载]Linux下编译Fortran
wlusheng 2011-5-26 09:08
注意:本文是关于 gfortran 的文章。如果你不清楚 gfortran , g77 , g95 等等的概念的话,不妨看看 GCC 的 Fortran 语言编译器介绍 ;如果你要用 g77 的话,这是一篇 g77 入门 。 href="http://wiki.ubuntu.org.cn/index.php?title=Compiling_Fortranaction=edit§ion=2" Fortran 编程中相关文件后缀 .a 静态库 (archive) .f,.for,.FOR .ftn*,.f90*,.f95*,.f03* Fortran 源代码(不需编译预处理) .F,.fpp,.FPP .FTN*,.F90*,.F95*,.F03* Fortran 源代码(需要编译预处理) .r Fortran 源代码(需要 RatFor 编译预处理) .o 对象文件 .s 汇编语言代码 .so 动态库 其中,标 * 的后缀名是 gfortran 的文件后缀, g77 不能识别。 href="http://wiki.ubuntu.org.cn/index.php?title=Compiling_Fortranaction=edit§ion=3" 单个源文件生成可执行程序 传统的 Fortran 程序(也就是以 Fortran77 为代表的)只能用大写字符书写,而且每行前六个字符为特定用途所保留。第一列为字符 C 或 * 所保留,用来表征整行都是注释。第二列到第六列是为标号预留的。代码从第七列开始,到 72 列结束( 73 列及以后将被直接忽略,可作注释)。下面是示例程序采用的是传统的 Fortran 格式: Chelloworld.f C PROGRAMHELLOWORLD WRITE(*,10) 10FORMAT('hello,world') ENDPROGRAMHELLOWORLD 编译器 gortran 并不要求所有代码都大写 ── 程序中任何关键词都可以用小写字母。下面的命令将该程序编译成可执行文件: $gfortranhelloworld.f-ohelloworld 注意到: gfortran 默认会将 .f,.for,.fpp,.ftn,.F,.FOR,.FPP 和 .FTN 结尾的文件作为固定格式处理,而将 .f90,.f95,.f03,.F90,.F95 和 .F03 结尾的文件作为自由格式来处理。如果我们将上面程序文件重命名为 helloworld.f90 ,那么我们必须手动指定其为固定格式: $mvhelloworld.fhelloworld.f90 $gfortranhelloworld.f90-ffixed-form-ohelloworld Fortran90 及以后的标准允许并鼓励用自由的格式书写 Fortran 代码。注释以感叹号(!)开始直到行尾。先前的程序采用自由格式重写如下,其中语句、标号都可从任一列开始: !helloworldff.f90 ! ProgramHelloworld write(*,10) 10format('hello,world') endProgramHelloworld 后缀名为 .f90 ,故 gfortran 将其作为自由格式处理 $gfortranhelloworldff.f90-ohelloworldff 同样,如果将程序重命名为传统后缀名,那么要通过在命令行中加入选项 -ffree-form 进行编译,如下: $mvhelloworldff.f90helloworldff.for $gfortran-ffree-formhelloworldff.for-ohelloworldff 由于两种格式的具有很大的区别,程序书写是只能选择其中的一种格式进行书写。注意:遵守后缀约定是很重要的。 多个源文件生成可执行程序 命令 gfortran 可将多个 fortran 源码文件编译链接成为一个单一的可执行程序。下面列出了一个保存在文件 caller.f 中的简单程序的主体部分,它调用一个函数并显示出结果: Ccaller.f C PROGRAMCALLER I=Iaverageof(10,20,83) WRITE(*,10)'Average=',I 10FORMAT(A,I5) ENDPROGRAMCALLER 名为 Iaverage 函数定义在另一个独立的源文件中,如下: Ccalled.f C INTEGERIaverageof(i,j,k) Iaverageof=(i+j+k)/3 RETURN ENDIaverageof 通过下面的语句这两个源码文件可被编译链接成一个名为 caller 的可执行程序: $gfortrancaller.fcalled.f-ocaller 同样的结果可由下面的命令序列得到 ── 先将每一个源码文件编译成对象文件,而后将对象文件链接为可执行程序: $gfortran-ccaller.f-ocaller.o $gfortran-ccalled.f-ocalled.o $gfortrancaller.ocalled.o-ocaller 生成汇编代码 选项 -S 指示编译器 gfortran 生成汇编语言代码然後结束。要得到我们本文先前的 helloworld.f 例子的汇编代码,只需输入以下命令: $gfortran-Shelloworld.f 生成的汇编语言文件名为 helloworld.s 。汇编语言的具体形式依赖于编译器的目标平台。 href="http://wiki.ubuntu.org.cn/index.php?title=Compiling_Fortranaction=edit§ion=6" 编译预处理 编译以 .F,.fpp,.FPP,.FTN,.F90,.F95 和 .F03 结尾的文件时,在它真正编译之前需要预处理。预处理器原本是为协助 C 语言工作所设计的。下面的例子是一个自由格式的 Fortran 程序,它通过预处理器将一个函数包含进主程序: !evenup.F90 ! #defineROUNDUP # include"iruefunc.h" ! programevenup do300i=11,22 j=irue(i) write(*,10)i,j 300continue 10format(I5,I5) endprogramevenup 函数 irue() 的源代码保存在文件 iruefunc.h 中,根据宏 ROUNDUP 所定义的值的不同将产生不同的编译结果。该函数将任何一个奇数近似为一个偶数。默认情况下,它向下取近似,但是当 ROUNDUP 被定义时,该函数将向上取近似而得到一个偶数。 ireu() 的函数体如下: integerirue(i) k=i/2 k=k*2 if(i.EQ.k)then irue=i else #ifdefROUNDUP irue=i+1 #else irue=i-1 #endif endif endirue 下面的命令将该程序编译成可执行文件: $gfortranevenup.F90-oevenup 采用自由格式写程序以利用预处理器不是必须的。固定格式程序也可进行编译预处理,下面的程序也是有效的: Cadder.F C #defineSEVEN7 #defineNINE9 C programadder isum=SEVEN+NINE write(*,10)isum 10format(I5) endprogramadder 下面的命令将该程序编译成可执行文件: $gfortranadder.F-oadder 理解 gfortran 是 gcc 的前端 像 g++ 一样, gfortran 也只是设置过 Fortran 程序所需基本环境的 gcc 的一个前端。本文一开始的例子我们可以通过下面 gcc 的命令来编译: $gcchelloworld.f-ohelloworld-lgfortran-lgfortranbegin 库文件 libgfortranbegin.a( 通过命令行选项 -lgfortranbegin 被调用 ) 包含运行和终止一个 Fortran 程序所必须的开始和退出代码。库文件 libgfortran.a 包含 Fortran 底层的输入输出等所需要的运行函数。当运行 gfortran 时,会自动链接这两个库。这和下面的命令是等价的: $gfortranhelloworld.f-ohelloworld 当我们运行 gfortran 时,实际上运行并不是这个编译器,而是编译器驱动器。该驱动器解析命令行中所给出的选项,然后才调用真正的编译器,汇编器和链接器。默认情况下,编译器驱动器根据命令行中给定的文件的后缀决定它自己下一步的动作:一个名为 foo.c 将传递给 C 编译器,而名为 foo.f95 的文件将传递给 Fortran95 的编译器,等等。 理解了这一点,我们就可以知道 gcchelloworld.f 将自动调用 fortran 的编译器。只不过我们要为链接器指定必要的库。 理解了这一点,我们可以知道 gfortranhelloworld.c 可以编译一个 c 程序, gfortranhelloworld.cpp-lstdc++ 编译的是一个 C++ 程序。 一.Fortran编译器的安装 Linux安装盘一般都自带有Fortran编译器,在SuSe9.1以前均带有g77,在Suse9.2以后为gfortran。但是,相对来说,由于g77和gfortran的编译的程序运行效率不是很高,所以都会选择再安装专业的Fortran编译器。 主流的Fortran 90/95编译器有PGI Fortran、HP Fortran Compiler(由Fortran PowerStation进化过来的)和Intel Fortran Compiler等。因为Intel Fortran Compiler9.1是Intel提供的免费的Non-Commercial版本,且在Intel平台上,具有较高的编译效率。它的发行版有 Windows和Linux两种。在此,主要介绍在SLES10.0 上安装 Intel Fortran Compiler 9.1 的过程。 1.下载安装包 本次安装包从ftp上下载,ifcliv91.bin,为光盘文件,可以直接拷贝到目录/home/hou/software/intel_fc_91下。 2.解压安装文件,命令如下: 编译器是安装在/opt/intel目录下,协议则在该目录下的licenses文件夹中。最后还要指定licenses的位置。具体命令如下: #mkdir -p /opt/intel/licenses #cp /home/hou/isoftware/intel_fc_91/Crack/i*.lic /opt/intel/licenses # export INTEL_LICENSE_FILE=opt/intel/licenses
个人分类: 计算机工具|0 个评论
[转载]科学计算的语言------Fortran95(著作)
swx0789 2011-4-6 14:03
第一篇 闲话 中国数学的 经典 著作大都以依据不同方法或不同类型分成 章节的问题集的形式出现。每一个别问题又都分成若干个 条目:条目一是“问”,提出有具体数值的问题;条目二 是“答”,给出这一问题的具体数值答案;条目三称为“术”, 一般说来乃是解答与条目一同种类型问题的普遍方法,实 际上就相当于现在计算机科学中的“ 算法 ”,但有时也相 当于一个公式或一个定理;条目四是“注”,说明“术” 的依据与理由,实质上相当于一种证明;宋元以来,可能 是由于印刷术的发达,往往加上条目五“草”,记述依据 “术”得出答案的详细计算过程。… 早在《九章算术》中,第八章方程章全章就是线性联立方 程组的解法。依刘微注:“并列为行,故谓之方程。”又 说:“令每行为率,二物者再程,三物者三程,皆如物数 程之。”当时以筹作算,依题意将数据按物数排成各行, 然后进行运算。…中学教科书解线性方程组的消元法,最 早即见之于《九章算术》。… 数学在中国古时历来称为算术。这正好反映了中国古算构 造性、计算性与机械化的特色。数学不仅为了 应用 ,即使 为了在纯数学范畴内获得具体结果,也一不能无算,二必 须有术。…中国古代算术的思想与方法,正好与近代计算 机的使用融合无间,也必将因此而重返青春,以另一种崭 新面貌在未来的数学发展中扮演重要角色。 ----吴文俊 《对中国传统数学的再认识》   -------------------------------------------------------------------------------- 吴文俊,中科院院士,我国最卓越的 现代 数学家之一,在拓扑学等领域取得世界性成就,近年积极推动数学机械化领域的发展。 第1章目的是计算 我们这本书描述了一门语言。不幸的是语言很难成为我们的爱好,更何况FORTRAN语言也无法仿效英语,能够被某些英语大师成功地转换为一种疯狂。因此几乎每一个学习过编程语言的人都会有一种痛苦地记忆,那就是一种极端缺乏趣味的痛苦,因为很容易我们就会忘记这种语言的用处。显然,如果一种工具连用处都不是那么明显的话,哪里还谈得上给人带来乐趣呢? 所以要想在FORTRAN语言的学习过程当中不至于总是遭遇无聊,唯一的解救之途就是一开始就要找到学习它的绝对充足的理由,并且在学习过程中还会不得不反复地依靠这个理由,来说服自己需要更加有耐心,更加能够忍受烦琐和约定,…逐渐地,也许我们能够从FORTRAN语言的惊人表达能力当中,体会到一种新的看待世界的角度,这大概会是我们享受那么多的枯燥之后,所能够得到的最佳回报。 所以我们不得不在进入正题之前,扯点闲话,看看学习FORTRAN的理由何在。 1.1始于计算,终于计算 在荒莽的历史尽头,我们常常听说人类之所以能够凭借虚弱的身躯,一次又一次地打退其他凶猛动物的猖狂进攻,而终于成就为今日当之无愧的“万兽之王”,这个原因有很多种解释,比如说是劳动,说是手,说是大脑,莫衷一是,各有各的 理论 和证据,确实是个很难取得定论的问题,因此我想提出一种新的解释,大概是不会遭到立刻的覆灭性打击。 人类的进化始于计算,我相信。 在有关非洲黑猩猩的纪录片里面,可以看到黑猩猩也会用手使用工具,一个黑猩猩家庭为了生活也勤于劳动,一个黑猩猩群落社会里面的形形色色的政治行为,显示它们的大脑也不是很闲,但有一次我看到黑猩猩们抢香蕉时,总是把自己已经抢到手的香蕉给弄丢了而无知觉,突然觉得它们最缺乏的也许是算数的能力。 所以我想,人类的起源一定跟计算有莫大的关系。 当然这些都是戏说而已,不过如果说人类的科学是从计算开始的,应该是不会有太大的争议的。 也许有人说人类的科学活动应该是起源于观察,但从动物行为学的角度来看,观察这种行为是很难界定的,而计算则是非常好界定的行为,因为最原始的计算行为显然应该就是数数。 可以想像,当人类开始意识到自我在这个世界的孤独存在时,对于日月星辰和风雨雷电的无常和力量该是多么地恐惧啊!而人类把自己从这种恐惧当中解救出来的第一个方法应该就是算数。 算数导致了人类最早的科学活动,即天文学和几何学。人们从对日月星辰的计数当中发现了天体的运行是有常的,而不是什么不可琢磨的妖魔鬼怪在主宰;从对土地与山河的度量中建立了明确的几何概念,从而对自己所生活的世界开始了真正的理解。而这种理解活动一旦开始,就无法停止了,演变至今,就构成了我们人类最值得自豪的成就-科学。 科学的历史表明了它的 基础 ,就是计算。 如果我们只是一味地观察,我们会纪录下来大量的经验,然而却无法理解这些经验的含义,更进一步,对于经验有了总结归纳之后,哪怕是进行计算的确切程度不同,也会导致不同的对于自然的理解。 一个很著名的例子,就是太阳系行星的运动的问题。 对于古人来说,所谓行星的意思就是那些星星是相对于恒星在明显地运动,在漫天的相互位置不变的恒星的对比之下,很少的这种匆匆行者是非常引人注目的。于是很早开始人们就纪录这些行星的运行轨道。在古希腊时代,最有名的关于这些行星运动的 研究 是托勒密完成的,他对有关行星的纪录数据进行了计算,得到了一个极其“圆满”的行星运动模型,即以地球为宇宙中心,行星围绕地球做严格圆周运动。现在看来,他的观测数据肯定不会是很精确,而他的计算也不会很准确,一直到开普勒,在获得了非常精确的行星轨道运行的数据的基础上,进行了大量的计算,最终得到了革命性的开普勒三定律。 接下来牛顿出场了,他更是一个计算天才,正是通过计算,从开普勒三定律获得了他的最伟大的发现,万有引力定律。 然后还是通过计算,我们现在可以运用牛顿万有引力公式的计算而把脚印留到月球的寂寞沙尘上。 还有一个非常独特的例子,就是中国的古代科学和技术,更是强烈地依赖计算。 相比于古希腊,中国的古代数学几乎完全是以计算为中心的,翻开中国的古代数学书,一个鲜明的特点就是几乎完全是针对问题的数学计算方面的内容,而很少有古希腊传统的公理,定理与证明。 实际上,古代中国在技术上一直领先于世界,很难说跟这种重视计算的风格没有关系。其中最有名的例子就是祖冲之关于圆周率的计算,而我们现在都很清楚,祖冲之的计算不仅是为圆周率提供了一个远比西方要精确的具体数值,更重要的是提供了一种非常通用的计算方法,即一种近似积分计算,而这种计算方法对于古代中国的技术进步的重要作用,正是一个表明计算的重要性的典范。 如果说计算在科学的早期发展中扮演了关键的作用,那么我们还是可以说计算仍然在现代科学技术扮演着关键的角色。 固然现代的科学技术领域广阔而渊深,然而哪里没有计算的身影呢?甚至在某些科学哲学家的看法里面,能否进行计算是衡量一个理论是否已经成为科学的一个部门的标准。也许这种说法不免偏激,但是计算对于科学与技术的意义,确实是一个深刻的问题。 我们不妨考察一下 物理学 里面计算所占据的地位,也许有助于更深地理解这个问题。 几乎是一直到十九世纪,物理学还是以实验为主要内容,进入二十世纪之后,一场物理学革命飞快地竖立了理论物理学的丰碑,足以与传统的实验物理学分庭抗礼。而这种局面在二战时期就开始改变,二战后在实验物理与理论物理之外,计算物理鼎足而立,导致这种变局的最关键的因素就是电子计算机的发明。 在计算机没有发明之前,人们不得不采取各种方式来回避过于庞大的计算问题,而大量的关键问题,又非得进行庞大的计算不可,以物理学为例,一旦我们透彻地解决了线性问题之后,接踵而来的就是远比线性现象更为广泛的非线性现象,对于非线性问题,我们已经掌握的解析方法极其贫乏,这就逼迫我们走上直接计算之路。 而除了非线性问题之外,物理学乃至整个自然科学看待世界的方式都是原子式的,即我们总是可以把一个现象理解为它的组成成分的相互作用的结果,于是我们就有了分子,原子等基本的科学观念,这种做法一直都是充满胜利的,然而这种胜利也是伴随着沉重代价的,即当我们重新面对复杂现象时,不得不从它的组成成分开始考虑,这就是统计物理学的基本思想,显然这种做法给我们带来了繁重的计算任务,因为迄今为止,我们人类对于一个包含超过了3个的,具有相互作用的成员的 系统 的精确解析求解,已经是有点一筹莫展了,更何况常见的那些动辄包含了成千上万成员的系统呢?因此在很大一部分情况下,我们唯一的选择,就是直接的计算。 因此我们可以说,计算确实是科学的基石之一。 至于我们所生活的这个几乎完全由科学技术所塑造的世界,哪里没有计算的身影呢?如果说计算帮助我们理解了自然,进一步我们还可以说计算创造了我们的生活。 任何一个科学上的对于自然的新的理解,要转变为为人所用的技术时,基本的途径就是计算。道理很显然,当我们要制造一种自然界从未出现过的物品时,我们需要的是具体的制造数据,而不能只是一堆公式,那么数据从哪里来呢?当然只能从对公式的计算得来。因此我们可以把计算理解为理论的实现途径。即理论无论是要反映真实世界,还是要体现到我们的实际物品上,它都需要通过一条叫计算的路,理论武装了我们的大脑,而计算则创造了我们的世界。 所以我们可以说,人类的历史,最早是始于计算,而这个历史的实现的最前沿,则仍然是终于计算。 1.2描述计算的语言 长久以来,人类的计算都是依靠自己来完成的,但如果仔细考虑一下,就会发现计算的实质是把一个经过清楚的分析,明确了求解的方法的问题, 分解 为明确,可行,而有限的步骤,显然这种工作本质上是机械性的,如果把已经得到求解方法的问题还交给人脑来计算,显然有浪费人力之嫌;从另外一个方面来讲,往往一个需要专门加以计算的问题,也是一个需要进行庞大的计算的问题,庞大到人力已经难以胜任的程度。 因此这两个方面都要求人类发明能够进行计算的机器,这就直接导致了计算机的发明。 今天当我们面临需要计算的问题时,对付问题的就不再只是一个大脑,而是一个大脑加上一台机器。所以接下来的问题就是如何让大脑与计算机协同工作,使得大脑能够专门用来寻求问题的算法,而计算机能够被用来计算解。 这种协同工作的第一个要求就是人需要能够把自己对于问题的计算过程的理解表示为机器的机械运动。首先人对于问题及其求解的理解是可以非常清晰地依靠数学语言来加以描述说明的。然后我们再看机器那一端。 所谓计算机就是能够自动执行一系列机器动作的机器,对于那些机器动作,我们可以把它们和基本的计算操作对应起来,这就使得有可能把计算问题的能行的计算步骤分解为有限的机器操作,这里剩下的问题就是一方面是我们熟知的描述问题求解的数学语言,另一方面就是描述机器操作的语言,这两个方面的语言如何对应的问题。 下面我们 讨论 一个非常简单的例子,用来说明这里面的语言翻译问题。 一个一元二次方程的求解是一个很简单的数学问题。设方程的形式为: ax^2+bx+c=0 那么方程的解的一般公式为: x1=(-b+(b^2-4ac)^(1/2))/2a x2=(-b-(b^2-4ac)^(1/2))/2a 注意我们现在的目的是计算,而不是求解析解,因此得到这个公式不是问题的结束,而是问题的开始,即运用这个公式,在a,b,c都有具体取值的情况下,求出具体的数值解。因为如果该方程描述的是一个抛体运动,那么我们需要知道的当然是解的数值,以便我们了解和控制该抛体运动。 所以我们还需要进一步把这个数学表述转述为一个具体的求值过程的描述如下: (1) 获得a,b,c的具体取值; (2) 根据a,b,c的具体取值计算 的值; (3) 如果 b^2-4ac大于0,那么分别计算上面的两个公式的数值,得到两个不同的实数根; (4) 如果b^2-4ac =0,那么计算公式 ,得到的x的值就是方程的两个相同的根; (5) 如果b^2-4ac 小于0,那么分别计算上面的两个公式的数值,得到两个不同的复数根。 这样得到的上面的5个计算步骤描述,才是希望计算机所执行的计算过程的一个大概描述。当然这个描述是基于我们已经获得的对于一元二次方程的求解问题的数学上的彻底了解。 那么描述计算机的操作的语言是什么样的形式呢? 我们知道所谓计算机的工作在本质上是运行一些基本的机械电子操作,固然我们可以把那些操作根据运算法则而定义为相应的运算,例如一个开关电路的 信号 叠加,可以适当地定义为二进制数值的加法,但显然不可能期望依靠这种物理现象能够直接地表达更加复杂的计算,例如上面公式里面的开平方。幸好我们依靠数学,可以做到把任何复杂的计算分解或近似分解为极少数简单计算的叠加与组合。因此最终我们已经至少在理论上可以期望使用计算机来完成那些具有明确求解过程的计算任务。 然而问题是当我们需要向计算机提交一个数值计算任务时,如果总是要求我们首先把计算任务的描述按照计算机所能够理解和执行的程度来进行的话,无疑会使得向计算机提交计算任务成为一件极其麻烦的事情,本来我们利用计算机的目的是为了减轻自己的机械劳动工作量,如果向计算机提交问题是如此麻烦的话,显然不符合我们发明计算机的初衷。 因此进入二十世纪五十年代后,提出了计算机高级语言的概念,所谓高级语言,就是非常接近我们平常对于数学问题的描述的语言,而所谓给出计算机高级语言,实质上并不是说计算机能够直接理解高级语言了,而是把高级语言到计算机内部的机器语言的翻译工作本身交给计算机来完成,因为这种翻译工作在规范了高级语言之后,是计算机自身就完全可以做到的,当然这个翻译任务本身又是一个需要我们首先明确给出一般解答的计算问题。 世界上第一个诞生的计算机高级语言就是我们在这本书里要给出的FORTRAN。 FORTRAN最早是IBM公司在1957给出的,专门用于向IBM自己生产的704计算机提交表述计算问题的。一个计算问题的求解过程如果使用了计算机语言来表述,就称为一个 程序 ,FORTRAN正是这样一种专门用来写程序的语言。 由于IBM的709计算机的成功,同时也使得FORTRAN获得了广泛的传播,这时FORTRAN的语言本性开始在另外一个方面显示了出来,即语言的生命就在于它的传播。于是大多数其它计算机制造商也纷纷使得他们的产品能够输入和理解FORTRAN语言。 FORTRAN语言从它的名称来源就可以知道是一种与某些特定机器进行通讯的语言工具。即FORTRAN语句可以通过特定的机器上配置的编译程序而翻译成机器语言,因此早期的FORTRAN语言具有强烈的与机器系统相关的一些特征,它们反映了那些特定机器的某些特点,显然如果考虑到高级语言的本质是要用来描述计算过程,即所谓算法语言,那么这些与机器相关的特征是没有意义的,而FORTRAN不断更新版本所带来的进步,也就包含了不断舍弃这种特征的目的在内。 FORTRAN的这种历史痕迹的一个例子就是把语言用那些特定机器所能接受的字符序列(亦就是IBM公司用来向计算机输入程序的卡片穿孔设备的48个字符)来定义.而它的那种卡片输入方式决定了很多的程序书写格式。例如通常把语言中的一个语句写在一个记录上,也就对应着穿孔的一张卡片;各个语句按顺序读入,对应着卡片的顺序读入;卡片的格式是固定的,构成后来所谓的固定源码形式。 这就是最初提出计算机高级语言时我们所拥有的第一种高级语言的状况。 不久降生的另外一种高级语言是ALGOL,这个名称就是算法语言的简称,因此可以预料到这种语言具有与FORTRAN非常不同的面貌,因为这种语言在 设计 初始,就不是计算机制造公司为某种特定机器设计的,而是纯粹面向描述计算过程的,也就是所谓面向算法描述的。 ALGOL最早是在1958年由德意志联邦共和国应用数学与力学协会提出的。ALGOL的设计目标显然比FORTRAN要来得高远,它希望不仅能够用于人对机器转述计算过程,也希望能够直接用于人与人之间的对于算法的描述。 为了实现这个通用的目的,ALGOL的字符不是针对任一具体机器定义的,因此它不反映任何一台特定机器的特性。实际上ALGOL所使用的字符与词汇完全是独立定义的。除此之外ALGOL还具有许多更加独特的性质。 不过语言终久摆脱不了它的市场属性,由于FORTRAN更加具有市场侵占能力,最终FORTRAN至今还是主流的科学计算编程语言,而ALGOL语言则不幸成为了任人凭吊的古董。 1.3为什么选择FORTRAN 到底选择什么样的语言,本身是一类非常具有争议性的问题。曾几何时,在科学计算领域,就沸沸腾腾地讨论过最好使用什么样的语言。也许我们可以说这是一个见仁见智的问题,因为我们作为语言的使用者,总是拣自己已经很熟悉的语言,当然总是自己能够很好驾驭的语言是最好的。但是具体地针对科学计算来说,由于科学计算问题具有自身的独特的价值标准,在这个价值标准之下,各种不同的语言还是可以进行客观比较的。 首先我们得把自己面临的任务界定清楚,也就是什么是科学计算问题? 所谓科学计算问题大体上包括如下三个涵义: ● 问题本身以及问题的解答都能够使用数学语言予以精确描述; ● 如果要使用通常的数学方法来给出我们所需要的数值答案,会很麻烦或者根本无法给出; ● 问题以一定的科学与技术知识作为背景。 我们会看到正是科学计算问题的这种内涵决定了它在选择计算语言时所具有的价值标准。 首先,一个科学计算问题总是要以一个数学计算问题的形式出现,因此描述科学计算问题的语言应该能够自然地描述数学问题,即要求编程语言和数学语言在表达方式上具有比较直接自然的对应关系。 然后一个科学计算问题之所以需要使用计算机,那肯定是因为这个问题具有一定的计算量,那么程序的运行效率往往是选择语言时最重要的考量因素。 正是在这两点上,FORTRAN是现在众多语言当中的绝对胜出者。 在描述数学语言的自然性方面, FORTRAN可以说比现在还“活”着的任何语言都强。当然在历史上曾经出现过象ALGOL那样的相当数学化的语言,可惜的是它缺乏市场生存能力,所以就只剩下FORTRAN独美于今了。FORTRAN擅长描述数学计算,这点应该是几乎没有什么争议的。也正是由于这个缘故,FORTRAN的易学是公认的。任何一个科技专业人员,只要对于一个具体问题的数学求解过程有明晰的概念,要把这个求解过程翻译为FORTRAN语言是非常轻松的。 至于执行速度方面,则常常有些似是而非的说法误导 初学者 。最典型的一个错误观念就是“C 代码 的执行速度最快”。这个说法来源于C语言的特殊性,因为C语言更多的是一种系统编程语言,对硬件的控制能力很强,在高级语言里面无出其右者,于是给人以C程序的速度必定最快的印象。但是忘记了这个速度快是来自C语言的系统编程特性,而在做科学计算时,并不需要过多地涉及到系统内核,因此C语言的长处在科学计算方面可以说并不能适当地发挥,相反,在数值计算方面,C绝对不是FORTRAN的对手,因为相对于C以系统编程为目的,FORTRAN是以科学计算为目的的,语言本身在设计之初,就考虑到了针对科学计算而进行优化,因此FORTRAN生成的可执行代码是高度优化的。 实际的运行效率方面的比较也表明了FORTRAN在科学计算方面的优越性。无论是国内还是国外,也无论是经典的串行机还是并行矢量机,大量的经验表明,在执行同一个科学计算任务时,C或C++代码的效率都低于FORTRAN代码,。 除了常见的对于C有着高效的迷信之外,还常常有着对于FORTRAN是如何如何落后的偏见。当然这种偏见是有来源的,那就是曾经功勋卓著的FORTRAN 77在很长一段 时间 里面,都缺乏进取心,使得迄今很多人提起FORTRAN,想到的就是在当今时代已经显得非常落后的FORTRAN 77。实质上,FORTRAN标准在进入FORTRAN 90时代之后,特别是现时的FORTRAN 95版本,可以说只要是对于科学计算有用的特性,C和C++有的,现在FORTRAN 95绝对不缺,而反过来FORTRAN 95所具有的很多针对科学计算的特性,却是C和C++所不具有的。哪怕是C++最引以为傲的面向对象性质,FORTRAN 2000也将全面引入。所以说,FORTRAN已经完全赶上了编程语言的潮流。 与程序运行的效能有关的另外一个重要方面,是程序语言能否支持程序的并行运行,在这点上,可以说FORTRAN表现了它的最大优势,因为FORTRAN 95正是着力于获得并行计算的能力的一个版本。 由于现代科学计算的规模越来越大,计算并行化是一条不得不走的路线,现代计算机硬件的发展,也使得并行化具有实际的普及前景,因为不仅专门的大型计算机是并行的,现在的一般PC都可以拥有多个处理器,因此现代的从事科学计算的用户不得不掌握并行化计算的编程能力。 但是进行并行化编程所遇到的一个主要问题,就是任何过程编程语言都内在地使用线性存储模式,也就是一个数组的元素总是被认为按照数组元素的先后顺序而连续地存储在内存单位里面,这样一种模式就决定了这样的过程编程语言无法真正地实现对并行计算的描述。而FORTRAN 95则完全改观了这种制约,因为在FORTRAN 95里面对于数组以及数组运算建立了全新的面向并行化计算的概念,诸如纯过程的概念,逐元过程的概念,FORALL结构等等,都有效地摆脱了线性存储模式的制约,使得FORTRAN 95成为描述并行计算的标准语言,特别是那些专用的数据并行化语言都纷纷采用FORTRAN作为基础语言,例如高性能FORTRAN(High Performance Fortran),Fortran D,Vienna Fortran,以及CRAFT等。这样就使得使用FORTRAN 95编写的程序可以直接在这些数据并行化语言的平台上运行,而反过来使用这些专用语言编写的程序也可以毫不困难地转移到FORTRAN 95平台上运行,这样一种局面使得FORTRAN在并行计算领域独领风骚。 综上所述,我们完全可以说FORTRAN 95是进行科学计算的最佳语言,作为需要进行科学计算的科学与技术领域的工作人员,掌握FORTRAN 95远比掌握C,C++等语言要重要得多,至于那些计算机符号代数与数值计算软件,例如MATHEMATICA,MAPLE, MATLAB ,Macsyma,MATHCAD等等,只能说是进行科学计算的教学模型与辅助工具,由于它们都提供了现成的算法,因此可以使得初学者能够应用于一些简单的场合,真正要用它们来对付稍微大一点的问题,有经验的用户都知道,那会是一件非常痛苦的强人所难的事情。因此最终要自由地进行科学计算,则非FORTRAN莫属。 1.4FORTRAN的进步 下面我们介绍一下FORTRAN在历史上所取得的重要进步,特别是FORTRAN 95有别于FORTRAN 90的特征,更能够引导我们走向高性能的FORTRAN并行编程的领域。 1.4.1FORTRAN 95的进步 FORTRAN的每一个进步,都意味着要把很多的语言特征归结为三类: ● 新的语言特征; 顾名思义,就是FORTRAN最新版本引入的全新语言。 ● 过时的语言特征; 就是暂时在最新的FORTRAN标准里面仍然可以使用,而不会出现不兼容的情形,但是被指明为过时的语言特征,是在未来的下一个FORTRAN版本里面将要被淘汰的部分。 ● 废弃的语言特征。 顾名思义,就是在过去的FORTRAN版本里面曾经出现过,但在现在的版本里面已经不许使用的语言特征。 下面分别介绍它们。 1.4.2新的语言特征 FORTRAN 95所增加的新的语言成分包括全新的功能和对旧有功能的改进两个部分。它们主要包括: ● FORALL语句与结构。 ● 纯(PURE)过程。 ● 逐元(ELEMENTAL)过程。 ● WHERE结构的扩展。 ● 默认初始化。 ● NULL固有 函数 。 ● CPU_TIME固有子例行程序。 ● 固有函数CEILING,FLOOR,MAXLOC,MINLOC的扩展。 ● 可分配数组的动态去分配。 ● 名称列表输入里面的注释。 ● 最小域宽格式说明。 ● 用于支持 IEEE 754/854浮点运算标准的某些修改。 下面分别予以说明。 1. FORALL语句与结构 FORALL语句与结构和纯过程这两个新的语言成分,主要是为了提高在多处理器系统上面的程序并行运行效率而引进的。 只要系统支持并行的赋值,FORALL语句以及结构,就能够自然地实现对一个庞大的数组的所有元素进行同时赋值,从而充分地利用了系统的并行效能。这个新特征的引入充分显示了FORTRAN在并行运算方面的努力。 2. 纯(PURE)过程 PURE是一个完全新引入的过程属性。具有PURE属性的函数或子例行程序,除了返回值之外,将不具有任何的后效。也就是说,对于它的任意变元或全局变量,它都不改变它们的值,指针关联状态,以及数据映射,也不执行输入输出。 在FORTRAN 95标准里面,一些场合要求其中的过程必须具有PURE属性。特别地PURE属性对于应用FORALL语句或结构进行并行赋值是必要条件。 3. 逐元(ELEMENTAL)过程 逐元过程同样是执行并行运算的强大工具。把一个逐元过程应用到一个数组,就把这个针对数组的运算转化为对数组的所有元素的单个的运算,所有单个的结果再组合起来,返回一个同样形状的数组。 4. WHERE结构的扩展 WHERE结构经过扩展,可以包含一个FORALL结构的嵌套,而FORALL结构又可以包含WHERE语句或结构的嵌套。两者的配合使用具有强大的功能。 5. 默认初始化 默认初始化可以应用于派生类型,包括哑元,这样就能保证具有指针成员的派生类型对象能够一直可以访问。从而避免了出现内存可分配,但是不能去分配的情形。 对于指针,可以使用新的固有函数NULL来给出初始的关联状态,也可以在使用数据之前,使用NULLIFY语句来获得初始化。 6. NULL固有函数 新引入的固有函数NULL的功能主要是给指针赋予一个初始的去关联的关联状态。 NULL函数可以在一个类型声明语句当中使用,给定一个指针的初始去关联状态,也可以在一个结构构造器里面使用,用来给定其中指针成员的初始去关联状态。 7. CPU_TIME固有子例行程序 固有函数CPU_TIME用来测量一个程序或一段代码所消耗的处理器时间。 这个函数的测量结果不一定是非常准确的,但在一些情形下是非常有用的。例如用来比较不同代码的运行时间,从而比较它们的效率;也可以用来 检测 一个并行程序是不是真正地把一个变元作为一个数组来并行处理,从而评价它的并行效率。 8. 固有函数CEILING,FLOOR,MAXLOC,MINLOC的扩展 在FORTRAN 90和高性能FORTRAN之间,某些固有函数以及相关函数存在某些不兼容,因为在高性能FORTRAN里面,在不同的变元位置增加了一个DIM函数。 在FORTRAN 95里面,就放松了对于变元顺序的要求,这样在变元序列当中MASK和DIM的出现就可以是任意的了,从而保证了FORTRAN 95与高性能FORTRAN的兼容性。 9. 可分配数组的动态去分配 在FORTRAN 95里面,当退出一个给定的作用域时,其中没有通过使用SAVE而得到保留的可分配数组,就自动地去分配,和使用DEALLOCATE语句的效果一样。这样就可以防止可能发生的内存遗漏,从而规范分配过程。 10. 名称列表输入里面的注释 在名称列表输入纪录当中可以使用注释,从而方便了用户。 11. 最小域宽格式说明 在使用数值的格式输出的时候,运用增强的输出格式编辑描述符,就可以对域宽进行极小化,从而避免输出时的白边。 12. 用于支持IEEE 754/854浮点运算标准的某些修改 在所有以前的FORTRAN版本里面,都不区分+0.和-0.,即正0和负0,在FORTRAN 95版本里面,就能够区分这两者了,即在使用SIGN函数时,可以通过让第一个变元为0,而第二个变元为负号,就得到负0。 1.4.3过时的语言特征 FORTRAN 95里面被划归为过时的语言特征,在FORTRAN 90里面还是允许正常使用的,但是在FORTRAN 95里面已经强烈地不提倡使用,因为它们在下一个FORTRAN版本里面会被彻底淘汰。这些过时的语言特征主要包括: ● 算术IF语句。 ● DO终止的某些形式。 ● 替代返回。 ● 计算GO TO语句。 ● 语句函数。 ● 可执行语句之间的DATA语句。 ● 哑长度字符函数。 ● 固定源码形式。 ● 字符型声明里面的CHARACTER*形式。 下面分别说明,并特别要注意它们为什么是多余的或容易产生错误的。 1. 算术IF语句 算术IF语句的功能完全可以通过使用IF语句或IF结构来替代。 2. DO终止的某些形式 共享 DO终止,或者不是使用END DO语句或CONTINUE语句的DO终止都是过时的,无论是从安全的角度,还是从清晰的角度,都最好使用具有不带标签的END DO语句的DO结构块形式。 3. 替代返回 替代返回强烈地依赖标签,是不值得提倡的,而且同样的功能完全可以通过CASE结构来实现。 4. 计算GO TO语句 计算GO TO语句完全可以使用CASE结构来代替,而且CASE结构还具有比计算GO TO语句更加广泛的功能。从结构化编程的角度来看,也应该使用CASE结构,而不是完全非结构化的,带有早期与系统相关特征的计算GO TO语句。 5. 语句函数 内部函数就具有语句函数的许多特征。而语句函数非常容易与赋值语句相混淆,所以语句函数也是不提倡使用的。 6. 可执行语句之间的DATA语句 如果在程序的可执行部分使用DATA语句的话,会容易让人产生一种错觉,即DATA语句能够在程序的执行过程当中进行赋值,而实际上,DATA语句只能用于数据的初始化,因此DATA语句最好还是只用于程序的说明部分。 7. 哑长度字符函数 哑长度字符函数要求在调用程序当中声明函数的名称,它完全可以通过运用具有显式界面或动态字符长度的子例行程序或函数来替代。 8. 固定源码形式 固定源码形式完全是由早期的卡片纸带式输入输出方式所决定,而现在卡片纸带式输入输出早已经不再使用,因此这种源码形式必定是要被淘汰的。 9. 字符型声明里面的CHARACTER*形式 在字符型声明语句当中用来说明字符长度的CHARACTER*完全是多余的,一般提倡采用更为合乎英语习惯的形式。 1.4.4废弃的语言特征 在FORTRAN 90里面还可以使用的语言特征有些已经被FORTRAN 95完全废弃了,因此要特别留意它们,以免出现 源代码 无法在FORTRAN 95系统里面调试通过的情况。 这些被FORTRAN 95废弃的语言特征主要包括: ● 实型和双精度实型的DO变量。 ● 从块的外部分支到块内部的END IF语句。 ● PAUSE语句。 ● ASSIGN语句,带标签的GO TO语句。 ● nH编辑描述符。 为了能够阅读使用FORTRAN 90以及更早版本的源代码,下面还是分别予以简要的说明。请注意它们遭到淘汰的原因所在。 1. 实型和双精度实型的DO变量 实型和双精度实型的DO变量或循环控制的DO参数都是难以移植的,而且也是很少用到的。因此现代的FORTRAN版本都要求DO变量为标量整型变量。 2. 从块的外部分支到块内部的END IF语句 从块的外部分支到块内部的END IF语句对于语句的执行序列的控制,是完全不必要的,也是不规范的,因此已经被完全淘汰了。 3. PAUSE语句 PAUSE语句用于把一个正在运行的程序挂起来,直到系统或操作重新开始运行。它的功能完全是多余的,因为WRITE语句可以发送消息到任何设备,例如操作控制台或终端,而READ语句则可以等待或接收来自同一个设备的消息。它们的配合使用就可以替代特别的PAUSE语句。 4. ASSIGN语句,带标签的GO TO语句 ASSIGN语句用来给一个整型变量赋予一个语句的标签。 它的一般用途就是,在程序的运行过程当中,通过ASSIGN语句就可以给该整型变量赋予分支目标语句的标签,从而给程序提供一种动态分支的能力。 然而整型变量除了可以具有标签值之外,还有可能具有一般的整数值,这就会容易导致程序错误,也使得程序难以阅读。 实际上ASSIGN语句,以及带标签的GO TO语句的功能,都可以由内部过程实现,因为ASSIGN语句无非就是纪录了一个可重用代码块完成运行后的返回点。 ASSIGN语句还可以把一个FORMAT语句的标签动态地赋予一个整型变量,然后该变量就可以用作WRITE,READ,PRINT语句的格式说明符。不过这个功能也可以通过把字符型变量,数组,以及常量用作格式说明符而得到实现。 总之,标签的使用总是不合时宜的。 5. nH编辑描述符 使用这个编辑描述符非常容易导致错误,因为跟在该描述符后面的字符数目很容易计算错误,而如果使用字符常量编辑描述符来行使相同的功能的话,则根本不需要计算字符数目。 2 # 发表于 2005-8-30 10:09 | 只看该作者 Re:科学计算的语言------Fortran95 第2篇. 计算的叙述 算法的每一个步骤,都必须给予确切的定义。对于算法当中所 考虑的每一种情况,每一个有待执行的动作,都必须严格地和 不含混地加以规定。…对于以描述算法作为目的而设计出来 的,采用了形式的定义的程序设计语言,或者说计算机语言, 它的每一个语句都必须有非常确切的意义。 ---- D.E.Knuth 《The Art of Computer Programming》 本质上FORTRAN就是一门语言,一门人与计算机赖以进行有效交流的语言,在这个意义上和我们使用的中文,英文等没有本质差别。现在假设要来描述一种大家都陌生的语言,那么总是要分成两个方面来描述,即一方面要描述这门语言的表象和形态,也就是它使用哪些符号,哪些词汇,一般的句式如何,怎样才能完整叙述一个任务之类;另一方面需要说明这门语言的语义,也就是说这门语言是如何用来表达我们需要它表达的意思的。 第4章基本上就是描述FORTRAN作为一种语言的基本形态,也就是书写这种语言的书写规则。 接下来几章则逐步说明如何用FORTRAN来表达我们的要求,或者反过来说,FORTRAN提供了些什么表达方式,以便我们用来向计算机提出合理的任务: ● 表达基本数据; ● 表达数据的结构; ● 完整地描述数据; ● 构造表达式; ● 驱动计算的赋值; ● 计算过程的结构控制; 在整个第二篇,我们将领略到FORTRAN 95是如何能够做到精致地描述计算的,而把一个问题阐述清楚了,就意味着问题已经解决了一大半。   -------------------------------------------------------------------------------- Donald E. Knuth (高纳德), Stanford University的The Art of Computer Programming荣休教授,而The Art of Computer Programming(计算机程序设计技巧)正是他的伟大著作的名称。洋洋七大卷的《The Art of Computer Programming》是当今全世界每一个计算机科学家所膜拜的圣经。1974年在该书刚完成前面很少一部分时,就给他带来了计算机科学家们梦寐以求的图灵奖。 第4章FORTRAN 95语言的形貌 要说明一门语言的形态,必须回答以下问题: ● 它使用哪些符号来表达信息? ● 它的词汇如何构成? ● 它的语句如何构成? ● 如何表达一个完整的任务? 具体的对于一门计算机语言,把这几个问题更加明确地转换过来,就是: ● 它使用键盘上的哪些符号,各个符号有哪些用途? ● 它的词汇如何由键盘字符构成?含有哪些固定的词汇?以及容许自由构成合法词汇的规则是什么? ● 它具有哪些固定的语句格式?以及容许自由构成合法语句的规则是什么? ● 我们交待给计算机的任何任务,都必须明确说明任务的开始,执行步骤和完成,因此一段完整的源代码应该具备什么样的形式?以及应该具备哪些要素? 本章就是要回答这些问题。 4.1FORTRAN语言所使用的字符 从最抽象的层面来看,人与计算机的交流只是信息的交流,而信息总是需要依靠某种信号来表示,对于人来说,最方便的就是字符。而对于计算机来说,自然就是键盘所能敲出的那些字符(信号),因此下面就是要说明: ● FORTRAN 95能识别键盘上敲出的哪些字符? ● 每个字符对于FORTRAN 95来说又意味着什么? 4.1.1FORTRAN 95所使用的基本字符 按照FORTRAN 95标准的规定,一切FORTRAN 95的实现平台都必须使用下面表4-1所列出来的这个基本的字符集,或者说,这个字符集是所有遵循FORTRAN 95标准的编译器所使用的字符集的公共子集。这样原则上,局限在这个字符集上的源码是能够被任何遵循FORTRAN 95标准的编译器所识别的。 表4-1基本的FORTRAN 95字符集: 文字字符 英文字母 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 数字 0 1 2 3 4 5 6 7 8 9 下划线 _ 特殊字符 图形名称 图形名称 空格 : 冒号 =等号 !叹号 十加号 ” 引号 一减号 % 百分号 * 星号 & 英语的and /斜线 ; 分号 ( 左括号 < 小于 ) 右活号 > 大于 ,逗号 ?问号 .小数点或句号 $ 货币符号 ’撇号 可以看到基本字符分为两大类:文字字符和特殊字符。除了货币符号可以本地化之外,其他任何字符都必须依照表里的形式。 对于基本字符有如下几个问题需要予以注意。 一.文字字符的用处: ● 主要是命名的作用,可以用来命名语言中的一切对象,这三种符号可以混合使用; ● 其中数字还具有它本来的含义,就是表示数目。 二.特殊字符的用处: 特殊字符主要具有功能的意义,如编辑功能,运算功能,语法功能等。 FORTRAN 95标准原则上接受小写字母。因此除了以下位置,大小写是等价的。 三.大小写必须区分的位置: ● 作为字符常量的字符串里面; ● 输入输出的纪录里面; ● 作为编辑描述符的引号或撇号里面。 因为在上述几种情形,大小写是字符型数据的不同数据取值。 如果不幸遇到一个FORTRAN 95标准的怪异的编译平台,偏偏不接受小写字母,这是FORTRAN 95标准所许可的,这时就得小心了。不过幸好我们常用的编译平台,例如CVF,都是接受小写字母的。 另外,在OPEN或者INQUIRE语句里面的FILE=或NAME=后面是否区分大小写,也是由编译平台指定的。 如果是需要调用其他语言写的子程序, 而恰好该种语言(例如C语言)是区分大小写的,这时就需要特别小心。 【例4-1】 如果用C写了两个子程序EIGEN和eigen,然后有如下的FROTRAN片断: EXTERNAL EIGEN ... CALL EIGEN ... END 这时它是该引用EIGEN还是eigen呢?如果所使用的FROTRAN系统正好是怪异的那种,没问题。如果是常见的如CVF,这时它就无法区分EIGEN和eigen,这样就必须给它们更换名称了。 四.数字的涵义: 除了以下情形,数字总是表示十进位数字 ● 属于二进制,八进制,十六进制的字面常量; ● 带有B,O,Z编辑描述符的输入输出纪录。 【例4-2】 以下语句当中的数字不是属于十进位数字: DATA I, J, K / O’1001’, 23.54, Z’5CA2’ / 其中第一个为八进制数,第二个为十进制数,第三个为十六进制数。 五.下划线的涵义: ● 下划线的主要作用就是置于单词之间代替空格,使得我们在命名时使用清楚的英语词汇。 ● 下划线不能置于任意名称的前面,但是可以置于名称的最后。 ● 下划线也用于在字面常量中区隔常量的值和种别参数。 无论给什么对象起名,都尽量使用完整的英语单词,同时使用下划线以区隔不同的单词。所谓好记性不如烂笔头,只有这样才能切实保证你在任何时候,在程序代码的任意位置都知道任意变量等的含义。 4.1.2与平台有关的FORTRAN辅助字符集 上节列出的基本字符集是在一切FROTRAN的编译平台都可以使用的,被FORTRAN 95标准规定为必须使用的默认字符集。另外还有些辅助的字符则是不同的平台有不同的用法约定。 辅助字符分两类:可打印字符和不可打印字符。 ● 可打印字符; 各种本地化语言的字符,象汉字,希腊字母等,都可以应用在字符串,注释,和输入输出纪录当中。 ● 不可打印字符。 主要就是控制字符,例如制表符Tab键。 制表符(Tab键)在FORTRAN77标准当中主要用来表示6个空格,这样在固定源程序形式的代码的每行的开头使用Tab,就自动地空出6个空格。 对于一个FORTRAN77标准的编译系统来说,在固定源程序形式里的Tab被看成是至少6个空格,而在自由源程序形式里的Tab被看成1个空格。这样如果Tab被放在文本当中用于输出格式控制,那么这种默认的转换方式,有时就会导致输出格式的混乱。 有关FORTRAN 95的辅助字符集的使用规则,请参考具体的编译系统的说明。 4.2词汇 所谓FORTRAN的词汇就是一个语句的最小的意义单位,它由一个或多个FORTRAN字符集里的字符组成。包括两类共6种,分类例举如下: ● 由文字字符组成的词汇,包括4种: · 语句关键词:IMPLICIT · 名称:EIGEN_FREQUENCY_3 · 由单个词汇组成的字面常量:1.234567_long · 标识符:213 ● 由特殊字符组成的— · 算符: +,.OR. · 定界符:逗号,=,=〉,:,::,;,%。 FORTRAN 95的一切合法的词汇都必须按照语法来构成。完备的构词语法规则在附录B给出。 下面分别予以详细说明。 1. 语句关键词 语句关键词的功用: ● 标志语句本身。 【例4-3】 下面的DO语句中的关键词DO本身标志了该语句 DO I=1,500 ● 标志选项。 【例4-4】 下面的INTENT语句当中的IN,OUT,或INOUT。 INTENT(IN),A。B INTENT(INOUT),X,Y,Z ● 用在语句当中,起分界的作用。 【例4-5】 如下面DO语句当中的WHILE DO WHILE( .NOT. VECTOR ) 并非所有的语句都必须包含关键词,在FORTRAN里面,赋值语句和函数都不需要关键词。 尽管FORTRAN 95不区分大小写,本书任何地方出现的语句关键词都使用大写字母。纯粹是为了醒目的原因。 2.名称 在一个程序当中,任何对象都需要有一个名称,给它们命名所得到的词汇,可以说就是一般语言里的名词,这样的对象包括:变量,命名常量,程序单元,过程,公用块,构造,派生类型,哑元等。 名称的拼写规则为: ● 名称必须由字母开头,可以由文字字符混合组成,而下划线不能作为名称的第一个字符。 ● 一个名称至多允许含有31个字符。 3. 常量 一个常量就是对一个值的合乎语法的字符标记。 常量分为字面常量和命名常量两种: ● 一个值如果没有在程序里面经过命名,则称为字面常量,这种常量不能取派生数据类型。 【例4-6】 66953 Z’5120A’ 2.3417 .TRUE. (33.2, 5.0) ● 一个值如果在程序里面经过命名,则称为命名常量,这种常量能取派生数据类型。 【例4-7】 在如下声明语句当中的常量UNSTABLE_POINT为命名常量: REAL, DIMENSION(3), PARAMETER ::UNSTABLE_POINT = (/5.332, 0.221, 190.632/) 对于常量的语义,将在说明数据时进一步讨论。 4. 语句标签 在一个程序单元内部,对任何一条语句,都可以在该语句的前面加上语句标签,以便在该程序单元内部的任何其他位置引用该语句。需要引用其他语句的语句包括CALL语句,DO结构,分支语句,输入输出语句等。 语句标签的书写规则为: ● 语句标签由1到5个十进制数字组成,其中必须至少有一个数字不能是0,例如000不能作为标识符; ● 标识符以0开头是没有任何意义的,例如0034与34没有区别。 ● 标识符不能放置于空语句之前。 【例4-8】 456 上面的语句只出现了一个语句标签,是不合法的。 ● 对于在一个程序单元内部,标识符不唯一出现的情形,具有特殊的含义,将在后面讨论。 5. 算符 算符用在表达式当中,通过运算而获得某种类型的值。 算符分为固有算符和自定义算符两类: ● 固有算符 在FORTRAN 95语法当中,R310规定了固有算符的构成法则。 【例4-9】 //表示字符串的连接 + 表示对数值的加法 .NOT. 表示逻辑否 .OR.表示逻辑或 上面都是固有算符。 ● 自定义算符 自定义算符的一般语法形式为: .XXX… . 即在两个句点之间有n个字符构成的字符串,n不大于31。 中间的字母串最好是一个表达该运算含义的英文单词。这个单词不能与固有算符或者逻辑常量里面已经使用了的单词重复。 6. 定界符 全部的定界符有如下12种形式: / ( ) (/ /) , = = : :: ; % 其中(和),(/和/)都必须成对出现 顾名思义,这些定界符的功能就是在一个连续的源码文本当中,用来把不同性质的源码成分区分开。它们的具体含义将在具体的语句当中说明。 4.3语句 一条语句由一些词汇组成,可以理解为表示要求计算机进行的一个动作,但一个说明,一个描述之类的,表面看好象不是计算机的一个动作,不过实质上同样要求机器内部的一个动作与之相对应,因此同样也构成FORTRAN的一条语句。 FORTRAN 95的语句分为两大类: ● 非执行语句 当需要引入或说明一个程序单元或子程序,或者是说明数据类型时,就需要使用非执行语句。 ● 可执行语句 当需要计算机进行一个指定动作时,就需要使用可执行语句。 FORTRAN 95全部的语句的具体分类,以及语法和例示参见附录A,语句的语法也参见附录B。在后面的有关章节则分别说明了所涉及到的主要的语句。 4.4源码形式 一个FORTRAN 95程序就是由以下三种形式的程序成分所构成的分行的文本: ● FORTRAN语句 ● 注释 ● INCLUDE行 在一个FORTRAN 95程序里面,一条语句占一行或多行,一行也可以有多条语句,程序文本当中可以包含空行,但不具有任何含义,被FORTRAN编译器忽略。 这种形态的文本就是FORTRAN的源代码(源程序)。 从FORTRAN90开始,对于源程序的书写格式要求已经完全现代化了,也就是出现了所谓自由源程序格式,而此前,FORTRAN的固定源程序格式一直是初学者视为畏途的主要因素,那种传统的固定源程序格式完全是FORTRAN作为始祖级的高级语言的遗留痕迹,因为早期的源程序输入不是通过键盘,而是运用穿孔纸带,正是穿孔纸带的格式规定了相应的源程序的书写格式。现在之所以我们还需要了解这点,是因为FORTRAN的悠久历史,决定了有大量的源代码正是使用了那种古老格式,那是一个今天我们不得不继承的宝库,要想使用它们,显然就得会读那种格式,因此我们只需要了解固定格式,却不需要遵循固定格式来写代码。 【例4-10】 在这个例子里面,表明了行与语句之间可以有多种排列形式: 这里的例子显示了所谓自由源程序格式的自由之所在。这里使用了作为一个语句在行与行之间连续的标志,而!后面的字符永远是注释。 23 FORMAT( 6Y, J9)!这是一条语句占有完整的一行的例子 37 FUNCTION string_concat(s1, !这里一条语句被分到两行s2) 空格是被忽略的。 64 FORMAT( 6Y, J0);37 FUNCTION !这里一行里有两条语句,其中一条 string_concat(s1, s2)!语句还只是它的一部分。 TYPE (string) :: s1, s2, string_concat string_concat%string_data = s1%string_data(1:s1%length) // s2%string_data(1:s2%length);string_concat%length !这行里包含两条 = s1%length + s2%length!部分语句。 END FUNCTION string_concat 源码文本的一般规则如下: ● 在一个程序单元内部,行与行之间的顺序是有意义的,只有两个例外: •注释行的顺序与位置可以非常自由; •在CONTAINS语句和END语句之间的子程序的顺序也可以是任意的。 ● 在一个程序单元内部,或者完全使用自由格式,或者完全使用固定格式。但是一个程序内部的不同程序单元则可以使用不同的格式。后面要说明为了便于协调起见,如何使用一种自由格式与固定格式兼容的特定格式。 ● 所谓字符文本是指如下两种情形下的字符串: • 作为一个字符字面常量的取值的字符或字符串; • 被字符串编辑描述符控制的字符或字符串。 那么描述符本身和续行符永远都不属于其邻近的字符文本。 ● 针对字符文本的规则与针对非字符文本的规则是不一样的。 【例4-11】 下面例子说明了空格在字符文本与非字符文本当中的不同使用规则: 22 CHAR = NAME01 // “KNOWLEDGEARCHIVE” 23 CHAR = NAME02 // “KNOWLEDGEARCHIVE” 在双引号里的字符串之间的空格是有意义的,因此上面的两个字符串是不同的。 DO43I=1,N DO 43 I = 1,N 而上面这两条语句是等价的。 4.4.1自由源程序格式 自由源程序格式的主要思想就是不限制语句在行内的位置。与固定格式相比,主要是空格的用法有差异。 自由源程序格式的一般规则如下: ● 对于FORTRAN的基本字符集而言,一行至多能容纳132个字符,如果出现非基本字符集当中的字符,则具体的平台会有相应的规定,这时,可能能够容纳的字符数目就会少于132。 【例4-12】 假如下面的语句刚好包含132个字符,但是其中含有中文字符: TEXT = CHINESE_SENTENCE’this line has exactly 132 characters and contains人’ 这时,一个具体的实现平台会有相应的规定,一般来说它会认为上面语句的字符太 多了,因此为保险起见,尽量使用续行符。 ● 只要字符!不是作为字符文本当中的一个字符,那么在该行内它后面的所有字符都是属于注释的内容。而FORTRAN对于注释内容没有任何限制,可以是任意形式,因为反正任何编译器对于注释部分都是忽略掉的。一行内可以在语句后面接注释内容,也可以整行就以!开头,这时该行就是完全的注释行。 总之,注释的位置可以是任意的,关键是一行的任意位置只要出现了注释符!,那么它后面直到行末,都会被编译器认为是注释内容而不加理会。因此不要把语句放置在一行内的注释后面。 ● 只要字符不是作为字符文本当中的一个字符,那么在该行内它后面只能接空格以及注释,在紧接着的行内只要存在非注释部分,那就是和该前面的部分是连续的,被称为连续行。 在FORTRAN里,一个语句所跟随的连续行不能超过39行。 一行的非注释部分不能只是一个续行符。 注释不能利用该字符来表示续行,因此如果注释部分的行末为字符,则它只是属 于注释内容的一个字符,不具有续行的意思。 ● 一行如果只包含空格字符,或者根本不包含任何字符(这两者表现一样),那么编译器总是把该行视为注释行,予以忽略。 ● 一行之内可以不止包含一条语句,语句之间必须用(;)加以分隔。 ● 任何辅助字符集当中的字符都可以在字符字面常量和字符串编辑符当中使用。 ● 标签被放置于语句之前,任何情形下都必须避免标签被认为是属于一条语句内部的字符。 按照FORTRAN语法,空语句是合法语句,只要空语句不是出现在一行的开头,因此连续的;;,甚至中间包含空格; ;,都会被认为是单个的;,因为字符;总是意味着它的前面是一条语句,即使为空语句,也不算语法错误。 【例4-13】 下面的语句都是合法的。 X=(3.0,4.6);Y=(44.5,566.0) 这里的;是作为语句分隔符 X=(3.0,4.6); 这里的;被忽略而不认为是错误 X=(3.0,4.6); ;;; ;Y=(44.5,566.0) 这里的; ;;; ;等价于一个;,因为分号之间的空格被认为是空语句,不算语法错误。 X=(3.0,4.6) ;Y=(44.5, 566.0) 这里分号放在一行的开头,因为该行是连续行。 Y=(44.5, 566.0);Z=”ZERO” 【例4-14】 下面的写法是错误的。 53 INTEGER X,Y !这里53是合法的标签 IF (X==0)76 Y=X!这里的标签76不能说明自己不属于IF语句 下面我们更加详细地说明在自由源码形式里面续行符和空格的用法。 1. 续行符 只是采用续行符的不同用法,就有可能产生完全不等价的语句,因为续行符能够导致名称的变化。所以如果一个名称,字符常量,或词汇被迫分行,则必须在前一行的末尾和后一行的开头紧接着字符使用。 【例4-15】 ENERGY = 0.5*MASS * VILOC!这里VILOCITY是一个变量名 ITY**2 ENERGY = 0.5*MASS * VILOC !这里变量名成了VILOC ITY! ITY**2 ENERGY = 0.5*MASS * VILOC !这里变量名成了VILOC ITY! ITY**2 上面的三个语句是完全不等价的! 2. 空格的使用规则 在具有固定名称以及固定格式的算符当中,不能随意使用空格。因为空格默认的功能就是分隔不同的词汇。 【例4-16】 CALL SUBROUTINE A CALL SUBROUTINE A!错误语句! IF X = .NOT . IF X = .NOT. !这两条语句不同 【例4-17】 下面语句当中的空格是不可少的: INTEGER X,Y IF A=0 DO Y=1,20 但不是所有情形下的词汇之间必须要有空格,在不会产生混乱的前提下,有些语句关键词之间的空格是可以省略的,对于语句关键词来说,所有这些不同的情形列举如下表4-1: 表4-1语句关键词中间空格含义的不同情形 非必须的空格 必要的空格 BLOCK DATA CASE DEFAULT DOUBLE COMPLEX DO WHILE DOUBLE COMPLEX DO WHILE DOUBLE PRECISION IMPLICIT type-specifier ELSE IF IMPLICIT NONE END BLOCK DATA INTERFACE ASSIGNMENT END DO INTERFACE OPERATOR END FILE MODULE PROCEDURE END FORALL RECURSIVE FUNCTION END FUNCTION RECURSIVE SUBROUTINE END IF RECURSIVE type-specifier FUNCTION END INTERFACE type-specifier FUNCTION END MODULE type-specifier RECURSIVE FUNCTION END PROGRAM END SELECT END SUBROUTINE END TYPE END WHERE GO TO IN OUT SELECT CASE 4.4.2固定源程序格式 今天已经没有必要按照固定格式书写源程序,只需要能够阅读古老的使用固定格式的代码,如果有必要把固定格式的源程序转换为自由格式,也可以运用某些小软件完成,因此下面只是简要地介绍固定格式的几个规则。 ● 语句只能书写在一行的第7到第72个格子上。 ● 空格除了在字符常量里以外,都是没有意义的。 ● 在某行第一格为字符C,或*,就表示该行整行都是注释。注释总是被忽略。 ● 字符!只要不是出现在第6格,也不是属于字符文本,则从它开始一直到该行的行末,都属于注释。 ● 一行如果只包含空格字符,或者根本不包含任何字符(这两者表现一样),那么编译器总是把该行视为注释行,予以忽略。 ● 一行中的多条语句用一个或多个分号分隔;分号可以出现在行末,但没有更多的意义;分号不能是一行的第7到第72个格子上第一个非空格字符。 ● 除了空格和0之外的任意字符,只要出现在某行第6格上,则表示该行为连续行。一行后面最多只能有19个连续行,第一行称为初始行。 ● 标识符只能出现在第1到第5格上,被连续的语句只有第一行可以使用标识符,这样下面所有的连续行的第1到第5格上只能是空格。 ● END语句不能被连续,它也不能被视为初始行。 4.4.3 兼容源程序格式 在某些情形下,需要书写能够同时被自由格式和固定格式兼容的源码,要做到这点,只需要遵循以下规则即可: ● 标识符只能出现在第1到第5格上,语句只能书写在一行的第7到第72个格子上。 ● 按照自由格式的规则使用空格。 ● 使用!作注释符,但不要放置在第6格,也不要是用使用字符C,或*作注释符。 ● 需要连续行时,在被连续行的第73格写,同时在连续行的第6格也写,第74格到80格保持空格或者只写注释。而连续行的第1到第5格上只能是空格。 下面就是一个同时满足两种源码形式要求的代码例程: 【例4-18】 Column: 12345678...73 _________________________________________________________________________ ! Define the function CUT_SIN DOUBLE PRECISION FUNCTION CUT_SIN(X) CUT_SIN = X - X**3/FACTOR(3) + X**5/FACTOR(5) - X**7/FACTOR(7) CONTAINS INTEGER FUNCTION FACTOR(N) FACTOR = 1 DO 10 I = N, 1, -1 10 FACTOR = FACTOR * I END FUNCTION FACTOR END FUNCTION CUT_SIN 4.4.4程序结构 在FORTRAN 95的语法规则里面规定了程序结构的完整定义,参见附录B。 不过那里的语法规则并没有完备地表述在一个程序单元里,各种语句应该遵循什么顺序。下面给出一般原则: ● 数据类型声明和指定的语句必须放置在可执行结构或语句之前; ● FORMAT,DATA,ENTRY语句也可以放置在可执行语句中间,不过把DATA语句放置在可执行语句中间是一种过时的做法; ● 如果出现USE语句,必须总是放在最前面; ● 如果出现内部子程序或模块子程序,则必须跟在CONTAINS语句后面。 【例4-19】 下面是一个典型的只包含一个程序单元,也就是主程序的FORTRAN程序: !本程序能够求出所有100-999之间每一位上的数字的立方和等于自身的三位数。 PROGRAM SUM_OF_CUBES INTEGER A,B,C DO A = 1,9 DO B = 0,9 DO C = 0,9 IF (100*A + 10*B + C == A**3 + B**3 + C**3) PRINT “(3I1)”, A,B,C END DO END DO END DO END PROGRAM SUM_OF_CUBES RUN SUM_OF_CUBES 153 370 371 407 大家不妨尝试一下,1000-9999之间还存在这样的数字吗?如果是平方呢? 下面的表4-2给出了程序单元的基本模式,其中处于同一水平位置的各语句之间没有严格的前后顺序,而不同的行则表示了严格的在程序当中出现的前后顺序: 表4-2程序单元的基本模式 程序,函数,子例行程序,模块,数据块语句 USE语句 FORMAT语句, ENTRY语句 IMPLICIT NONE PARAMETER语句 IMPLICIT语句 PARAMETER语句, DATA语句 派生数据类型定义, 接口块, 数据类型声明语句, 语句函数语句, 特定语句 DATA语句 可执行结构 CONTAINS语句 内部子程序或模块子程序 END语句 ●把 DATA语句放置在可执行结构中间已经过时。 ●语句函数语句已经过时。 表4-3给出一个特定的语句能够在什么结构中出现,不能在什么结构中出现的概貌: 表4-3语句的环境 作用单元的种类 语句 主程序 模块 数据块 外部子程序 模块子程序 内部子程序 接口块 USE语句 Y Y Y Y Y Y Y ENTRY语句 N N N Y Y N N FORMAT语句 Y N N Y Y Y N 其他声明 Y Y Y Y Y Y Y DATA语句 Y Y Y Y Y Y N 派生类型定义 Y Y Y Y Y Y Y 接口块 Y Y N Y Y Y Y 语句函数 Y N N Y Y Y N CONTAINS Y Y N Y Y N N 可执行语句 Y N N Y Y Y N 其中Y表示该语句可以在相应的结构当中出现,N表示不能出现。 4.5INCLUDE行 很多时候一个完整程序的源码还可以原封不动地移植到另一个程序源码的中间,这时并不需要把被移植源码完整地抄写过来,而只需要简单地运用INCLUDE行即可。 【例4-20】 PROGRAM GREEN_FUNCTION REAL X,Y,Z ….!语句省略 INCLUDE ‘GAUSE’ ….! 语句省略 END 这样源码文件GAUSE就直接进入程序GREEN_FUNCTION的源码里面,取代了INCLUDE行的位置。 INCLUDE行由关键词INCLUDE和其后的文件名称组成。其中的文件名称是一个字符文本常量。 ● INCLUDE行只是针对编译器的一个提示,而不是属于程序内的FORTRAN语句。 ● 其中的字符文本常量不能带作为命名常量的种别参数。 ● INCLUDE行必须放置在程序当中,其所引用文件应当出现的位置。 ● INCLUDE行所在行不能有任何其他文字,包括标识符,当然可以有注释。 ● INCLUDE行可以进行嵌套,嵌套的层数由具体的编译器规定。注意在嵌套的同时不能导致定义循环。 ● INCLUDE行之前的语句不能是被连续行,其后的语句也不能是连续行。 4.6与其他语言的语法要素方面的比较 下面简要地比较一下,作为一种语言,FORTRAN和其它语言在总的语法风格方面的异同。 一种计算机语言的语法远比任何自然语言的语法要来得简单明了,计算机语言的语法风格可以明确地归结为各种基本语法要素的选择。因此可以按照语法要素对不同的语言加以比较。 1.字符集 按照语言的所谓形式定义,语言就是取自一个有限字符集合的任意字符所构成的有限字符串的集合。 显然,一种语言选择哪些字符作为它的字符集,正是语言文法设计的第一步。 最常用的字符集就是ASCII字符集,而一套完整的字符集除基本字母和数字外,通常还包含一些特殊字符,以便为语言提供足够的表达手段。 表面看来,一种语言所使用的字符越多,它的表达能力应该是越大,然而在增大字符集的同时,也增加了编译时词法分析的分量,因此这里的折衷方式的不同,造就了不同语言的字符集的差异。 字符集的选择首先来自语言的输入输出设备。对于FORTRAN,C等大多数语言,是面向以工业标准键盘为主的输入输出设备的,因此这些语言的字符集可以说是大同小异的,不过也有例外,如APL语言就使用了ASCII字符集之外非常特殊的字符,因此这种语言的字符集就不能被大多数输入输出设备直接使用。 从计算机历史来讲,到了1960年代的早期,计算机行业对字符的表示大都由六位字节转变为八位字节,这样理论上就有了256个字符可用,分配给52个大小写字母、十个数字以及一些标点符号,应该是足够了。不过,由于计算机语言本地化的趋势越来越流行,语言的国际化导致各种民族语言的文字都要求进入字符集。除了法语,德语里的语音符号之外,希腊语,阿拉伯语之类的语言有着完全不同的字符集。而如果中文和日文要进入计算机语言,则需要一个有一万多符号的字符集。因此甚至出现了一种想法,就是考虑用十六位(65536)来表示字符集。 2. 语句标签 在FORTRAN77及其前面的标准里,语句标签的使用具有严重的时代烙印,而随着技术的进步,这种过时的玩意在大多数情况下不再具有任何价值,特别是严重地损害源码的可读性。因此语句标签在任何语言里都是属于需要淘汰的对象。 3.运算算符 字符+和-(加号和减号)在大多数语言里都是代表两种基本的数学运算,不过除了这两个符号之外,其他的运算算符就有相当多的花样了。 例如只是用特殊字符来表示基本运算算符,典型的就是APL语言,还有LISP语言、TIMES语言等的逻辑运算符;而大多数语言使用一些字符组合,同时也利用特殊字符来表示部分运算算符,有时还使用一些不属于上述两种之中任意一种的字符串来表示部分运算算符,例如FORTRAN里面的.EQ.表示相等,指数运算用**表示等。 4.关键词 计算机语言大都使用保留字与关键词,从而能够提高翻译器的检错能力。而且大多数以关键词开始的语句本身就表明了语句的类型。例如IF、READ等。 一种语言究竟使用多少保留字和关键词,往往是一个折衷的结果:保留字少,可以减少编程者的记忆负担,同时却会增加语法分析的困难;而如果使用大量的保留字,例如COBOL语言,使用了大量的保留字,使得编程者很难全部记下,因此就会常常出现不小心使用保留字作变量名的错误,当然使用很多的保留字,就会使得翻译过程中的语法分析变得更为简单。 特别需要注意的是,当一种语言出现新版本时,得小心是否引入了新的保留字,例如COBOL语言,就在新标准里引入了新的保留字,这就意味着那些在程序中使用新的保留字作为变量名(或其他名字)的旧程序,按照新标准在语法上就不再正确了,尽管这个程序一点也没有改过。对于FORTRAN来说,这类问题就没那么严重。 5.注释 尽管注释一定是被编译器忽略的,但却是文件中十分重要的一部分,因为注释是保证源码的可读性非常重要的手段。在不同的语言里,表示注释的方式非常不同,甚至在一种语言里,也可能有几种方法引入注释: 在C语言程序中,需要使用/*和*/这样的特殊标记来界定而不管行边界,这时常常出现的错误就是漏掉结束的界定符,使得后面的语句也变成了注解! FORTRAN 95语言中的! ,Ada语言中的-,或是C十十语言中的//,都是在行的任意位置开始而直到行的末尾结束。这种做法就更为合理方便。 6.空格 在不同的语言里空格的使用规则非常不同。这点需要引起注意。在FORTRAN语言中,空格在除了字符串数据以外的任何其他地方都没有意义。而在很多语言当中使用空格来作为分界符,这就使得空格在语法中有实际的含义。对于初学者来说,常常意识不到空格带来的问题。 7.定界符和括号 定界符的作用就是简单地标志类似语句或表达式之类的语法元素,目的就是增加源码的可读性,并且使得语法分析更加简单。特别需要注意的是,成对的定界符用来清楚地界定特定语法结构的边界,从而可以有效地消除二义性。 8.自由或固定格式 严格的固定字段格式一般是应用在汇编语言当中。而早期的FORTRAN则是使用了部分固定字段格式;其实很多语言早期同样是具有部分固定字段格式,不过现代语言的风格是完全排斥那种固定格式的,因此几乎所有语言,只要还没有被废弃,都已经采用了自由字段格式。 发表于 2005-8-30 10:10 | 只看该作者 Re:科学计算的语言------Fortran95 第5章 准备数据 从本章开始,我们将赋予语言实质性的语义,也就是规定上章所描述的语言的每一个细节所具有的涵义。这种涵义与其说是我们对于一种语言细节的定义,不如说是算法的要求,要求语言具有足够多的细节,用来表达在算法当中有可能出现的精细情节。 对于任何的问题,站在计算机的角度来看,总是可以把它抽象为如下图所示的结构: 输入数据 计算过程 输出数据 因此要准备通过计算来解决一个问题,首先要作到的是把该问题所涉及到的数据整理好,也就是列出所有的数据,然后根据数据的数学属性进行分类,这个分类的过程就是对数据施加足够的标记的过程,将来把这些数据输入到计算机,计算机将能够依据这些标记,辨识出数据所应该具有的数学属性,从而施加相应的合法数学运算。 所以作为向计算机描述计算问题的FORTRAN语言,它首先要作到的是约定如何给数据施加足够详细的标记。 对这个标记过程的第一个要求是保证准确性,也就是说这个语言的标记系统必须正确地反映真实世界的问题里面,数据所具有的数学属性,因此这个标记系统必然是与数据的数学分类结构保持一致的。 从数学的观点来看,世界上的所有数据,总是可以被表示为整数,实数,复数等等基本的数据种类,因此本章的内容就是讨论: ● FORTRAN语言如何把数据归结为一些基本数据类型; ● 然后为了足够详尽地描述每一个数据类型的属性,FORTRAN是如何施加相应的标记的; ● FORTRAN语言对于这些标记(语法形式)所约定的语义是什么。 然后我们就可以知道,要想用FORTRAN来描述一个问题的算法,并进而以问题算法的FORTRAN语言版本为媒介,通过计算机来得到计算结果,第一个步骤,就是准备好数据的FORTRAN描述。 5.1数据是什么 在上一章里,据称计算机能够使用语言,而且是非常类似于人类的语言,至少从形式上看很象,这难免会令某些人(特别是看过KUBRICK的影片《2001: A Space Odyssey》的观众们)感到恐惧:) 别怕!且先不讨论FORTRAN作为语言是否具有与人类语言等价的表达能力,至少从自然语言的语义学的角度来看,FORTRAN说出来的话其实是绝对空洞的,因为FORTRAN语言的全部语义基础就只是数据,而数据对机器而言,只是意味着经过编码的符号。 一台计算机其实是由以下6个部分组成: ● 数据------也就是基本数据元素以及数据结构; ● 基本操作------也就是一个对上述数据进行操作的基本操作集; ● 顺序控制------也就是一个控制针对数据的基本操作执行的时间顺序的机制; ● 数据存取------也就是一个如何给操作提供数据的机制; ● 存储管理------也就是一个数据存储分配机制; ● 操作环境------也就是一个支持程序和外部环境进行数据通讯的机制。 因此一台计算机 ● 在程序的使用者看来,就是给它输入数据,它再给你加工过的结果数据; ● 在程序的编制者看来,就是把对数据的处理过程表示为计算机有限的一系列基本操作(指令)的集合,使得计算机能够处理相应的数据; 因此,计算机的一切可以说都是围绕着数据----如何表达数据,如何处理数据。而计算机语言所要具备的两个部分的功能,首先就是完备的描述数据的性质,然后就是描述数据的处理过程。 那么,什么是数据呢? ● 数据就是符号化了的信息! 对于计算机来说,任何信息都只有表示为符号,才能被认可;反过来说,计算机只能输入符号,而不会也不能理解符号的含义,它的能力只是体现在按照既定规则来处理符号。 然后,就是给出数据的表示,即如何用符号来明确而无歧义地表达数据。 要使得符号具备数据的含义,需要经过这么几个步骤: ● 处理符号的第一步:给符号分类,并给出描述符号性质的方法。 这个分类是人作为设计者给符号规定语义的第一步,因为对于人来说,数据不能只是符号,而是具有来自真实世界的语义,设计者正是根据符号的这种语义,制定相应的处理符号的规则,而计算机要想能够正确地处理符号,基本的前提,就是每当引入一个数据,都得由人向机器声明这个数据是什么类型,这个数据具备什么性质,而且假设计算机已经被引入处理该种数据类型的规则。 ● 处理符号的第二步:区分常量与变量。 这一对范畴反映了最基本的人类抽象能力,也正是人类思维的基本模式。要想让机器模拟这种能力,最简单的做法就是:任何时候都必须首先声明,哪些符号表示常量,哪些符号表示变量,而变量相应的取值范围必须规定好,也就是说必须描述其取值为具有何种属性的常量值的集合;或至少已经被机器默认。 ● 处理符号的第三步:给每一类数据规定相应的合法运算。 对于一种数据可以执行什么样的运算,来自于语言设计者对数据语义的规定,只有当运算被表示为相应的机器指令或指令集合,这时在表面看来,机器才开始真正“理解”了数据的“涵义”。 因此可以说,数据的定义构成了计算机的“灵魂”。 按照上面讨论的步骤,说明一个数据类型包括四个方面: ● 命名的语法 ● 取值的范围与属性说明 ● 该数据类型的常量的表示方法 ● 定义合法的运算 因此相应的一个数据类型的四个要素就是: (1) 名字; (2) 值的集合; (3) 表示值(相应的常量)的方法; (4) 操作值的运算的集合。 对于这四个要素,FORTRAN一方面要约定它们的语法形式,从而可以构成符号描述的唯一性标记,保证相应的描述语句能够被FORTRAN编译系统无歧义的辨识,另一方面就是要给出这些语法形式所对应的含义。 5.2用FORTRAN来说明数据的性质 真实世界的数据显然是多种多样的,几匹马,轴承的内径,圆周长与直径的比值,电子的波函数,非各向同性电介质的电极化率,10个被试每日的最高血压等等,这些数据都具有非常不同的形式与性质,如果我们每针对一种数据形式,都把它定义为某一种新的数据类型,则肯定是烦不胜举,因此合理的途径是找到一种统一的数据描述方式,而对于科学计算问题来说,自然的数据分类方式是数学对数值数据的分类,再加上非数值型数据,会是非常适合于科学计算的数据表达方式。 当然,如果是以描述其他类型的问题为目的,如事务处理,符号演算等,则选用另外的数据分类方式会更有效。 至少从数学的观点来看,我们常常需要处理的数据,都可以表示为一些基本数据类型的组合,例如我们知道向量实际上就是一个数组,数组的每个元素为标量,因此应用数组这种结构,就可以自然地表示向量,另外复数尽管也可表示为一个二元数组,但是这种二元数组的乘法不同于二维向量,因此为了避免这种歧义, FORTRAN把复数当成一个基本数据类型。而几种基本标量里面,整数和实数都同样必须构成基本的数据类型。由此可以建立FORTRAN的对数据的类似描述。 由于真实世界问题的要求的不同,对数据的描述也有程度不等的情况,最基本的情况就是直接说明数据的类型以及其他属性,又由于数据表示的实现具有一个重要的参数,即存储空间,所以当问题要求的数据,不能满足于默认的存储空间的时候,这时,就需要进一步给出数据的种别参数,这是更加详细的数据描述。如果在真实世界问题当中出现的的数据对象,干脆不符合已有的固有数据类型以及数组的定义,这时就还需要根据用户的要求构造一个依赖于问题的特定的数据结构,这就是数据描述时会遇到的第三种情况。 对于这三种情况,FORTRAN的解决方式如下: 第一种情况: 确定数据的类型以及相应的可能具有的属性。 首先,FORTRAN所能辨识的数据类型首先分为两大类: ● 固有数据类型 ● 派生数据类型 根据语义上的基本差别,数据首先具有一些基本的类型,这些基本类型一般是和构成真实世界里的信息的那些基本元素相对应,比方说数字,字符等。然后其他情况下遇到的数据都可以由这些基本数据类型组合得到。不过一种语言具体的规定哪些基本的数据类型,往往受到该种语言主要应用的场合的影响,由于FORTRAN主要用来进行科学计算,因此它所定义的基本数据类型,正是与我们在科学计算问题当中遇到的数据类型相契合的。 ● 所谓固有类型,是FORTRAN语言所定义的最基本的数据类型,每一种固有类型是和该种数据类型相应的各种运算一起隐式定义的,也就是说一旦声明引入某种固有数据类型,则系统总是默认为对它进行相应的运算是合法的,并且总是可访问的。 这样就做到了每种数据类型都和它相应的运算捆绑在一起,使得问题的描述非常自然。 ● 固有类型包括五种: 整型(INTEGER)、实型(REAL)、复型(COMPLEX)、逻辑型(LOGICAL)和 字符型(CHARACTER)。 这个分类完全是遵循数据的数学分类,即整型指整数,实型指实数,复型指复数,逻辑型指逻辑值,字符型则是语言的基本元素。这样就可以把基本的数学语言一一对应的直接翻译为FORTRAN语言。 ● 所谓派生类型是由用户定义的,非隐式定义的类型,只要用一个类型定义来声明其成员是何种固有类型,或者是何种其它已经定义过的派生类型,就能够被FORTRAN认可为一种数据类型。 由于派生数据类型正是由固有数据类型充当成员而构成的,因此在结构关系上,可以把固有数据类型看成原子,而把派生数据类型看成分子。由于语言的根本目的就是为描述算法服务的,因此从这个角度出发,派生数据类型本质上体现了非常重要的数据抽象与数组合的思想,由于我们需要运用语言来描述的问题是开放性,我们很难划定需要FORTRAN来描述的问题的范围,因此通过构造派生数据类型,使得我们可以很自然而简洁地建立新的数据类型。这是FORTRAN在FORTRAN77标准之后的一个重大进步。派生类型数据最重要的用途就是扩充了数组这种重要的数据结构,由于数组在科学计算领域,是一种极端重要的数据结构类型,FORTRAN除了能够直接描述数组,同时还能描述更为广泛的派生数据类型,也就可以直接对一个数据集合的各个成员同时施加运算,拥有了这种自然的数据类型,就避免了象FORTRAN的早期版本那样,需要通过特别设计的算法来实现这种运算。 所谓固有数据类型的固有,对于FORTRAN来说,就是为每一种固有数据类型规定了它的存储模式。 在FORTRAN77及其之前的标准里,整型,实型,逻辑型数据都是使用了一个数值存储单元,而复型和双精度数据则使用了两个数值存储单元,字符型数据使用一个字符存储单元。由于存储模式是非常底层的语言实现结构,因此FORTRAN后续的标准要想保持兼容,只有继承这个约定。 因此在FORTRAN90与95当中,默认的整型,实型,逻辑型数据都是使用了一个数值存储单元,而默认的复型和双精度数据则使用了两个数值存储单元,默认的字符型数据使用一个字符存储单元。而作为语言的一个发展,在FORTRAN90之后的标准里,开始允许在一个程序单元内,由用户定义特定的不依赖于固有数据存储模式的数据类型,这就是第6章的派生数据类型。 建立一种数据类型,最大的好处就是可以把相关的运算和数据捆绑在一起,对于一个特定问题当中的数据对象,是否应该被明确地看成数据类型,属于语言设计的权宜,因为建立一种数据类型所能带来的好处可以用算法来补偿,而FORTRAN77之后的版本的选择是增加派生数据类型,这样使得我们可以在进行科学计算时,有更为自然的描述方式。 数据类型的全部分类总结如下: 固有数据类型 数值型数据 整型 实型 复型 非数值型数据 逻辑型 字符型 派生数据类型 就数据的属性而言,类型当然是最重要的属性了。在指定类型之后,紧接着的就是根据实际情况,看需要描述的数据是否还具有其他需要说明的属性。 对于数组来说,具有一个基本的属性,就是数组的大小,相应的就是如何指定数组的存储空间的大小的问题。 由于FORTRAN具备可分配数组与指针的功能,因此在程序开头并不一定需要指定数组的大小(维度),在程序执行过程当中,数组的大小会作为输入或计算结果被读入,这个指标可以针对具体的问题的要求,以及运行的状况而定。在FORTRAN还不具备这种动态功能的时候,就需要在数组声明里指定数组的维度,而在事先又很难准确预料程序运行过程当中对数组储存空间的要求,因此如果指定的数组过大,就会大量地浪费当时非常宝贵的内存空间,而如果指定的数据组过小,则肯定会在程序运行过程当中导致错误。 所以为了避免这些问题,现在一般不会在数据声明的时候精确指定数组大小,而是把数组处理成一个动态对象,从而有效地回避了这个问题。 数据的一个重要属性,就是它的可访问性。在FORTRAN语言里,模块提供了对数据的访问控制。任何数据对象只要想把自己局限在模块内使用,模块就能够提供足够的保护,使得外部程序无法访问该数据对象。模块的这个功能使得FORTRAN成为一种安全可靠的语言。 是否打开数据的其他许多具体的属性,取决于具体的数据应用环境,因此要讨论数据的每一种属性,在这里不太现实,我们只有等到下面具体地说明每一种声明语句时再具体说明,因为属性指定总是在出现在声明语句当中。 第二种情况: 指定固有数据类型的种别参数。 对于计算机来说,在确定数据的类型,从而可以引导到相应的运算之后,进一步就需要为数据在内存指定存储位置和存储空间,实际上对于冯纽曼型计算机来说,这个步骤是非常关键的,因为冯纽曼型计算机的要点,就是硬件之外的一切,都必须表示为数据,都必须存储在内存当中,然后在程序的运行当中,随时与CPU进行通讯,因此在程序的开头就明确数据在内存当中的位置与每个数据所占有空间的大小,是保证程序运行非常基本的要求。 ● 用来指定程序当中需要使用的每一种固有数据类型所要求占据的内存空间大小的属性由种别参数表示。给这个变量(参数)指定一个数值,就可以说明数据所需要的存储空间的大小,也就是程序允许的数值数据的位数和字符串的字符数目。 ● KIND(种类种别参数)分别说明整数类型的十进指数范围,实数类型和复数类型的十进制精度和指数范围,字符类型和逻辑类型的表示方法。 ● LEN(长度种别参数)对字符类型规定了字符的个数。 【例5-1】 REAL(KIND=3)::ABC,X,LONG CHARACTER(LEN=40,KIND=GREECE)::NAME 具体的种别参数的约定是与语言的具体实现相关的,因此具体的取值还是得参考编译器的文档。 ● 如果没有声明数据的种别,那么程序就会采用默认的参数,由于FORTRAN的早期版本没有引入种别参数,因此对于有不同精度要求的实型变量,直接采用了两种不同的数据类型,这就是REAL和DOUBLE PRECISION,从FORTRAN90以来的版本里,通过引入种别参数,对种别参数的不同取值,就足够表达不同的精度,而同时为了保证和早期版本的兼容,单独的数据类型声明DOUBLE PRECISION还是被保存下来了,这样一来,就产生了一个有一定任意性的后果,即新的语言标准对不同精度的实型数据,可以通过使用同一个数据类型的不同的种别参数值来表示,而同时用DOUBLE PRECISION作为数据声明也是有效的,这样就保证了源码向前的兼容性,却不具备向后的兼容性。 ● 对于在指定种别参数的数值时,一般是以字为单位,这样对于字长不同的机器而言,相互之间就会出现程序移植的困难,下面分情况予以说明: ● 实型----由于DOUBLE PRECISION是属于老式标准的遗留物,因此使用DOUBLE PRECISION作为数据声明的程序就不具备良好的可移植性,因为所谓双精度是针对具体的机器的字长而言的,对于32位的机器,双精度就是64位,而对于64位机器,双精度就意味着128位,这样在不同字长的机器环境里,双精度就具有不同的位数,使得程序无法在不同字长的平台之间进行直接的移植。因此在这种情况下,最好还是统一使用REAL的种别参数来表达算法所要求的实数精度。可以说种别参数一劳永逸地解决了实数精度的可移植性问题。 ● 复型----由于所谓复型本质上就是由两个实数表达的,因此按道理复型同样应该能够具有表达多种精度的能力,而实际上早期的版本在这方面是有欠缺的,不过随着FORTRAN90引入种别参数,就可以在COMPLEX的声明语句里通过运用种别参数来实现多种精度的表达,对于任何FORTRAN的实现,至少能表达两种精度,而一般来说是多于两种的。 ● 字符型----对于字符,一般的机器都是用单字节8bits来表示一个字符,这样就可以总共表示28=256个不同的字符,这对于任何以字母写出来的语言都是足够的了,不过对于汉语,日语这样一些语言就不够用了,一般得需要双字节,即16bits,这样就可以表达216个字符。因此字符型数据同样需要附加种别参数,以便除了使用默认的基本字符之外,还可以使用辅助字符集里的字符,从而实现程序的本地化。不过某个具体的编译器是否支持双字节字符,必须参考相应的手册。因为FORTRAN 95标准也没有强制要求FORTRAN的任何实现都必须支持双字节字符。 ● 逻辑型----由于一切逻辑型数据都只有两个值,因此如何确定逻辑型数据的存储空间应该是非常好办的,不过不幸的是,FORTRAN的早期版本规定逻辑型数据使用和实型数据一样大小的机器存储单位,这样当机器的字长很大时,就会非常的浪费机器的存储空间。因此到了FORTRAN90和FORTRAN 95,除了作为默认的情形,和旧的语言标准保持兼容之外,还可以通过指定种别参数,使得逻辑型数据的存储空间大小只有一个字,甚至一个bit。当然具体的使用方法需要参考相应编译器的说明。 ● 整型----显然在程序应用当中会出现几乎任何大小的整型数据,因此无法在语言标准里面统一的规定整型数据的存储空间大小,这就同样需要依靠种别参数来指定应该给具体问题当中的整型数据确定多大的存储空间。具体地指定方式属于编译器设计者的选择,需要参考相应编译器的语言说明。 第三种情况: 派生数据类型。 数据的本义就是对真实世界里的事物的描述。这种描述可以是简单的,如一个标量,也可以是复杂的,如一个张量,对于更复杂的对象,在自然语言里有一种自然的描述方法,就是使用一系列的词汇,每个词汇都是对象在某个方面的属性的度量;在计算机语言里,可以采用类似的解决方案,即把对象的每一个需要描述的性质用一个适当的基本数据类型来表示,这样用一组基本数据类型就可以描述该对象。而这一组数据可以看成是一个新的数据类型,表示了一个变量。 这样构造出来的数据类型称为派生数据类型,和固有数据类型一样,在声明派生数据类型时,需要给出名称,描述它的每一个元素的固有数据类型以及相应属性和种别参数(如果非默认的话),当然也需要适当地定义其运算。 既然这种派生数据类型是由一组数组成,就会出现两种情况: ● 这组数据都是属于一个数据类型 这样构成的派生数据类型就是数组,显然对于数组的元素的描述就可以统一进行。具体的用法会在后面专门说明。 ● 这组数据的各个元素属于不同的数据类型 这样构成的派生数据类型称为结构,这时就需要对每个数据元素进行分别的说明,即每一个元素的数据类型,可能有的属性,种别参数等等。 上面对派生数据类型的描述实际上是递归式的,即一个派生数据类型的元素同样可以是另一种派生数据类型,而没有限定必须是固有数据类型。 【例5-2】 下面是一个典型的派生数据类型。 TYPE SAMPLE REAL CURRENT COMPLEX (KIND = QUAD)PHASE CHARACTER (LEN = 50) SOURCE END TYPE SAMPLE TYPE (SAMPLE) SI401,SI402,SI403,SI404 在上面的例子里,首先定义了一个名称为SAMPLE的数据类型,每一个SAMPLE类型的数据由三个分量组成,它们的名称分别为CURRENT,PHASE,SOURCE,分别属于实型,复型和字符型,其中复型和字符型还分别说明了种别参数和字符长度属性,然后给出了程序当中需要使用的四个属于该种数据类型的变量:SI401,SI402,SI403,SI404。 上面例子当中派生数据类型的定义,以TYPE开始,以END TYPE结束。 5.3数据不同种类的存储模式 对于计算机来说,数据分类的第一个反应就是针对不同类型的数据约定不同的存储模式。 由于存储模式的规定涉及到编译环境的设置,因此存储模式的约定是与系统环境相关的,鉴于Compaq Visual Fortran的广泛应用,本节特别针对Compaq Visual Fortran系统而言的说明了数据的各种存储模式。 下表5-1列出了Compaq Visual Fortran所有的固有数据类型的存储空间要求,和相应的能够在这个空间里表达的数据规模。 表5-1 固有数据类型的存储模式: 数据类型 单位存储空间 能表示的数据规模 BYTE INTEGER(1) 1 byte (8 bits) BYTE表示等价于INTEGER(1)的带符号的整型数据类型。 INTEGER 参见INTEGER(2), INTEGER(4), 以及 INTEGER(8). 带符号的整型数据, 包括INTEGER(2), INTEGER(4), or INTEGER(8)。数据规模由编译器选项/integer_size:nn 控制。默认的规模控制选项为/integer_size:32 (等价于INTEGER(4))。 INTEGER(1) 1 byte (8 bits) 从-128到127带符号的整数。 INTEGER(2) 2 bytes (16 bits) 从-32,768到32,767带符号的整数。 INTEGER(4) 4 bytes (32 bits) 从-2,147,483,648到2,147,483,647带符号的整型数据。 INTEGER(8) 8 bytes (64 bits) 从-9,223,372,036,854,775,808到9,223,372,036,854,775,807带符号的整型数据。 REAL(4) REAL 4 bytes (32 bits) 从1.17549435E-38到 3.40282347E38的按照IEEE S_floating格式的单精度实型浮点值。在1.17549429E-38和1.40129846E-45之间的值是非常态的。 REAL(8) DOUBLE PRECISION 8 bytes (64 bits) 从2.2250738585072013D-308到1.7976931348623158D308的按照IEEE T_floating格式的双精度实型浮点值。在2.2250738585072008D-308和4.94065645841246544D-324之间的值是非常态的。 COMPLEX(4) COMPLEX 8 bytes (64 bits) 由一对从1.17549435E-38到 3.40282347E38的按照IEEE S_floating格式的单精度实型浮点值组成的单精度复型浮点值。在1.17549429E-38和1.40129846E-45之间的值是非常态的。 COMPLEX(8) DOUBLE COMPLEX 16 bytes (128 bits) 由一对从2.2250738585072013D-308到1.7976931348623158D308的按照IEEE T_floating格式的双精度实型浮点值组成的双精度复型浮点值。在2.2250738585072008D-308和4.94065645841246544D-324之间的值是非常态的。 LOGICAL 参见LOGICAL(2), LOGICAL(4), 以及LOGICAL(8). 逻辑型值, 包括LOGICAL(2), LOGICAL(4),以及 LOGICAL(8). 数据规模由编译器选项/integer_size:nn 控制。默认的规模控制选项为/integer_size:32 (等价于LOGICAL(4))。 LOGICAL(1) 1 byte (8 bits) 逻辑型值.TRUE. 或.FALSE. LOGICAL(2) 2 bytes (16 bits) 逻辑型值.TRUE. 或.FALSE. LOGICAL(4) 4 bytes (32 bits) 逻辑型值.TRUE. 或.FALSE. LOGICAL(8) 8 bytes (64 bits) 逻辑型值.TRUE. 或.FALSE. CHARACTER 每个字符1 byte (8 bits) 根据约定的字符编码表示的字符数据,通过字符数据的声明形式:CHARACTER(LEN=n)或 CHARACTER*n,其中n 表示byte数,来表示数据规模。 HOLLERITH 每个Hollerith 字符1 byte (8 bits) Hollerith 常量。 表中的INTEGER(4)等价于INTEGER(KIND=4)以及INTEGER*4. 5.4FORTRAN数据类型描述的四个基本属性 一个数据如何才是被完备描述了,以及FORTRAN所要求的描述一个数据的要素是哪些,是一个问题的两面。这个问题对于程序的作者是很重要的,因为FORTRAN现在允许用户自己定义合乎自己需要的派生数据类型,这就要求我们知道一个派生数据类型的定义是否完备。 FORTRAN的数据类型必须包含如下四个部分: ● 数据类型的名称 ● 数据取值的集合 ● 可以施加于数据的值的运算 ● 该数据类型的常量的表示形式 5.4.1数据类型的名称 要能够说明数据的类型所属,首先每种数据类型本身得有个名称,才能在描述数据对象的时候,说某个数据对象属于某个数据类型。 固有数据类型就只有5种,它们的名称:INTEGER,REAL,COMPLEX,LOGICAL,CHARACTER是语言标准的规定。 但派生数据类型则完全是程序作者自定义的,因此必须由作者使用TYPE来给出其构造的派生数据类型的名称,也就是说只要一个数据或一个变量的取值符合TYPE与END TYPE之间的定义,就被该程序单元识别为属于该数据类型,就可以应用相应的运算。 如果一个程序单元里出现的数据不能被识别为该程序单元的数据声明里的诸种类型,那么FORTRAN还会尝试运用一种方式来试图确定它的数据类型,就是根据数据名称的第一个字符来进行判别,这种方式属于FORTRAN的古老传统,因为早期FORTRAN所处理的数据类型比较单纯,顾可以如此简化处理,FORTRAN90与FORTRAN 95都继承了这点。 5.4.2数据取值的集合 对于每种数据类型,存在一个允许的具体取值的集合。而属于该数据类型的变量的取值范围必定是在这个集合内。 表面看起来数据类型的取值集合都是明确的数学意义,但是由于本质上计算机的任何具体取值,都必须是有限的,因此数据类型表面的所谓数学涵义并不是很符合实际的。固然整型必定是取整数值,但只能取有限的整数值,而且这个值还有上限,即一个整型数据能够取多大的整数不仅在机器的硬件方面有制约,在语言的具体实现上也进行了约束。 同样,对于实型来说,更不可能就是和实数集合等价,实型数据的具体取值同样只能取可有限表示的实数,即有限小数。至于某些软件(如MATHEMATICA)声称可以精确的引用无理常数,例如欧拉常数,实际上是使用了一个收敛级数来表达无理常数,只有当用户指定有理表示的精度后,计算机才对级数做相应的截断,给出相应精度的有理表示,而并不是说该常数的无限位表示完全存储在计算机里面。 ● 逻辑型数据能够取得的值的个数是完全确定的,即仅有真和假两个值(即两个元素)。由此可见所有逻辑型变量都是某种判断,而对该判断的取值只能或真或假,这里实际上就规定了FORTRAN语言只能用来表述满足排中律的数学。 ● 对于整型和实型来说,既然只可能取有限值,那么剩下的问题就是如何给某个具体取值分配存储空间了,由于程序单元是根据数据声明当中对数据取值的规划来确定如何为数值分配存储空间的, 因此对于具有极大处理能力的现代计算机而言,最好针对数值占用空间的大小进行分级,以做到在保证数值表达需求的前提下,尽量避免存储空间的浪费。FORTRAN为了给数值占用空间的大小分级,引入了种别参数(K1ND),使得在数据声明的时候,就可以一致地规定该类数据在表达时,允许占用空间的大小。 例如整型除了默认表示之外,还可以标志以种别参数“SHORT”,这个参数意味着在整型的默认取值范围了划出了一个子集,只要是属于这个子集的数据,允许系统给它分配较为小的,但更为合算的存储空间。 对于实型来所,则完全可以根据算法的需要,在开始的数据声明里,就给程序单元里可能出现的数据划出三流九等,使得程序对存储空间的占用更为合理。当然FORTRAN语言标准只是规定了实型必须至少在默认精度种别之外,还需要有一个双精度种别,而在FORTRAN的各种编译实现里,还可以规定更多的精度种别。 ● 对于字符型数据来说,它的存储空间完全和字符串长度成正比,因此只要直接规定字符串的字符个数,就可以一致地得到其存储空间分配标准。 ● 至于复型和派生类型,则完全以其他数据类型作为成员,自身没有什么特别的规定,因此也就没有独特的针对这两种数据类型的种别参数。 显然,FORTRAN通过运用种别参数来明确地规定数据的表示,使得Fortran的标准化程度得到了进一步提高,从而提高了程序的可移植性。 5.4.3数据类型的合法运算 允许施加于数据的运算同样可以分为两类,即与固有数据类型相应的固有运算,还有自定义运算。由于在FORTRAN里面,运算的主要语法功能是构成表达式,因此详细的关于运算的讨论,参见有关表达式的章节。 1. 固有运算 固有运算就是固有数据类型在FORTRAN里面指定了表示符从而可以直接引用的那些固有运算,根据运算所能施加的算元据的不同,一共分为四类: 1. 算术运算 2. 串联运算 3. 关系运算 4. 逻辑运算 简述如下: ● 算术运算 针对三种数值型数据,可以直接引用7种固有的算术运算: ● 2种一元运算: 求反运算,其运算符为-; 求同运算,其运算符为+。 这两种一元运算可以施加于任意数值型数据和种别参数的组合,其运算结果的数据类型与种别参数和算元的数据类型和种别参数保持一致。 ● 5种二元运算是: 加法运算,其运算符为+; 减法运算,其运算符为-; 乘法运算,其运算符为*; 除法运算,其运算符为/; 乘幂运算,其运算符为**。 这5种运算的两个算元可以是数值型数据的任意数据类型与任意种别参数的任意组合。 如果参与运算的两个算元不是同一个类型或种别参数不同,那么FORTRAN如何决定结果的数据类型或种别参数呢?基本的原则就是向需要存储空间大的操作数看齐,以免损失算元的信息。具体地说,就是: ● 若两个算元是相同类型和相同种别参数,则运算结果的类型与种别参数就是算元的类型与种别参数。 ● 若两个算元都是整型但种别参数不同,则运算结果的种别参数是取十进制幂范围大的那个算元的种别参数;若范围一样大,则由系统决定。 ● 当一个算元是整型、另一算元是实型或复型,则运算结果的种别参数就取那个实型或复型的算元的种别参数。 ● 若两个算元属于不同种别参数的实型或复型数据,则运算结果的种别参数取十进制精度高的那个算元的种别参数;若精度一样,则由系统决定取舍。 规定了运算结果的属性,具体的值就是通常的算术运算的结果,即 ● 加法为两个算元之和; ● 减法为两个算元之差; ● 乘法为两个算元之积; ● 除法为两个算元之商,如果两个算元都是整型数据,它们相除时称为整除,其结果商就是首先进行算术上的除法运算,得到的商去掉小数部分,取得的整数值即为整除的结果。这是为了满足上面关于保持类型一致性的规则。 例如:99/100的值为0;(-99)/100的值为0;58/3的值为19;(-58)/3的值为-19。 ● 乘幂为以第一个算元为底,第二个算元为指数的乘幂值。 ● 串联运算 针对相同种别参数的字符型数据定义了串联运算,其运算符是//。 串联运算的结果为保持种别参数不变的字符型数据。运算结果的值为第一算元的字符值,在右边紧接第二个算元的字符值。 例如:ABC//RTY的值为ABCRTY ● 关系运算 关系运算是分别针对整型、实型、复型和字符型数据来定义的二元运算。 关系运算的结果为逻辑型数据,即只能取.TRUE.和.FALSE.两个值之一。 FORTRAN 95定义了六种固有关系运算,这六种固有关系运算根据其可以施加的操作数的不同,又可以分为两类: 可以施加于除复型之外的数值类型,种别参数以及字符型的第一类: ● 大于,其运算符为.GT.,或>; ● 大于等于,其运算符为.GE.,或=; ● 小于,其运算符为.LT.,或<; ● 小于等于,其运算符为.LE.,或=; 可以施加于所有数值型与字符型的第二类: ● 等于,其运算符为.EQ.,或==; ● 不等于,其运算符为.NE.,或/=。 对于数值型数据来说,关系运算具有通常的涵义,并且两个算元可以是任意的数值型类型与任意种别参数的组合。当然只有复型不能比较大小,而只能比较是否相等。 对于字符数据来说,关系运算具有独特的涵义。 首先要求两个算元具有相同的种别类型参数,但是可以具有任意的长度。其关系运算的执行可以理解为执行下列几个步骤: (1)首先使两个作为算元的字符串的字符长度变为一致,如果相对来说有个字符的长度较短,就在右边以空格字符填充,直到两个算元长度相同为止。 (2)然后对两个算元按字符位置从左边第一个字符开始逐个进行比较判别,直到足够判别关系是否成立为止。 (3)而字符的比较是按字符在字符集中排列序列的位置的先后来进行的: ● 若字符1在字符2之前,则认为满足小于关系,小于等于关系和不等于关系; ● 若字符1在字符2之后,则认为满足大于关系,大于等于关系和不等于关系; ● 如果位置相同,即为同一个字符,则认为满足等于关系。 ● 所有空串都是相等的。 ● 等于关系和不等于关系的运算结果与字符集序列无关,而其它四种关系的运算结果是依赖于字符集排列序列的。由于ASCII的排列序列对于任何系统都是一致的,所以一般而言可移植性是能得到保证的。 ● 如果参与运算的默认字符数据值全是字母或全是数字,则按语言的规定,其顺序是严格确定的; ● 如果参与运算的默认字符数据值参杂了字母与数字,则把其中的数字看成字符,而排序则依赖于系统的具体规定。所以在使用时要注意这点。 ● 如果参与运算的字符数据值包含了非默认的字符型数据,则同样依赖于系统的规定。 ● 逻辑运算 针对任意种别参数的逻辑型数据定义五种逻辑运算。 我们知道数值型数据和字符型数据进行关系运算后的结果是逻辑型数据,此外还 可以根据算法的需要自定义逻辑型数据,逻辑运算就是施加于逻辑型数据,而得到逻辑型数据值的运算。 根据算元的数目,逻辑运算包含两种,其中一元运算为: ● 非运算,运算符为.NOT.; 非运算的运算结果定义如下表5-2: 表5-2非运算的运算结果 .NOT.的算元 .TRUE. .FALSE. .NOT.的运算值 .FALSE. .TRUE. 二元运算包括: · 与运算,运算符为.AND.; · 或运算,运算符为.OR.; · 逻辑等价运算,运算符为.EQV.; · 逻辑不等价运算,运算符为.NEQV.; 各运算的结果定义如下列各表: 表5-3与运算的运算 A.AND.B A B .TRUE. .FALSE. A .TRUE. .TRUE. .FALSE. B .FALSE. .FALSE. .FALSE. 表5-4或运算的运算 A.OR.B A B .TRUE. .FALSE. A .TRUE. .TRUE. .TRUE. B .FALSE. .TRUE. .FALSE. 表5-5逻辑等价运算的运算 A.EQV.B A B .TRUE. .FALSE. A .TRUE. .TRUE. .FALSE. B .FALSE. .FALSE. .TRUE. 表5-6逻辑不等价运算的运算 A.NEQV.B A B .TRUE. .FALSE. A .TRUE. .FALSE. .TRUE. B .FALSE. .TRUE. .FALSE. 逻辑运算的结果的种别参数的约定: ● 当两个算元的种别参数相同时,则结果的种别参数与算元的相同; ● 当两个算元的种别参数不同时,则结果的种别参数依赖于系统的约定。 2. 自定义运算 由于上面列出的固有运算,并不能满足我们在构造表达式时对运算的全部需求。显然,要使得语言具有开放性,就不可能期望通过指定有限的对象来概括任意需求,因此必然需要制定一个构造规则,以便允许程序作者自定义运算。所谓自定义运算就是需要程序作者根据算法的需要自己来定义的运算。 从语法的角度来讲,一个运算的定义包括三个部分: ● 符号的表示; 所谓符号的表示就是给出运算的名称,命名规则为一个字符串的左右分别加一个小数点(句点)。 【例5-3】 .REMAINDER. .REVERSE. .INTEGRAL. 固有运算的表示符号除了通常的数学表示符号之外,同时还有一套等价的字符串加左右句点的表示方法,这就和自定义运算的符号表示统一起来了。这样做的好处就是可以用符号串直接作为文字来表示运算的涵义,(例如上面的三个名称就可以用来表示求余,反号,求积分这三种运算),从而便于程序的写作和阅读。这是一个值得遵循的良好的写作风格。 ● 运用固有运算的组合给出的自定义运算的定义; 自定义运算的定义是通过函数用OPERATOR来完成的,具体的说明见有关过程的章节。 ● 自定义运算的算元集合的描述。数学上定义一个函数,必定要指出函数的定义域,同样一种自定义的运算也需要指定能够施加于其上的算元的范围,这里包括如下几种情况: · 定义在某个固有数据类型的真子集上; 如果对一个固有运算也做这样的限制,那么就把这个固有运算看成自定义运算了。 · 定义在不止一个固有数据类型上,例如数值型数据和字符型数据的某种组合上; 可以针对某个固有运算做这样的扩展,同样视之为自定义运算。 · 定义在派生数据类型上; 这样得首先定义该派生数据类型。 · 定义在上述任意情形的组合而成的集合上。 5.4.4数据类型的常量的表示形式 数据在程序当中的行为,除了以指定数据类型的变量形式出现之外,还有就是以常量形式出现,也就是给出某个数据类型的具体取值的形式。 因此对于数据类型的说明,还包括给出该数据类型的常量的书写语法。 【例5-4】 下面给出每一种数据类型的说明常量的例子: 例子 数据类型 例子的取值 345 INTEGER 345 713.2或7.132E2 REAL 713.2 (2.77,5.38) COMPLEX 2.77+5.38i .TRUE. LOGICAL TRUE “SPACE_A” CHARACTERSPACE_A SAMPLE(1.582,(3.2,5.5),”CHENG”) 派生类型 SAMPLE(1.582,3.2+5.5i,”CHENG”) 可以看出,对于数值型数据,直接给出常量数据,就可以了,而对于字符型数据则需要写在定界符里面,对于派生类型则需要遵循派生数据类型的说明语法。 当然一个数据类型里的常量同样具有种别参数的属性,只要给出的常量不是属于默认的种别,就需要给常量加上种别参数,而种别参数有两种情况: ● 一种是采用整数,由于不同的编译器对于这些整数的具体解释有可能是不同的,因此会妨碍程序的可移植性; ● 一种是采用命名常量来作为种别参数,那么只要这些命名常量一直被使用,就能保证对它的解释的一致性。 【例5-5】 类型 例子 INTEGER 2_SHORT REAL 3.14159267895632_QUAD COMPLEX (3.14159_HIGH, 56.2) LOGICAL .TRUE._BYTE CHARACTER CHINESE_”例子” 例子里SHORT,QUAD,HIGH,BYTE,CHINESE都是命名常量。 对于整型,实型,复型和逻辑型来说,种别参数写在数据的右边,以下划线隔开,而字符型则是写在数据的左边,同样以下划线隔开。 5.5数据的基本类型:固有数据类型 对于计算机来说,数据的意义无非就是要知道在存储空间为一个特定的数据划出多大的空间来装载它,然后才谈得上给每一个数据编制地址,从而随时可以对数据进行读入读出操作。 确定数据占用空间大小的自然方式就是统一地给一类数据指定固定的存储模式,这就是FORTRAN早期的做法,即整型,实型,逻辑型统一地用一个数值存储单位来存储,而双精度实型与复型则统一采用两个数值存储单位来存储,字符型数据则统一采用一个字符存储单位来存储。由于数值存储与字符存储具有不一样的情况,因此这两种存储单元的字节数大小不一样。 不过语言的进步,毋宁说是算法的进步,要求语言能够提供更加灵活的存储模式的可选择性,这就是FORTRAN90引进的种别参数,这样就扩充了固有数据类型的存储模式。同时为了使得程序能够与旧的标准兼容,一般采取在默认的情况下采取旧的存储模式,而需要扩充时,则额外加上种别参数。 数据的存储模式是通过对数据进行声明来指定的。详尽的声明语句的使用参见数据的声明,不过下面我们给出描述各个数据类型的四个基本属性的词法与句法,以备寻检。 5.5.1整型 何谓FORTRAN里的整数? 数学上的整数用整型数据来表示。而所谓整数,具体表示出来,在数学上一般的表示形式如下: 。 其中: i 是一个任意的整数。 s 表示正负符号(可以取+1或-1)。 l 是一个正整数,表示i的位数,即表示i需要多少个数字。 r 是一个大于1的正整数,表示i的进制的基数,即逢r进一位的意思。 wk 是一个小于r的非负整数,表示了i的每一位的值。 例如一个形如-41的整数,也可以表示为二进制形式-0101001,因为我们有: 要完全的描述整数i,显然s,l,r,wk(k=1,2,…l)这些数值都是必须提供的。 例如给出十进制整数-41,实际上也就是提供了 s=-1;l=2;r=10;w1=1;w2=4 这一套完整的信息。 不过如果我们的目的是描述一个取整数值的变量n的数据类型,显然s和wk(k=1,2,…l)都无须给出,而整型数据的分类都是围绕l和r来进行的。 【例5-6】 下面的带种别参数的整型数据的声明语句: INTEGER(4) i 实际上表示的是如下形式的整数: 也就是取l=31和r=2。 下面我们就给出说明整型数据的四个基本属性的方式与相关功能函数 1. 整型的名称 整型的名称就是INTEGER。也可以说就是声明整型数据类型的语句的关键词。 声明一个数据对象属于整型数据的基本语句句法为: INTEGER kind-parameter) ] :: ]entity-list 【例5-7】 以下这些声明语句主要是要说明数据项: INTEGER X INTEGER DIMENSION( , POINTER :: days, hours INTEGER(SHORT)RED_BALL INTEGER(2) POINTER :: k, limit INTEGER(1) DIMENSION(10) :: min 【例5-8】 以下这些声明语句主要是要说明数据的属性: INTEGER days, hours INTEGER(2) k, limit INTEGER(1) min DIMENSION days( , hours( , min (10) POINTER days, hours, k, limit 整型数据也可用于指出某个变量为整型也可以构成一个条件语句。 【例5-9】 INTEGER I, X READ (*,*) I IF (I) THEN X = 1 END IF 2. 整型数据的取值 取值为整数值的数据对象被定义为整型数据对象。 值得注意的是整型数据的取值范围,无论如何都只能是整数集合的一个真子集,因为计算机所能表示出来的整数的大小是受到一个有限数值的限制的。至于某一个具体的编译器能够表示的最大的数值是多少,并不是统一的,需要具体的依据编译器的约定,当然只有在我们需要考虑有可能取非常大的数值时,才会注意到系统的这方面的限制。 更具体地考虑一下,当我们要定义一个整型变量的时候,我们不止是需要考虑它是否可能取非常大的数值,更重要的是要在满足算法的数据精度要求与节约机器的内存空间之间取得某种折中。因为尽管语言标准只要求编译器提供整数的一种存储标准,但是现在一般的编译器都能提供多种存储模式,针对算法的具体情况,程序作者就可以选择不同的数据存储模式,来获得高效的程序。 FORTRAN语言可以很方便地描述数据对象的具体的存储模式,那就是种别参数,实际上对于FORTRAN 来说,每一个数据总是认为它不仅属于一个数据类型,进一步还属于该数据类型的某个种别,在种别参数没有表达出来的时候,就会赋予默认的种别参数值。 由于FORTRAN标准只是规范了固有数据类型的定义,对于每一个固有数据类型所定义的各个种别的具体存储模式,则一般由具体的编译系统来约定,因为给具有不同字长的数据规定适当的存储空间,得依据系统不同具体情况来确定。 FORTRAN提供了三个固有函数,可以用于查询具体的某个系统对整型数据的存储模式的设置: · KIND · RANGE · SELECTED_INT_KIND ● 固有函数KIND 这个函数能够给出任意属于固有数据类型的数据对象的种别参数 句法: result = KIND (x) 输入x,可以是任意属于固有数据类型的数据对象,如变量或常量。 输出为一个属于默认整型数据的标量,就是x的种别参数值。 【例5-10】 KIND (0)为默认整型的种别参数值; KIND (12)同样为默认整型的种别参数值; ● 固有查询函数RANGE 这个函数能够给出数据用十进制表示时的幂次范围。 一般句法为: result = RANGE (x) 输入x是数值型数据。 输出为一个属于默认整型数据的标量,表示数据用十进制表示时的幂次范围。 由于机器的存储模式是以二进制的位数为单位的,因此这个函数的作用就在于把二进 制的幂次范围转换为十进制的幂次范围。 对于一个整型变量,输出值为INT(LOG10( HUGE(x) ))。 对于一个实型或复型变量,输出值为INT(MIN (LOG10( HUGE(x) ), -LOG10( TINY(x) ))). 【例5-11】 如果变量X属于REAL(4),那么RANGE (X)的值为37. (因为HUGE(X) = (1 - 2-24) x 2128 TINY(X) = 2-126,而 ; )。 ● 固有转换函数SELECTED_INT_KIND 这个函数能够根据数据取值的十进制幂次范围,给出其种别参数值。 一般句法为: result = SELECTED_INT_KIND (l) 输入十进制最大幂次l。 输出满足-10l n 10l的变量n的种别参数值。 如果系统不支持相应的整型种别,就会返回数值-1。 如果该数据同时满足不止一个种别参数值,则取具有最小十进制幂次范围的种别参数 值。 由于很多情况下,我们只知道数据取值的十进制幂次范围,因此这个函数就提供了通 过十进制幂次范围来指定种别参数的方法。 【例5-12】 INTEGER (SELECTED_INT_KIND (6))X!等价于INTEGER(4)X,而X !的取值最大可以达到106,而最小 !可以达到10-6。 INTEGER (SELECTED_INT_KIND (5)) N,M !声明了变量N和M的取 !值最大可以达到105,而最小可以达 !到10-5。 i = SELECTED_INT_KIND(8) ! 返回4 i = SELECTED_INT_KIND(3) ! 返回 2 i = SELECTED_INT_KIND(10) ! 返回-1, 因为系统不提供如此高 !的精度。 3. 运算 施加于整型数据的运算包括: ● 一元固有算术运算:求反运算-;求同运算+。 ● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。 ● 二元固有关系运算:大于.GT.或>;大于等于.GE.或=;小于.LT.或<;小于等于.LE. 或=;等于.EQ.或==;不等于.NE.或/=。 固有算术运算的结果仍然为整型数据,而固有关系运算的结果为默认种别类型的逻辑型数据。 4. 常量的表示形式 所谓常量,可以理解为取了具体的定值的数据对象,因此整型常量从形式上讲,就是一串数字,可能在前面(左端)加上正负号,也可能在后面(右端)加上下划线,然后跟一个种别参数。 整型字面常量的一般形式为(R403): n 其中: s表示正负号;如果取负号(-),则这个负号是不可缺的,如果取正号(+),则是可选 的。因此不带任何符号的数字串被默认为正数。 n表示数字(0到9),从左端开始第一个非0数字开始,它左边的任何0都会被忽略。 在默认情形下,这些数字都被认为是十进制表示形式的数值。 k是一个可选的种别参数,必须用下划线( _ )和表示数据的数字串区分开。 种别参数的一般语法形式为: digit-string scalar-integer-constant-name 即数字串,或者是取整型标量值的命名常量。 【例5-13】 1表示INTEGER(1); 2表示INTEGER(2); 4表示INTEGER(4); 8表示INTEGER(8); LONG SHORT 其中1,2,4,8同样属于默认种别的整型数字,而LONG和SHORT都是命名常量,它们的具体取值完全依赖于编译系统。 在默认情形下,整型常量总是被解释为十进制表示的数值,除了这种默认的十进制表达形式之外,还可以在DATA语句当中初始化表示为其他系统许可的进制形式。 在FORTRAN语言标准里面,规定了十进制之外的三种进制形式: ● 二进制常量形式(R408): B ’ digit …’ B “ digit …” 其中的数字只能是0或1. 即二进制常量表示为以字母B开头,后跟用一对撇号或引号括起来的数字串,而且每 个数字不是0就是l。 ● 八进制常量形式(R409): O ’ digit …’ O “ digit …” 其中的数字只能是0到7. 八进制常量表示为以字母O开头,后跟用一对撇号或引号括起来的数字串,而且每个 数字是0到7之间的一个数字。 ● 十六进制常量形式(R410): Z ’ digit …’ Z “ digit …” 其中的数字只能是0到9,和A到F这五个字母,用来表示10到15. 十六进制常量表示为以字母z开头,后服用一对撇号或引号括起来的数字或字母的串, 而且每个数字是数字0到9或字母A到F之一。 这里定义的二进制、八进制和十六进制的字面常量形式只能用于DATA语句中。 如果要在CVF系统里面使用非十进制来表示数值,除了属于FORTRAN标准的二进制,八进制,十六进制三种额外的进制形式之外,还可以使用如下的语法形式表示更多的进制形式: #] nnn... 其中: base是从2到36的任意整数。这表明CVF可以使用从2进制一直到36进制来表 示整数。 而如果base省略了,但是给出了符号#,那么表示后面的整型数据被看成16进制, 如果base 和#都被省略了,那么后面的整型数据就被看成默认的10进制。 为什么取能够表示的最大进制是36呢,因为一般从英文26字母里面来取得 符号来表示0到9之外的数字表示符号,一般的约定就是按照字典顺序,例 如如果使用11进制,那么A表示10,如果使用36进制,那么A表示10,B 表示11,C表示12等等一直到Z表示35。 这种表示方法里面,字母的大小写是不加区别的。 【例5-14】 下面的7个变量所赋予的整型数值都是表示十进制的3,994,575: I = 2#1111001111001111001111 m = 7#45644664 J = +8#17171717 K = #3CF3CF n = +17#2DE110 L = 3994575 index = 36#2DM8F 【例5-15】 这些都是正确的整型常量; 0 -112 +43212 62_2 1992110235764803_8 31_SHORT 9999999999999999999_LONG 【例5-16】 这些都是错误的整型常量: 9999999999999999999!对于默认的种别参数来说,这个数太大了。 3.14 !不允许出现小数点。 32,767 !不允许出现逗号。 33_3 !3不是一个有定义的种别参数。 【例5-17】 下面都是非十进制形式的整型常量: FORTRAN赋值 十进制值 十六进制值 LOGICAL(1)X INTEGER(1)X X = -128 -128 Z'80' X = 127 127 Z'7F' X = 255 -1 Z'FF' X = 255 255 Z'FF' X = -32768 -32768 Z'8000' X = 32767 32767 Z'7FFF' X = 65535 -1 Z'FFFF' 数字串后的下划线和种别参数是可选项。如果省略,就被认为是默认整型,这时系统默认的种别参数值就是固有查询函数KIND(0)的结果。 SHORT是命名常量,且具有常量值,这个值必须是非负的,而且与一种表示方法相对应。 非字面常量的数值数据都有一个名字,对于名字要说明其类型,具体说明的方法参见第6章。 5.5.2实型 何谓FORTRAN里的实数? 计算机显然也不能表示所有的实数,因为计算机只能表示有限形式的数值,并且表达一个数值所使用的数字数目也是受到限制的。 对于计算机来说,只能用下面的形式来表示实数: 。 其中: ● x 是一个实数。 ● s 表示正负符号(可以取+1或-1)。 ● r 是一个大于1的正整数,表示x的进制的基数,即逢r进一位的意思。 ● l 是一个大于1的正整数,表示x的位数,即表示x需要多少个浮点数字来表示。 显然这依赖于系统对r的选择。 ● wk是一个小于r的非负整数,表示了x的每一位的值,而且w1不能是0。 ● e 为幂次范围emin 到emax 的一个整数。 ● 如果x = 0,那么wk 和e定义为0。 从实型数据的数学表达式可以看出,如果要对实型数据的存储模式进行分类的话,在默认取十进制的情况下,就一定是围绕l和e来进行的,例如单精度实型(REAL(4))的定义如下: x=0,或者 ● 一个实型数据值最多可以用多少个浮点数字来表示,依赖于系统的约定,下表5-7 是CVF在不同环境里的相应约定。 表5-7实型数据值的浮点数字 IEEE S_floating 24 Compaq (以前是DIGITAL) VAX F_floating 1 24 IEEE T_floating 53 Compaq VAX D_floating 1 53 2 Compaq VAX G_floating 1 53 1仅限于VMS 2VAX D_floating的内存格式是56位浮点数,不过实际只有53位用于计算,因此Compaq Fortran认为它是53位。 ● 幂次范围emin 到emax同样依赖于系统的约定,下表5-8是CVF在不同环境里的相 应约定: 表5-8幂次范围的浮点数字 emin emax IEEE S_floating -125 128 Compaq VAX F_floating 1 -127 127 IEEE T_floating -1021 1024 Compaq VAX D_floating 1 -127 127 Compaq VAX G_floating 1 -1023 1023 1VMS only 1. 名称 实型数据的名称是REAL,不过实型数据还有一个种别,拥有单独的名称,即DOUBLE PRECISION,这属于历史的遗留,是在FORTRAN没有出现种别参数的时候所增加的双精度实型数据的名称,当然使用种别参数是完全可以代替使用这个名称。 实型数据的声明的语法形式如下: REAL n) ] entity-list DOUBLE PRECISION entity-list 其中: n取值为种别参数4, 8, 或16。 种别参数16只出现在OpenVMS, Tru64 UNIX, 和Linux环境当中。 ● 种别参数是可选的,如果使用了种别参数,那么给出的实型数据对象就属于相应 的种别;如果没有使用种别参数,那么给出的实型数据对象就属于默认实型。 ● DOUBLE PRECISION实际上就是REAL(8)。如果使用DOUBLE PRECISION就不能再使用种别参数。 ● 如果要改变实型的默认指定。可以使用编译选项/real_size:size。 给出实型数据对象的名称的例子如下。 【例5-18】 这些声明语句主要说明数据项。 REAL (KIND = high), OPTIONAL :: testval REAL, SAVE :: a(10), b(20,30) 这些声明语句主要说明数据属性。 REAL (KIND = high) testval REAL a(10), b(20,30) OPTIONAL testval SAVE a, b 2. 实型的取值 鉴于FORTRAN的所谓实型只是有限位有理数的集合,因此同样有必要依据精度对实型数据进行分类,以避免给任意的实型数据安排同一个存储模式,从而有可能导致非常大的内存空间的浪费。 FORTRAN标准规定了实型至少要有两种精度形式,一种是默认实型,一种是在默认实型的基础上精度加倍,得到双精度实型。 在FORTRAN90以后,为了与早期的语言版本相容,保留了说明默认双精度实型的关键词DOUBLE PRECISION,不过可以使用REAL加双精度种别参数(REAL(8))的形式,或直接使用指数符D加指数的形式代替。 和整型类似,FORTRAN设置了四个固有函数,可以用来查询具体的某个系统对实型数据在精度方面的约定。这四个固有函数为: · KIND · PRECISION · RANGE · SELECTED_REAL_KIND ● 固有函数KIND 这个函数用于查询变量的种别参数。由于实型数据的双精度型被认为是一种单独的实 型数据种别,因此针对它的种别参数查询函数使用KIND(0.0D0),而对于默认实型则 使用KIND(0.0)。 句法: result = KIND (x) 输入x可以是任何属于固有数据类型的数据对象。 函数结果为一个属于默认整型的标量,表示x的种别参数。 【例5-19】 KIND (0.0)为默认实型的种别参数。 ● 固有函数PRECISION 这个函数给出实数的有限十进制小数表示或近似表示的十进制精度。 函数输出为一个正整数p,表示数据取值至少含有p个有效数。 句法: result = PRECISION (x) 输入x必须是实型或复型,可以取值为标量或数组。 函数结果为一个属于默认整型的数值,等于INT((DIGITS(x) - 1) * LOG10(RADIX(x)))。 如果 RADIX(x)等于10的某个整数次幂,则结果加1。 如果X是一个REAL(4)值,那么PRECISION(X)等于6。因为: INT ((24-1) * LOG10 (2.)) = INT (6.92...)。 ● 固有函数RANGE 参见应用于整型数据的该函数的说明。 ● 固有函数SELECTED_REAL_KIND 这个函数能够根据数据的十进制精度和十进制幂次范围,给出其种别参数值。 使用这个函数,就可以在只了解数据的十进制表示的精度与幂次范围要求的情况下, 在数据说明当中指定其种别参数。 一般形式: result = SELECTED_REAL_KIND ( ) 其中: p作为可选输入,属于默认整型的标量; r作为可选输入,属于默认整型的标量。 这两个可选量必须至少出现一个。 输入表示十进制精度的p或(和)十进制最大幂次r。 输出具有十进制精度的p或(和)满足-10r n 10r的变量n的种别参数值。 显然其中的p就是函数PRECISION返回的结果,r就是函数RANGE返回的结果。 如果系统不支持相应的实型种别,就会出现如下结果: ● 如果系统不支持相应精度,则得到-1; ● 如果系统不支持相应幂次范围,则得到-2; ● 如果系统两者都不支持,则得到-3。 如果该数据同时满足不止一个种别参数值,则取具有最小精度的种别参数值。 【例5-20】 REAL (SELECTED_REAL_KIND (5))X!表示X具有至少5位有效数,并且 !没有指定幂次范围。 REAL (SELECTED_REAL_KIND (6, 50))X!表示X具有至少6位有效数, !并且指定幂次范围为1050到10-50。 REAL (SELECTED_REAL_KIND (6, 70))!等价于REAL (8) i = SELECTED_REAL_KIND(r=200)! returns 8 i = SELECTED_REAL_KIND(13) ! returns 8 5.5.3实型数据的运算 施加于实型数据的运算包括: ● 一元固有算术运算:求反运算-;求同运算+。 ● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。 ● 二元固有关系运算:大于.GT.或>;大于等于.GE.或=;小于.LT.或<;小于等于.LE. 或=;等于.EQ.或==;不等于.NE.或/=。 对实型数据施加固有算术运算的结果总是实型数据,即使二元运算的某个运算元为整型,结果仍然被视为实型。 固有关系运算的结果为属于默认种别类型的逻辑型数据。 5.5.4实型常量的形式 FORTRAN可以写出有限位的整数,也可以写出有限位的小数,然后可以再加上正负号,到此为止,实数里面的非有限小数就只能用有限小数来近似了。 带符号的实型字面常量的写法(R412)有以下几种形式: 不含指数部分的实常量: n 含指数部分的实常量: n E nn... n D nn... n Q nn... 413 其中: s表示正负号,如果是负数必须有(-),如果是正数,(+)可选。 n表示从0到9的数字(默认实型的情况下)。如果不含指数部分,则其中必须包含 小数点。在整数与小数部分的数字串遵循R402,指数部分的数字串遵循R416和 R401。 k是种别参数:REAL(4)是4, REAL(8)是8,种别参数前面必须有下划线( _ )。 E,D表示指数符(R415)。后接指数表示10的幂次,例如1.0E6 表示1.0 * 10**6。 ● FORTRAN只能表示实数的有限小数部分,并且按十进制科学记数法解释,即有 效数乘以10的指数幂。书写的有效数字个数可以多于系统约定的用来近似表示该 常量值的位数。 ● 在一个实型常量里同时出现种别参数与指数时,使用E。 ● 指数符使用D,表示常量为双精度实型数据(REAL(8)),由于这种精度的实型是单 独定义的,因此不能再带有种别参数。它的种别参数值是固有查询函数 KIND(0.0D0)的结果,而且约定其表示方法的十进制精度要大于默认实型的精度。 ● 带种别参数的实型常量受到其种别参数的约定。 ● 既不带指数,也没有种别参数的实型常量为默认的单精度常量,即REAL(4),此 时种别参数值是固有查询函数KIND(0.0)的结果。 ● 应用种别参数来规定所谓的有效数的个数时,数字串里第一个非0数字左边所有 的0全被忽略。例如00.0000562389里面,5之前所有的0都不计入有效数。 ● 如果实型常量不含指数部分,则在数字串部分必须包含小数点,如果有效数为不 带小数点的数字串,即整型字面常量时,必须出现指数符及指数,否则就变为整 型数据了;如果含指数部分,则小数点不是必须的,而指数部分的数字串里不能 出现小数点。 ● 在默认情况下,指数符E指单精度实型(REAL(4)),而如果带有种别参数,则依 种别参数,例如-7.E2_8为双精度实型常量,也可以写为-7.D2。 ● 如果实型常量的数字串当中出现指数符,那么指数数字串不能省略,但可以是0。 ● 种别参数可以使用HIGH,LOW,QUAD等命名常量,其具体取值由系统约定。 单精度实型(REAL(4))的定义如下: x=0,或者 【例5-21】 下面都是合法的实型常量: -12.78 +3.73E2 -13.6E_4 10.93E7_QUAD .96D2 3.14159_4 -.00127 +5.0E3 2E-3_4 123456789D+5 123456789E+5_8 +2.7843D00 2E200_8 123456789Q4000 1.23Q-400 【例5-22】 下面都是不合法的实型常量: 1,234,567.! 数字串当中不能含有逗号 325E-47 !对于REAL来说,这个数太小;但是属于合法的DOUBLE PRECISION !常量。 -47.E47 !对于REAL来说,这个数太大;但是属于合法的DOUBLE PRECISION常 !量。 625._6 !6不是合法的实型种别参数。 100 !在不含指数部分的情况下,没有小数点,故只能是合法的整型常量。 $25.00!不允许含特许字符。 -.25D0_2 !2不是合法的实型种别参数。 +2.7182812846182 !没写指数符D,不过在忽略7个有效数之外的数字之后,可 !以视为合法的单精度实型常量。 1234567890D45 !这个数对于D_floating格式来说太大;但属于合法的G_floating和 !T_floating格式。 123456789.D400 !这个数对于双精度格式来说太大。 123456789.D-400 !这个数对于双精度格式来说太小。 1.Q5000 !这个数对于三倍精度格式来说太大。 1.Q-5000 !这个数对于三倍精度格式来说太小。 5.6复型 两个实型数据就构成一个复型数据,因为在数学上,一个复数本质上就是一个实数二元组。 和实型值集合只是实数的一个子集一样,复型值集合只是数学上复数的近似表示的一个子集。复型的值运用实值的有序对来表示,第一个实值称为实部,第二个实值称为虚部。 复型数据的取值的写法如下: (c,c) 其中逗号与括号不可或缺,而c可以取: ● 整型常量或REAL(4)常量,得到COMPLEX(4)常量; ● 整型常量,REAL(4)常量,或DOUBLE PRECISION (REAL(8))常量,得到COMPLEX(8)常量; 正是由于复型本质上由实型组合成,因此复型数据的存储模式依据实型而定。 5.6.1 名称 复型的名称为COMPLEX,说明复型数据类型的句法为: COMPLEX kind-parameter)] entity-list 其中: n为种别参数4 or 8,同于实型。 ● 由于复型数据种别参数是应用于实部和虚部两个实型数据的,如果出现种别参 数,该种别参数指定实部和虚部两个实型数据的种别;如果不带种别参数,则称 此复型数据为默认复型,即实部和虚部都属于默认实型。 【例5-23】 COMPLEX ch COMPLEX (KIND=4),PRIVATE :: zz, yy !等价于COMPLEX*8 zz, yy COMPLEX(8) ax, by! 等价于COMPLEX*16 ax, by COMPLEX (kind(4)) y(10) complex (kind=8)x, z(10) 5.6.2 复型数据的取值 复型数据对象的取值的种别完全由实型而来,根据两个实型分量的组合,可以分为如下情形: ● 两个实型分量都是默认实型,则相应复型也称为默认复型; ● 两个实型分量都是双精度实型,则相应复型也称为双精度复型; ● 给复型对象指定一个种别参数,意味着同时给它的两个实型分量指定了同样的种别参数; ● 两个分量都是整型对象,则都转换成默认实型,因而认为是默认复型的; ● 其中一个分量是整型对象,另一个分量是实型对象,则将整型的分量转换成与另一实 型分量相同种别的实型对象,其种别参数就是实型分量的种别参数。 ● 如果两个实型分量的种别参数不同,则具有精度高的分量的种别参数就是复型的种别 参数,并且把另一分量转换为这个精度高的种别形式。 这里显然符合FORTRAN对存储模式一致性处理的一般原则,即当需要统一存储具有不同存储模式的几种数据时,就一律采用需要存储空间最大的那种模式。 应用于复型的固有查询函数与实型相同,其中有关KIND,RANGE,PRECISION的说明参见实型的相应说明。 SELECTED_REAL_KIND也可以直接用于复型对象,用法与功能都与用于实型对象一致。 【例5-24】 COMPLEX (SELECTED_REAL_KIND(5,50))CY !表示复型变量CY至少具有 !5位有效数,同时取值范围 !可达10-50到1050。 5.6.3复型的运算 施加于复型数据的运算包括: ● 一元固有算术运算:求反运算-;求同运算+。 ● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。 ● 二元固有关系运算:大于.GT.或>;大于等于.GE.或=;小于.LT.或<;小于等于.LE. 或=;等于.EQ.或==;不等于.NE.或/=。 这里的算术运算就是定义在复数集合上算术运算。 对复型数据施加固有算术运算的结果总是复型数据,即使二元运算的某个运算元为整型,或实型,结果仍然被视为复型。 固有关系运算的结果为属于默认种别类型的逻辑型数据。 5.6.4 复型常量的形式 复型字面常量的句法(R417)为: (real-part,imaginary-part) 其中实部和虚部可以是带号整型字面常量(R403)与带号实型字面常量(R412)的任意组合。 复型数据对象的种别如何确定,参见上面的6.4.5.2节 【例5-25】 下面是合法的复型常量: (1,3) !默认复型 (7.0,-7.0) !默认复型 (0,3.6E5) !默认复型 (0.5_4,+8.6E29)!如果假定种别参数值4比8所表示的实数精度低,则该复型数据具有 !种别参数值8 【例5-26】 下面是不合法的复型常量: (1.23,) !漏写了作为虚部的整型或单精度实型常量值。 (1.0, 2H12) !不能用Hollerith常量 (,1.23Q0) !漏写了实部 (1.7039E0,-1.7039D0)! 2个常量都不属于REAL(16),但属于合法的双精度常量。 5.7 逻辑型 用来作为任何判断的结果的变量或常量,就是逻辑型数据对象。之所以把它单独归结为一种数据类型,是因为它的取值只有是和否两个,在存储方面可以非常简单,为了充分显示这个特点,有必要单独作为一种数据的类型。 5.7.1名称 逻辑型数据对象的名称为LOGICAL,它的声明语句的句法为: LOGICAL kind-parameter n)] entity-list 其中n可取种别参数1, 2, 4, 或8。 种别参数是可选项,如果没有使用种别参数,则为默认逻辑型。 【例5-27】 下面的声明语句主要说明逻辑型对象。 LOGICAL, ALLOCATABLE :: flag1, flag2 LOGICAL (KIND = byte), SAVE :: doit, don’t 【例5-28】 下面的声明语句主要说明对象的属性。 LOGICAL flag1, flag2 LOGICAL (KIND = byte) doit, dont ALLOCATABLE flag1, flag2 SAVE doit, dont 5.7.2逻辑型数据取值 逻辑型数据取值只有两个可能,即表示真的(.TRUE.)和表示假的(.FALSE.)的两个值,它们后面可以跟可选的下划线与种别参数。 一个系统至少要提供一种表示方法作为默认逻辑型,在默认的情形下,一个逻辑型值需要和默认实型值一样大的存储空间,它的表示方法就是省略可选的下划线和种别参数,此时种别参数值可以通过固有查询函数KIND(.FALSE.)获得。 大多数系统还提供其他存储模式,例如一个逻辑型值采用一个bit或一个byte来存储,具体的种别参数的约定依赖于具体的系统。 至于查询数值型数据的种别参数的固有函数SELECTED_INT_KIND和SELECTED_REAL_KIND对于逻辑型没有相应的函数。 5.7.3逻辑型数据的运算 施加于逻辑型对象的运算包括: ● 一元固有运算:非(.NOT.) ● 二元固有运算:与运算(.AND.);或运算(.OR.);逻辑等值运算(.EQV.);逻辑不 等值运算(.NEQV.)。 对逻辑型值施加上面的运算,仍然得到逻辑型值。 5.7.4逻辑型常量 逻辑型常量只有两个,它们也可以附加系统约定的种别参数,句法为: .TRUE. .FALSE. 其中k为可选的种别参数: LOGICAL(1)是1;LOGICAL(2)是2;LOGICAL(4)是4;LOGICAL(8)是8。注意种别参数前面一定要有下划线( _ )。这里的种别参数是与实型数据的种别参数保持一致的。 【例5-29】 .TRUE._BIT .FALSE._LONG !这里的LONG必须是已说明的有名常量,并且具有非负的整常量值。 !其存储模式必须是已经说明的. 5.8字符型 字符型数据对象在系统认可的字符集当中取值,其唯一形式就是由有限个属于系统字符集当中的字符构成字符串。 5.8.1名称 字符型数据的名词为CHARACTER,字符型对象的基本声明句法为: CHARACTER length-parameter kind-parameter])] entity-list 可以看到其中各种可选项的组合情况比较多,为清晰起见,分别写出如下: CHARACTER CHARACTER( n) CHARACTER( len) CHARACTER( len n]) CHARACTER(KIND=n ) CHARACTER*len 其中n取种别参数值1; len是字符长度值,不是种别参数值,一般取正整数值,也可能取星号或特定表达式。 参见表达式 有关CHARACTER声明语句,还参见数据声明 【例5-30】 下面是不同形式的字符型数据的声明语句。 CHARACTER (70)PROJECT CHARACTER (LEN=30, KIND=GERMAN)TRANSFORMATION CHARACTER (LEN=25,KIND=GREEK),DIMENSION(11)::Z1 5.8.2 字符型的取值 字符型能取的值的集合就是所有有限字符串的集合。 所谓一个有限字符串就是一个字符按照文字方式排列的有限序列,如果从左到右对每一个字符进行编号,得到1,2,3.……,n,那么n就是该字符串的长度,即该字符串所有字符(包括重复字符)的数目。 决定一个字符串变量的值的存储空间的因素有两个: ● 字符串的长度; ● 字符的存储单位的大小。 因此这两个因素都是在描述字符型对象时,系统需要知道的参数。 字符长度作为一种种别参数,它的取值分为如下情形: ● 可以等于0,这时称长度等于零的字符串为空串; ● 在没有指定LEN值的时候,默认长度值为1; ● 根据字符数目,取大于0的整数。当然这个整数存在上界,由系统给出约定。 系统至少需提供一种定义字符型数据值的集合的表示方法,一般是会有几种方法,每种方法用不同的种别参数来区分。 种别参数的具体取值依赖于具体的系统的约定。 字符型数据还有一个需要仔细加以约定的属性是字符集的排序问题。 由于字符型数据的运算除了串联之外,还有一类关系运算,需要依据对字符集的字符顺序的规定。一般原则如下: ● 首先出于可移植性的要求,最好把字符集限制在FORTRAN的基本字符集。 ● 空格优先于所有字母与数字; ● 字母按照英文字母表排序; ● 数字按照0,1,2,…,9的顺序排序; ● 字母与数字不能间杂; ● 除了空格,对其他特殊字符与下划线的位置都没有约束; ● 语言标准不要求系统支持ASCII编码,但要求提供固有函数,ACHAR和IACHAR, 用来在系统编码和ASCII编码之间进行转换。 ● 固有函数LGT,LGE,LLE,LLT可用来基于ASCII顺序进行排序比较运算。 5.8.3运算 可以施加于字符型数据的运算包括: ● 串联运算:运算符是//,针对具有相同种别参数的字符型数据而定义。其运算结果仍 然为一个保持种别参数不变的字符型数据。 ● 关系运算: ● 大于,其运算符为.GT.,或>; ● 大于等于,其运算符为.GE.,或=; ● 小于,其运算符为.LT.,或<; ● 小于等于,其运算符为.LE.,或=; ● 等于,其运算符为.EQ.,或==; ● 不等于,其运算符为.NE.,或/=。 关系运算的结果为逻辑型数据,即只能取.TRUE.和.FALSE.两个值之一。 5.8.4字符型数据常量 字符字面常量用可选的种别参数,然后是下划线后跟用撇号或引号为界的,由可表示字符组成的字符串来表示。字符型字面常量的构成句法(R420)如下: ' ' " " 其中: k为可选种别参数,默认情形为1,后接下划线( _ )。 注意字符型数据的种别参数置于常量前面(左边)。 ch 为ASCII字符,或者说系统可表示字符。 前一个撇号或引号称为前定界符,后一个称为尾定界符,它们统称为定界符。 ● 字符型常量的值限制在定界符之间,而定界符本身不属于字符串。 ● 定界符之间的任意字符,包括空格和Tab键都属于字符串,不过对于控制符得参 考系统的约定。 ● 字符串当中可以包含定界符本身,不过就只能看成字符,不能看成是字符型常量 的定界符。当用引号作定界符时,则用一个撇号来表示该字符,当用撇号作定界 符时,则用两个连续的撇号来表示,且其间不夹有空格字符,此时两个连续的撇 号计作一个字符。对字符字面值中的引号也类似地处理 ● 长度为0的字符常量,也就是空串,由两个连续的同样的定界符表示,中间不夹 空格。 ● 如果前定界符前不出现种别参数和下划线,则该字符常量为默认字符型。 ● 对于可表示字符的约定与源程序形式有关。在固定源码形式中,它依赖于系统字 符集,它是除一部分(甚至全部)控制字符外的所有字符;在自由源码形式中,它可以 包含系统的附加图形字符。 【例5-31】 下面是合法的字符常量。 "WHAT KIND TYPE? " 'TODAY''S DATE IS: ' "The average is: " '' “” ‘I’’m a student’ “I’m a student” CHINESE_”汉字的种别参数CHINESE作为命名常量已经被定义” 【例5-32】 下面是不合法的字符常量: 'HEADINGS !缺少尾定界符 'Map Number:" !定界符的首尾不配套 Re:科学计算的语言------Fortran95 第6章 构造数据 固有数据类型只是描述了问题当中出现的基本数据形式,但在实际的计算当中,计算对象往往并不只是限于那些固有数据类型,而是一些数据结构。 例如: 线性数学问题里面的计算对象很少是单纯的标量,而是诸如行列式,向量这样的由 一系列标量组成的数据结构。 甚至在计算过程中,还需要以这种数据结构的某个特定部分作为计算对象,例如行 列式或向量的单独的行与列,或对角线,部分块等 又例如: 样品的属性值可以是多方面的,例如在测量一个导体样品的交流输运性质时,需要考虑的物理量包括电流,电压,相位等,如果以每个样品的属性值作为数据对象,那么这种数据对象就至少包括3个成员:电流,电压,相位。它们的取值的类型不同,精度要求也可能不同,这3个数值同样构成一个数据结构。 因此,进一步FORTRAN 95需要以固有数据对象为基础,能够构造一定的数据结构,从而能够作到基于数据结构进行运算。 本章讨论的就是FORTRAN 95构造数据结构的几种方式: ● 派生数据类型; ● 数据结构的子对象; ● 数据结构的成员; ● 数组; ● 指针。 特别的,我们首先讨论FORTRAN 95关于数据对象的一个基本分类:变量与常量。它们是数据在程序当中的2种基本行为方式,是我们理解FORTRAN 95语言范畴的关键。 6.1数据的2种基本行为—变量与常量 首先我们给出几个抽象的基本概念,在这里只是给出大意而不作更加形式化的讨论,完全只是为了后面行文的方便。读者不用深究它们的精确定义,只需要在后面行文当中遇到这些概念时,知道回到这里稍加体会即可。 ● 数据对象: 计算过程当中,任何充当计算对象的数据内容,都称为数据对象,是最一般意义上 的数据个体,今后提到数据对象,我们可以理解为一个固有数据类型,一个属于某 固有数据类型的变量,一个常量,一个标量,一个数据结构,一个数据结构的成员 或子对象,表达式求值的结果,或函数引用的执行结果(即函数值)。数据对象有数 据类型(固有的或派生的),可以有数据值。可以是一个数据的集合,集合的元素 数目称为秩,每个数据对象有一个秩。它可以是一个标量或是一个数组。有名数 据对象的类型可以显式或隐式地规定。 ● 子对象: 子对象是某些有名对象的一部分,可由程序的其他部分引用和独立地定义。包括数据组的部分,字符串的部分(子串)和结构的部分(成员)。作为子对象的来源的数据对象称为该子对象的父对象。子对象只能用子对象描述符来引用。变量的子对象是变量。 ● 标量: 任何单一的数据对象,无论它是属于固有数据类型还是属于派生数据类型,就称为标量。 ● 数组: 一个标量的集合,如果其中每一个元素都具有相同的数据类型,相同的种别参数,那么无论它们组织成什么形式,例如行,列,块,甚至更高维度,都把这种集合称为数组。 ● 派生数据类型: 一个标量的集合,如果其中元素的种别参数不同,或者数据类型不同,那么把这个 集合称为结构,或派生数据类型。 从程序运行这么一个动态过程的角度来看,特别是基于下面的基本计算模式: 输入数据 程序运行 输出数据 数据可以有2种基本行为方式: ● 数据对象在整个程序运行过程当中保持不变; ● 数据对象在整个程序运行过程当中发生变化。 保持不变的称为常量,发生变化的称为变量。 FORTRAN 95中常量与变量,标量与数组这些概念与早期的FORTRAN版本有微妙的差异。请熟悉FORTRAN77的读者注意。 ● 常量有两种,一种是字面常量,早期的FORTRAN版本称为常量;另一种是命名常量,早期的FORTRAN版本称为常数符号名,显然新的名称突出表达了该数据作为常量的特点,它与字面常量的最大差别在于有一个名字。这个名字可以在它的定义作用域当中任意引用。它们的共同点在于,它们的值在可执行程序执行期间是不变的。 ● 字面常量是标量,根据它的字面的句法构成来表明其数据类型,种别参数以及值。在一个可执行程序中,所有具有相同形式的字面常量具有相同的值。命名常量则是一个与具有PARAMETBR属性的名字相结合的常量。它的数据类型,种别参数和取值,都需要预先定义,而一旦定义之后,在其定义的作用域内都不会改变,而且这个名称也必须接受唯一解释的。 ● 变量的值即使用DATA语句给以初始化也能在程序运行过程当中发生变化。而FORTRAN 95的变量包括了早期FORTRAN标准里面所谓的变量和数组。具有DIMENSION属性的名字在FORTRAN 95标准里面也称为变量,属于数组。 ● 数组是标量的有序集合,它在FORTRAN 95标准当中有许多新的扩充,将在后面作详细讨论。 在程序运行过程当中,对数据的使用首先必须是定义,然后才能是引用,所谓定义和引用的差别在于: ● 对于一个变量,当程序中确定其值时称为定义,而使用其值时称为引用,已经定义了的变量可以重定义或者回复为无定义。 例如,在程序单元中使用DATA语句使得一个变量具有初值,在程序执行过程中又可以对它赋值而得到重定义;如果该变量不具有SAVE属性的话,当该程序单元执行RETURN语句或END语句后,它就回复为无定义状态。 ● 对于一个常量,它的值可以被引用但不能被重定义。命名常量虽然出现的形式是一个名字,而不是一个具体的值,但它只是常量的一个便于引用的标记。 【例6-1】 X = 0 N = X+1 在第一个语句当中,变量X被赋值为常量值0,这个语句的执行与X是否在此之前被定义为其他值无关,总之此时X得到一个定义,此后只要X没有被重定义,它就能用值0被程序引用。 在第二个语句当中,X被引用,并且在执行加1的运算之后,得到的值被赋值给N。 在第一个语句当中的X和在第二个语句当中的N都不是被引用,而是被定义,而在第二个语句当中的X属于被引用。 变量(variable)的句法形式(R601)为; scalar-variable-name array- variable-name subobject 其中子对象(subobject)的句法形式(R602)为: array-element array-section structure-component substring 变量可以是任意的数据类型,在一定的上下文当中,变量也必须取一定的数据类型。 常量父对象的子对象不可能是变量。 6.2数据的结构----派生数据类型 五个固有数据类型已经能够作到描述,或者近似描述整数,实数,复数,逻辑值,字符串这五类常用数据对象。 考虑一下FORTRAN建立这五种相对独立的数据类型的出发点,是有助于我们进一步理解语言的数据描述这个重要任务的。 首先考虑整数。 似乎整数是一切计算的基本出发点,这里是没有省略的余地的,想像一下,没有了整数,我们还能做什么计算呢?别忘了Kronecker的名言:“上帝创造了自然数,其余都是人造的”。 然后就是实数。 由于FORTRAN本质上只能表示有限位有理数,因此实际上也可以通过整数的除法来得到实型数据的近似表示,比方说用一个整数二元组(37,71)来表示37/71这个有理数,相应的也可以用来近似地表示实数,我们可以来衡量一下这样做的利弊: ● 好处就是可以不用建立实型数据类型,也就是不用引入小数点,减少了一个数据类型,也就简化了FORTRAN语言的实现; ● 但同时坏处就是把麻烦留给了程序作者,在处理涉及实数的问题时,肯定会因为不能直接表达实数而增加算法的复杂性,甚至在语言本身,也有可能不得不增加一些函数之类的语言成分,用来解决由此产生的一些问题。 所以FORTRAN的解决方案就是直接引入实数的有限有理小数表示(实际上一般的程序语言都是这样做的),对于语言本身来说,无非就是增加了对小数点的语义解释,增加了对浮点数值的多种存储模式(FORTRAN使用种别参数来进行分类)的建立等,由此而可以在语言当中自然的使用有限有理小数所表示的实数。 再来分析一下复数。 本质上复数就是一个实数的二元组,而且这个二元组与复数是一一对应的,因此可以很完美的通过运用二元实数组来代替复数,这也是大多数程序语言的处理方式。但是FORTRAN的选择是建立一个独立的复型数据类型,尽管得为这个独立的数据类型定义相应的运算,但是由于FORTRAN主要就是面向科学计算的语言,因此FORTRAN能够直接写出复数,成为FORTRAN的一个优点,极大地方便了程序作者。 由此可见,建立一个数据类型的主要功能就是面向问题,以能够直接描述算法为目的。 那么我们是否有可能从实际问题当中归纳出一些数据类型,然后可以期望这些数据类型就可以满足任何问题的需要呢? 显然是不可能的。 一方面,程序语言必须能够精确描述任何需要用它来解决的问题当中出现的数据;另一方面我们不可能给每一个新问题当中出现的独特的数据结构形式规定一种相应的数据类型,唯一的解决方法,就是在FORTRAN里面约定一种语法机制,可以依据这种语法机制来描述与问题要求相适应的数据类型。反过来只要是依据这个语法机制构造出来的语言结构都能被FORTRAN编译器辨认为一种数据类型,从而知道按照什么存储模式来存储该数据,也知道应该对该种数据施加什么运算。 这就是FORTRAN从90版标准以来设置派生数据类型的出发点。 鉴于固有数据类型被认为是真实世界最基本的数据类型,任何的数据对象,或者就是属于某种固有数据类型,或者就是可以由一系列固有数据描述,因此构造派生数据类型的基本思想就是认为一个派生数据等于一系列成员的集合,这个集合构成一种数据结构,而每一个成员或者仍然属于一个派生数据类型,或者就是属于某种固有数据类型。这样对一个派生数据类型的描述,就归结到对它的成员的描述,另外再加上给出该数据类型的名称,并可选择性地描述其他属性。 这样得到的所谓派生数据类型,和五种固有数据类型不同的地方主要在于: ● 每一种固有数据类型都拥有一个特定的名称,用来作为数据声明的关键词;而派生数据类型则需要程序作者自己首先定义一个名称,然后才能在后面作为关键词进行声明。 ● 每一种固有数据类型都已经定义好了自己的常量书写方式;而派生数据类型则依据其成员的常量形式来确定自己的常量的形式。 ● 每一种固有数据类型都已经定义好了自己的特定的运算;而派生数据类型则需要程序作者自己预先依据实际问题的需要来定义好相应的运算。 ● 每一种固有数据类型都已经定义好了自己的取值范围以及相应的多种存储模式;而派生数据类型,则完全依据其成员的取值范围与存储模式,来确定自己的取值范围和存储方式。 ● 派生数据类型可以在数据类型的定义里,就给出该数据类型的默认初始值,使得今后属于该类型的数据对象都一定取该初始值;而固有数据类型的定义里面不含有定义初始值的部分,要想显式地给出变量初始值,得需要使用数据类型声明语句或者DATA语句。 ● 固有类型的名称是全局性的,在任何环境总是可访问的,而派生类型只可在其定义的作用域内访问它们。 FORTRAN定义的数组同样是数据对象的集合,不过派生数据类型与数组的差别在于组成数组的各个成员必须是相同的数据类型,而派生数据类型的特点就在于它的成员可以是属于不同的数据类型。派生数据类型的成员可以仍然是某个派生数据类型,但数组的成员就不能是数组。 要想让数组的成员为数组只能通过间接的方式,即取数组的唯一成员为一个派生数据类型,然后该派生数据类型的成员取为数组。 对于这样构造出来的与具体问题密切相关的派生数据类型,由于其所具有的内部结构个性太强,显然我们需要定义一些与这种结构相关的特征属性,可以用来描述这种个性: ● 正是由于一个已经定义好的派生数据类型具有完全个性化的内部结构,因此如果一个派生数据类型是定义在一个模块内部的话,自然的问题就是这个派生数据类型的面向模块外部的可访问性如何控制。 在默认情形下,一个在模块内部定义的派生数据类型可以被模块外部的任意程序单元访问,这个默认状态等价于使用PUBLIC语句。 反之,也可以通过使用PRIVATE语句来阻止模块外程序单元的访问。 不过,由于一个模块包含许多不同层次的数据对象,因此PRIVATE的作用对象必须是精确指定的,这里分为以下几种情况: · 每一个对象都可以单独地通过使用属性PUBLIC或PRIVATE来指定其可访问性。 · 对于一个派生数据类型,可以在定义类型的语句当中设置可选的PUBLIC或PRIVATE描述符。 · 使用单独的针对类型名称的PRIVATE语句或在类型定义当中使用PRIVATE描述符,可以把对该类型访问控制在本模块之内。 · 在类型定义的内部使用单独的PRIVATE语句,可以只控制该派生类型的特定的组元的模块可访问性,而不影响整个类型的默认下的可访问性。 ● 组成一个派生类型的各个成员可以按照一定的顺序进行存储,这是通过在派生类型的定义当中运用SEQUENCE语句得到的。 一个派生类型的定义当中,成员的叙述上的顺序并不表示它们具有相应的存储上的顺序,只有在定义当中设置一个SEQUENCE语句,才能对这些成员的存储进行排序。从而可以进一步对这些成员应用COMMON语句或EQUIVALENCE语句。 ● 一个派生数据类型的取值范围显然就是它的所有成员的取值范围的组合,使用集合论语言就是各成员值集的乘积。FORTRAN规定了一个语法机制来给派生数据类型构造值的形式,例如用来定义命名常量,这样就可以在类型声明语句或PARAMETER语句当中使用其命名常量。这种语法机制称为派生数据类型的结构构造器。 至此我们可以看到FORTRAN语言对派生数据类型的定义,不是以某种真实世界的自然的数据集合为依据,而是提供一种描述方式,并且在语言当中约定,只有运用这种方式进行描述,就可以成立为一种数据类型。而对于通过这种方式成立的数据类型,同样包含四个方面的基本属性: ●拥有名称,以备引用; ●取值在一定的集合上; ●可以施加的运算; ●常量的写法。 下面就从这四个方面来说明派生数据类型。 6.2.1派生数据类型的构造 给出一个派生数据类型的名称,同时意味着要给出该派生数据类型的完整定义或着说描述。一个完整的派生数据类型的句法形式(R422)为: TYPE :: ] type-name … component-definition-statement … END TYPE 其中: · 可选的访问说明(access-specification)为关键词PUBLIC或PRIVATE。 · 类型名称(type-name)需要由程序作者自己给出,最好是有意义的英文词汇或缩写。 · 私用序列语句(private-sequence-statement)为PRIVATE语句和SEQUENCE语句。 每一条成员定义语句(component-definition-statement)(R425)包含一个数据对象的类型说明(R502),其句法形式为: type-specification :: ] component-declaration-list 其中: · 类型说明(type-specification)表示一个固有数据类型,或者是一个已经定义过的派生数据类型,不过如果这个描述符后面带有POINTER属性,由于指针的赋值特性,那么该成员就可以表示任意可访问的派生类型,包括正在被定义的派生类型自身。 · 成员属性(component-attribute)(R426)只能是POINTER或DIMENSION,即对于指针性质的成员,使用可选的属性POINTER;而对于数组成员,使用可选的属性DIMENSION,后面还可以再加上数组描述。POINTER和DIMENSION这两个属性可以同时出现。 · 成员声明(component-declaration)给出成员的名称。 成员声明(R428)的句法形式为: component-name 其中: · 成员数组说明(component-array-specification)是可选项,放置在括号当中。如果该数组具有指针属性,那么它的形状未定,否则就是显形状。在显形状的描述当中,数组的界都必须是标量整型常量表达式。如果在这里没有指出数组的界,那么一定要在后面的DIMENSION属性里说明。 · 字符长度参数(character-length)是一个可选的标量整型字面常量,它必须以星号开头。当然这个参数只能用于修饰字符型成员。 成员初始化(component-initialization)(R429)取下面2种形式之一: =initialization-expression =NULL() 其中: · 初始化表达式(initialization-expression)用来说明非指针成员的默认初始值。 · =NULL()即把成员作为指针赋值为不带变元的固有函数NULL的结果,表示指针成员的未结合或空置这2种初始结合状态。初始化指针状态的用处在于,很多情形下,要设置一个指针,都必须首先要求该指针具有确定的结合状态。 构造了一个派生数据类型之后,要使用这个派生类型,自然需要声明属于该类型的变量,对于派生类型来说,变量的声明采用如下句法形式: TYPE(type-name) entity-list 注意这个TYPE语句和定义派生类型的TYPE语句在句法上有差别:声明变量时,TYPE后面必须接括号里面的变量所属派生类型名称;而定义派生类型时,后接不带括号的派生类型名称。 构造了一个派生数据类型之后,经常需要引用其中的成员,引用的句法形式为: parent-structure % component-name 其中父结构(parent-structure)指需要引用的成员所属的派生数据类型(或数据结构)。 派生数据类型构造的一般规则如下: ● 派生数据类型的定义当中给出的类型名称不能与任何固有数据类型的名称重合,也不能与任何本模块能够访问的其他派生数据类型的名称重合。必须保证该名称在它的有效作用域内是指称同一个类型对象。而派生数据类型内部成员的名称的作用域只局限于该派生类型内部,因此不同派生类型的成员可以使用相同的名称,即使这些不同的派生类型属于同一个作用域。同样的,不同作用域当中如果出现了相同的派生类型名称,那么它们实际上是不相干的数据对象。 ● END TYPE语句后面或者省略类型名称,或者后接该类型的名称,不能出现不一致的情况。 ● 一个作用域内,一个派生类型的名称只能定义一次。 ● 在一个派生类型的定义当中,PRIVATE语句只能出现一次。 ● 在一个派生类型的定义当中,SEQUENCE语句也只能出现一次。 ● 只有当一个类型定义是放置于一个模块的规则说明部分时,才能使用访问控制符PUBLIC或PRIVATE。 ● 只要派生类型的定义当中出现SEQUENCE语句,就说明该派生类型的所有成员都必须具有顺序属性 (即其中也必须出现SEQUENCE),并且在定义中成员定义的次序就说明了该类型的对象的存储序列。 ● 在一个派生类型的定义当中,必须至少包含一个成员的定义。 ● 在成员定义当中,任何特定的成员属性都只能出现一次。 ● 一个成员只有在具有指针属性时,才可声明为属于被定义的派生类型本身。 ● 任何非指针的数组成员,都必须表示为显形状形式,其上下界都是整型常量 表达式。 ● 如果成员是指定了长度的字符型数据,那么它的长度必须是整型常量描述表达 式。如果没有指定长度值,那么默认值为1。 ● 如果进行成员初始化,那么必须使用双分号间隔符。 ● 在2种初始化方式中,初始化表达式只能用于非指针变量,也可以说等价赋值符号(=)只能用于非指针变量。而固有函数NULL()则是用于指针变量的。 ● 初始化表达式只能在派生类型的作用域当中取值。 ● 一个派生类型内部的成员当中,可以只有部分成员是默认初始化的,即取得默认初始值,或取得默认初始结合状态,而不是一定要求所有成员都必须初始化。 ● 一个成员如果是数组,那么它的默认初始值必须满足数组的定义,可以是一个数组构造器,也可以是一个标量,这个标量表示了该数组成员的每个元素的初始值。 下面给出不同情形下构造派生数据类型的例子: 【例6-2】 TYPE SET INTEGER N, M END TYPE TYPE (SET), DIMENSION (2, 2) :: a, b(3) 在这个派生类型的定义当中,a和b都是属于派生数据类型SET的数组变量。 【例6-3】 TYPE CURRENT REAL::HIGH=5.5, LOW=1.2 END TYPE CURRENT 在这个派生类型的定义当中给出了其唯一实型成员的初始值. 【例6-4】 TYPE employee_name CHARACTER(25) last_name CHARACTER(15) first_name END TYPE TYPE employee_addr CHARACTER(20) street_name INTEGER(2) street_number INTEGER(2) apt_number CHARACTER(20) city CHARACTER(2) state INTEGER(4) zip END TYPE 在上面2个派生类型的定义当中,一个派生数据对象是另一个派生数据对象的成员。 【例6-5】 例6-4中定义的数据对象还可以被第3个数据类型引用,如: TYPE employee_data TYPE (employee_name) :: name TYPE (employee_addr) :: addr INTEGER(4) telephone INTEGER(2) date_of_birth INTEGER(2) date_of_hire INTEGER(2) social_security(3) LOGICAL(2) married INTEGER(2) dependents END TYPE 【例6-6】 TYPE ARTICLE CHARACTER(LEN=100)ABSTRACT INTEGER LINES CHARACTER,POINTER::TEXT(:) END TYPE ARTICLE 在这个派生数据类型里面包含一个指针成员。 【例6-7】 TYPE LINK INTEGER VALUE TYPE(LINK),POINTER::PREVIOUS=NULL() TYPE(LINK),POINTER::NEXT=NULL() END TYPE LINK TYPE (LINK),POINTRE::A_LINK ALLOCATE(LINK) 在这个派生数据类型当中,指针成员的类型已经被定义,这种派生数据类型被广泛应用于链表与树结构。 【例6-8】 TYPE,PRIVATE::FILE INTEGER FILE_NO CHARACTER(LEN=30) FOLDER_NAME CHARACTER(LEN=10) ACCESS_LEVEL END TYPE 这个派生数据类型定义在一个模块当中,但是对这个模块保持私用状态。 6.2.2派生数据类型的取值,运算,以及常量表达式 一个派生数据类型的值集就是它的成员的值集的乘积,也就是它的成员的所有可能取值的所有可能的组合. 我们知道每个固有类型都有一个固定的值集,在任何环境下引用固有类型,这个值集都是一致的,而派生类型的数据值集则完全是根据它的成分的数据值集确定的。 由于派生数据类型本质上是一种数据结构,因此对派生类型对象的运算的定义,需要使用具有OPERATOR界面的函数,而赋值则使用具有ASSIGNMENT界面的子例行程序。详细的有关子例行程序及其界面的讨论参见第13章。 上面给出的派生数据类型的定义过程,实质上就是给出了一个语法机制,根据这个语法机制构造出来的语言结构肯定能够被FORTRAN编译器识别为派生数据类型对象,即或者是属于该派生数据类型的变量,或者是属于它的变量的具体取值。 因此派生数据类型的定义自动给出它的取值的结构构造器,该结构构造器生成一个数据序列,其中每一个数据对应着该类型数据对象的一个成员的取值。而该类型数据对象的所有成员的所有取值的任意组合,正是该结构构造器所能生成的所有常量。 通过这样的结构构造器生成的常量,也可以用来给定义为该派生类型的命名常量赋值。 由于派生数据类型的成员除了可以是固有数据类型之外,还可以是任意的数据结构,特别是设置为 FORTRAN的一种重要的数据结构-数组。在这种情况下,特别定义了一种用来描述派生类型的数组成员的语法机制,称为数组构造器。 当数组构造器的取值全是常量表达式时,得到数组值常量表达式,这样一个表达式可以作为一个命名常量的数组成员。 数组构造器不仅是可以用来给出一个数据结构的成员的值,也可以用来给出任意数据类型的数组值。 6.2.3数据结构构造器 一个属于派生数据类型的常量的句法形式(R431)为: type-name (expression-list) 其中 类型名称(type-name)为某个派生数据类型的名称。 表达式列表(expression-list)为给出派生数据类型的各个成员值的表达式序列。 结构构造器的一般规则如下: ● 结构构造器不能在它所代表的派生数据类型被定义之前出现,也就是说它给出的派生类型名称不能置空。 ● 结构构造器给出的值必须与type-name所指的派生类型的成员一一对应,书写顺序也必须和派生类型定义里各成员的排列顺序保持一致。为了保证与各个成员在类型,种别参数,长度以及秩的一致,有时可能需要对值进行适当的转换。 ● 如果派生类型包含数组成员,那么在各个成员值的表达式序列当中数组的形状,必须与类型定义当中的该数组成员的形状保持一致。 ● 如果派生类型包含指针成员,那么在各个成员值的表达式序列当中,该指针所对应的值,必须是相应指针赋值语句当中的有效目标。 ● 如果结构构造器的所有成员值都是常量表达式,那么该构造器实质上就构成一个派生类型的常量表达式。 【例6-9】 考虑下面的派生数据类型: TYPE EMPLOYEE INTEGER ID CHARACTER(LEN=40) NAME END TYPE EMPLOYEE 可以得到下面的结构构造器: EMPLOYEE(5645, "陈辉") 【例子6-10】 下面的派生数据类型包含另外的派生类型作为成员: TYPE ITEM REAL COST CHARACTER(LEN=30) SUPPLIER CHARACTER(LEN=20) ITEM_NAME END TYPE ITEM TYPE PRODUCE REAL MARKUP TYPE(ITEM) BOOK END TYPE PRODUCE 这时,就需要使用内嵌的结构构造器来得到成员取值: PRODUCE(.70, ITEM (.25, "XIN HUA", "A BOY")) 6.3子串 从符号的意义上来看,下面两个数据对象都是属于字符串: ASDFJASDF 12349123123 如果我们把12349123123看成是标量,那么没有理由不把ASDFJASDF也看成是标量。然后进一步由标量构造成数组,同样可以用字符串作为数组的分量,由于字符串的参数就是它的长度,因此在用字符串构成数组时,有必要限制它们的长度保持一样。 要以长度为参数构造字符串,一个自然的途径就是从一个字符串当中,按照长度截取其连续的某个部分,这就提出了子串的概念。 子串的句法形式(R609)为: parent-string(substring-range) 其中父串(parent-string)的句法形式(R610)为以下诸种形式之一: scalar-variable-name array-element scalar-structure-component scalar-constant 子串范围(substring-range)的句法形式(R611)为: : 其中子串的始点和终点为标记父串里字符位置的下标的整型表达式,始点为子串最左边的字符在父串里所处的下标表达式,终点为子串最右边的字符在父串里所处的下标表达式,显然,始点和终点都处于1到LEN之间,LEN为父串的长度。如果始点值大于终点值,那么系统认为该子串的长度为0。给出始点和终点,就能从父串截取到相应的子串。 子串的一般规则如下: ● 字符串属于字符型数据类型,父串与子串都是字符串。 ● 字符串的下标是按照从左到右的顺序,从1开始,一直到LEN,LEN为字符串的 长度。 ● 子串是父串里从第i个字符开始,到第j个字符结束的连续的一段字符串,I和j 都大于等于1,小于等于LEN,下标为i的字符称为子串的始点,下标为j的字符 称为子串的终点。 ● 如果始点与终点的下标表达式的值不是整型,则必须转换为整型。 ● 如果i没有给出,默认i为1;如果j没有给出,默认j为LEN。 ● 子串的长度可以是0,但不能是负值,如果给出的i大于j,则系统认为子串的长 度为0。 计算子串的长度使用函数MAX,公式为: MAX(ending-position - starting-position + 1,0) ● 如果父串的长度为0,那么它的任意子串的长度都是0。 【例6-11】 CHARACTER(10)signal_peptide signal_peptide = “HLA-A*0301” signal_peptide(1:3) = “HAI” PRINT *, signal_peptide(7:10) 在这个例子里,signal_peptide被定义为长度为10个字符的字符型变量,在第一个赋值语句里,该变量被赋值为字符串“HLA-A*0301”,在第二个赋值语句里,该变量的下标从1到3的子串被引用,并且被赋值为字符串“HAI”,这样signal_peptide的值就变成了“HAI-A*0301”,接下来的打印语句的执行结果是打印下标从7到10的子串,为“0301”。 【例6-12】 CHARACTER*8 A, Asteroid Asteroid = '2000 DG8' A = Asteroid (1:4) 在这个例子里,首先定义了长度为8的2个字符型变量A和Asteroid,然后给出2个赋值语句,对字符型变量Asteroid赋值为'2000 DG8',而对A赋值为Asteroid的一个子串'2000'。 【例6-13】 TYPE nearly_isotropic_comets INTEGER inclination REAL semi-major_axis, eccentricity, perihelion_distance, Absolute_Magnitude CHARACTER*12 designation END TYPE nearly_isotropic_comets TYPE(nearly_isotropic_comets) 2000YEAR CHARACTER*12 ASTEROID, BIG_INCLINATION(5) 由上面定义的字符串,可以得到如下合法子串: BIG_INCLINATION(2) (1:4)!父串为数组元素 2000YEAR % designation(6:12)!父串为结构成员 ASTEROID (1:4)!父串为标量变量 “ABCDEFGH”(N:N+1) !父串为字符常量 如果一个数组的分量为字符串,那么由这个数组构造子串,可以有很多种合法的可能性,例如,BIG_INCLINATION( (1:4),表示对一个由子串组成的数组片断的引用;BIG_INCLINATION(1:4) (1:4),同样表示一个由子串组成的数组片断的引用。因此如果要从一系列长度一致的字符串构造相同下标范围的子串,可以通过对数据组的片断引用而实现。不过必须注意数组片断下标放置于子串下标之前。由于表示数组片断的句法形式和表示子串下标范围的句法形式一样,因此必须使用这个顺序安排,才能得到无歧义的表示。 【例6-14】 下面2个引用含义完全不同: BIG_INCLINATION( (1:4) BIG_INCLINATION (1:4) 前者表示由5个子串构成的数组,后者表示由4个父串构成的数组。 ● 由“ABCDEFGH”(N:N+1)得到的子串既非常量,也非变量,因为它的下标是一个变量,得到的子串称为常量子对象。参见表达式的形式。 6.4结构成员 所谓结构就是一个集合,其元素可以是任意数据类型,也可以是标量或数组,一个结构至少包含一个元素,这样的结构本身就是一种派生数据类型,由同一类型的多个结构又可以组成一个数组。因此我们可以看到,结构的定义包含递归的成分,这样能够保证提供最大的灵活性,以便足够充分地描述实际问题当中出现的数据结构,减少因为数据结构复杂而导致算法复杂的压力。 定义一个结构之后,如果要引用其成员,可以直接使用如下形式: parent ] ... %component 其中: parent为父结构名称。 百分符号(%)称为成员选择符。 component为其左边邻接父结构或成员的成员 section-subscript-list表示片断下标列,如果该下标列包含下标三元组或者向量下标,那么就表示引用一个数组片断。 由于结构定义具有递归成分,因此要引用结构的成员,就会出现很多的情形,更加抽象的理解是把结构成员的引用(R614)看是一种数据引用(R612)的形式。 所谓数据引用,是把任何被引用的数据看成某个数据集合的部件,这种包含关系可以任意多层地嵌套,因此结构成员的引用也可以运用句法形式(参见附录B)递归定义如下: part-reference … 其中部件引用(part-reference)(R613)的形式定义为: part-name 其中片断下标(section-subscript)(R618)的形式定义为以下几种情况之一: subscript subscript-triplet vector-subscript 有关数组片断的下标参见6.5.5节。 结构成员引用的一般规则如下: ● 结构成员的引用作为数据部件的引用,要求被引用的部件是属于多个部件之中的 一个。 ● 作为结构成员引用的数据引用的最右边的被引用的部件,必须是一个部件名称, 如果该部件引用具有形式: part-name (section-subscript-list) 那么被引用的就是一个数组片断,在最简单的情形下,是一个数组元素。 ● 在上述数据引用定义里,除了最右边的部件名之外,每一个部件名称都必须是指 称一个派生类型数据对象。 ● 数据引用时,除了最左边的部件名之外,每一个部件名称都必须是其左边邻接部 件名称所表示的派生类型的成员。 ● 在上述数据引用定义里,任意位置的部件都可以是数组,一个数组右边邻接 的成员不能具有指针属性。 ● 如果父结构具有INTENT, TARGET, 或PARAMETER属性,则其结构成员也具有 同样的属性。 ● 在父结构还没有被声明之前,其结构成员不能被引用。 ● 结构成员如果含有非0秩的部件,那么它的秩就是非0秩的部件的秩,否则秩为0。 结构成员的类型和类型参量(如果存在的话),与最右边的部件保持一致。 【例6-15】 下面定义一个包含2个成员的派生数据类型: TYPE EMPLOYEE INTEGER ID CHARACTER(LEN=40) NAME END TYPE EMPLOYEE 然后声明变量CONTRACT 属于派生数据类型EMPLOYEE,并且引用该变量的一 个成员。 TYPE(EMPLOYEE) :: CONTRACT CONTRACT%ID 【例6-16】 下面定义一个派生类型作为另一个派生类型的成员: TYPE DOT REAL X, Y END TYPE DOT .... TYPE SCREEN TYPE(DOT) C, D END TYPE SCREEN 然后声明变量M属于派生类型SCREEN,并且引用该变量的几个成员: TYPE(SCREEN) M M%C M%D (both of type DOT); M%C has components M%C%X M%C%Y of type REAL. 【例6-17】 下面定义一个包含数组成员的派生类型: TYPE CAR_INFO INTEGER YEAR CHARACTER(LEN=15), DIMENSION(10) :: MAKER CHARACTER(LEN=10) MODEL, BODY_TYPE*8 REAL PRICE END TYPE ... TYPE(CAR_INFO) MY_CAR 【例6-18】下面定义派生类型以及属于该类型的2个变量: TYPE CHARGE INTEGER PARTS(40) REAL LABOR REAL MILEAGE END TYPE CHARGE TYPE(CHARGE) MONTH TYPE(CHARGE) YEAR(12) 如下引用的数组都是合法的: MONTH%PARTS(I) ! An array element MONTH%PARTS(I:K) ! An array section YEAR(I)%PARTS! An array structure component (a whole array) YEAR(J)%PARTS(I) ! An array element YEAR(J)%PARTS(I:K) ! An array section YEAR(J:K)%PARTS(I) ! An array section YEAR%PARTS(I)! An array section 【例6-19】 下面定义的派生类型包含一个已有定义的指针成员: TYPE NUMBER INTEGER NUM TYPE(NUMBER), POINTER :: START_NUM = NULL( ) TYPE(NUMBER), POINTER :: NEXT_NUM = NULL( ) END TYPE 这样的结构可以用来构造链表,注意其中的指针为默认的非结合初始状态。 【例6-20】 下面定义一个私用派生类型: TYPE, PRIVATE :: SYMBOL LOGICAL TEST CHARACTER(LEN=50) EXPLANATION END TYPE SYMBOL 这个派生类型属于模块私用类型,该模块可以被其他作用域访问,但该类型只能被本模块引用。 6.5数组 数组同样是一个集合,它的元素必须是标量,其标量元素可以属于任何的固有数据类型,派生数据类型,甚至是结构,但数组最关键的一个特征是: 数组的所有标量元素必须属于同一个数据类型,并且具有同样的种别参数。 这个特征决定了数组是一种功能强大的数据结构,因为存在大量的实际问题,需要用同一个计算过程来处理大规模的同种类型的数据,因此FORTRAN 95提供了强大的内部运算和固有函数,专门用来处理整个数组,或者数组元素,或者数组片断,同时硬件的并行化发展,也为这一类通常是极大规模的运算,提供了优化的解决方案。 在FORTRAN的早期版本里面,数组是作为数据的一种属性加以描述的,所使用的关键词为DIMENSION。这个规则一直被保留下来,因此任何数据对象只要是具有DIMENSION属性,就一定为数组。 6.5.1数组的结构 数组的结构可以抽象地理解为多维离散空间的点的集合,即这些点都有相同的维度,在每一维上的坐标取值都是离散的标量。 一个具体的例子就是表格。实际上表格总是可以用二维数组加以描述,例如取上章的如下表格: 表6-1表格可以看成数组 作用单元的种类 语句 主程序 模块 数据块 外部子程序 模块子程序 内部子程序 接口块 USE语句 Y Y Y Y Y Y Y ENTRY语句 N N N Y Y N N FORMAT语句 Y N N Y Y Y N 其他声明 Y Y Y Y Y Y Y DATA语句 Y Y Y Y Y Y N 导出类型定义 Y Y Y Y Y Y Y 接口块 Y Y N Y Y Y Y 语句函数 Y N N Y Y Y N CONTAINS Y Y N Y Y N N 可执行语句 Y N N Y Y Y N 如果该表格的行,即作用单元的种类看成是一个维度a;把该表格的列,即语句的种类,看成是另一个维度b,那么该表格的每一个元素都可以用符号E作用单元的种类,语句的种类,或者Ea,b,来表示,其中a可以取7个值: 主程序,模块,数据块,外部子程序,模块子程序,内部子程序,接口块。 b可以取10个值: USE语句,ENTRY语句,FORMAT语句,其他声明,DATA语句,导出类型定义,接口块,语句函数,CONTAINS,可执行语句。 而每个元素Ea,b取值为符号Y或N。 这样我们就把一个表格完全的用一个数组加以描述了。 从上面的例子可以看到,数组的结构是由维度,和每个维度的取值数目决定的。其中: 一个数组的维度的数目称为该数组的秩; 每个维度的坐标数目称为数组在该维度的宽度; 那么一个数组的秩和每个维度的宽度就决定了该数组的形状; 一个数组的所有维度的宽度的乘积,称为该数组的尺度,也就是该数组的元素的数目。 数组的秩至少可以达到7,这是FORTRAN标准所要求的任何编译器都必需达到的数组处理规模,当然实际的编译器所能处理的数组的秩都要大于7。同时FORTRAN标准对于宽度的大小没有限制。 数组的形状可以用类似于向量的形式表示,即括号当中的一串标量数值,用逗号相隔,每个数值表示相应的维度的宽度。例如: REAL A(2,25,10) 表示以实型数值为元素的数组A,含有3维,每个维度的宽度分别是2,25,10。 在描述每个维度的宽度时,可以使用下标范围的形式,也就同时说明了该维度的元素的下标表示方法,例如: REAL A(2,0:24,10) 这个数组可以用上面的语句来描述,但是这里的描述方式更加具体地说明了数组的第2个维度的下标标示方式,即用从0到24的下标了标示25个该维度的坐标。 显然数组A的尺度为 ,即数组A包含500个元素。同样形状的数组A还可以用别的形式加以说明。 【例6-21】 DIMENSION A(2,0:24,10) REAL,DIMENSION(2,0:24,10):: A COMMON A(2,0:24,10) TARGET A(2,0:24,10) 由于机器表达数据的有限性,数组在每个维度上的坐标都必定有上下界,当在数组描述时使用了下标,那么最小下标值就是数组在该维度的下界,最大下标值就是数组在该维度的上界。 必需注意的是这时数组的下标范围可以取任意的整数值范围,而如果没有在数组说明当中说明下标范围,只是给出其宽度n,那么数组在该维度的默认下标范围是1:n,即从1到n的自然数。 因此与上面同样形状的数组A还可以表示为: REAL A(2,-5:19,10) REAL A(2,5:29,10) 关于形状的描述里面的上下界的表示,参见第8章的有关小节。 6.5.2全数组 由于数组是一个数据结构,因此对于数组的引用,可以是引用整个数组,也可以是引用数组的某个元素,某些元素,或数组的片断,具体的分别在于,如果是引用了数组的名称,无论是一个常量数组的名称,还是一个变量数组的名称,只要在引用该数组名称时,不包含该数组的下标列,或数组片断的下标列,那么就是把该数组作为一个整体加以引用,即作为一个全数组。 一个全数组如果具有INTENT,TARGET,PARAMETER这些属性,那么它的任意元素和片断都具有相应的属性; 一个全数组如果具有POINTER属性,注意,它的任意元素和片断都不具有相应的POINTER属性。 6.5.3数组元素 数组元素的引用很简单,直接用下标给出该元素在每一个维度上的坐标即可。 【例6-22】 声明1维数组X如下: INTEGER,DIMENSION(25)::X 那么对X的元素的引用例子为: X(3),X(12),X(24)。 声明3维数组A如下: REAL,DIMENSION(2,25,10):: A 那么对A的元素的引用例子为: A(1,12,5),A(2,22,8),A(1,17,10)。 这里的(1,12,5)等称为下标列。 6.5.4数组片断 数组最灵活的功能体现在能够以数组的任意部分为对象作运算,把一个数组的任意指定部分作为一个新的数组,称为数组片断,而其所属的数组称为该数组片断的父数组。 数组片断的引用,首先是一个父数组变量名称,然后是下标列,不过这里的下标列里面,必需至少包含一个下标是用下标三元组,或者是下标向量来表示的,否则这个引用就成了对数组元素的引用。 下标三元组和下标向量的定义见下节。 【例6-23】 下面是引用了一个数组片断: INTEGER,DIMENSION(25)::X … X(10:16)=78 上面定义了1维整型数组X,其宽度为25,后面引用了X的一个片断,由X的第10个坐标到第16个坐标这7个元素组成,在这个引用当中,X所指定的这7个元素都被赋值为78。 注意这里的坐标引用(10:16)为一个省略了第3项的下标三元组。 6.5.5数组元素和数组片断的句法形式 数组元素的引用实质上就是数据引用,因此数组元素的一般句法形式就是R612。 数组片断的一般句法形式(R616)为一个数据引用,后接一个可选的包含在括号当中的子串范围。子串范围的句法形式见6.3节的R611。 数据引用当中的组分名称可以后接一个可选的片断下标列,片断下标的句法形式(R618)可以是如下3种之一: subscript subscript-triplet vector-subscript 其中下标三元组(subscript-triplet)的句法形式(R619)是: : : 下标向量(vector-subscript)(R621)则就是一般的秩为1的整型数组表达式。 这里的下标和步长(stride)都必须是整型标量表达式。 一般规则如下: ● 如果被引用数据是一个数组元素,那么这个数据引用的每个部件引用都必需是秩为 0,而最后的部件引用必需包含一个下标列。 ● 如果被引用数据是一个数组片断,那么必须有一个部件引用为非0秩,并且或者是 最后的部件引用非0秩的片断下标列,或者是另外一个部件引用具有非0秩。 ● 如果一个数组片断以后接子串范围的数据引用的形式出现,那么最右边的部件名称 必须是字符型。 ● 在数组的每一个维度上,片断的下标都必须给出。 【例6-24】 下面都是合法的数组元素与数组片断的引用: X(2,3) X(1:N:2,M) Y(: , : , (1:3) SCALAR_A % ARRAY_D(L) SCALAR_A % ARRAY_D(1 ) SCALAR_A % ARRAY_E(1:N)%SCALAR_D ARRAY_F(1:N:2) % ARRAY_G(I,J) % STRING(K)( TYPE REPAIR_BILL REAL PARTS (30) REAL LABOR END TYPE REPAIR_BILL TYPE VEHICLE CHARACTER(LEN=35)OWNER INTEGER MILEAGE TYPE(REPAIR_BILL)COST END TYPE VEHICLE TYPE(VEHICLE)BLACK_BURKYELLOW_XIALI PRINT*,BLACK_BURK % COST % PARTS PRINT*,YELLOW_XIALI % COST % LABOR PRINT*,BLACK_BURK % OWNER TYPE(REPAIR_BILL)FIRST TYPE(REPAIR_BILL)FOR2000(10) 【例6-25】 下面都是合法的标量父结构: FIRST % LABOR FIRST % PARTS(I) FIRST % PARTS FIRST % PARTS(I:J) FOR2000(K)% LABOR FOR2000(K)% PARTS(I) FOR2000(K)% PARTS FOR2000(K)% PARTS(I:J) 【例6-26】 下面都是合法的向量父结构: FOR2000 % LABOR FOR2000 % PARTS(I) FOR2000(K:L)% LABOR FOR2000(K:L) % PARTS(I) 【例6-27】 下面都是非法的向量父结构: FOR2000(K:L) % PARTS!非法 FOR2000(K:L) % PARTS(I:J) !非法 FOR2000 % PARTS !非法 FOR2000 % PARTS(I:J)!非法 1. 下标 在对数组元素作引用时,每一个下标都必须处于该元素所属维度的下标上下界之中。 如果下标出现在数组片断的引用当中,那就意味着该被引用的片断相对于其父数组缺少了相应的维度,因此片断的秩也就相应地要减少1,因此在引用数组片断时,如果父数组的某个维度只是以其上下界的某个坐标出现在其片断引用当中,显然该维度实际上在其片断里就被压缩为1点了,也就是减少了一个维度。 2. 下标三元组 所谓下标三元组是一种表示从在某个维度上连续取值的数组里,有规律地按照固定间隔取数组片断的表达形式。 下标三元组的一般形式(R619)见上。 其中第一个数值表示取片断的相应维度上的下界;第二个数值表示取片断的相应维度上的上界;第三个数值表示在从上界到下界,按照固定间隔取坐标时的步长。 下标三元组的一般规则如下: ● 如果下界被省略了,那么所取默认值为父数组所声明的下界; ● 如果上界被省略了,那么所取默认值为父数组所声明的上界; ● 如果步长被省略了,那么所取默认值为1; ● 如果上下界和步长都被省略了,而只剩下冒号(:),那么就是取整个维度范围; ● 如果步长为正整数,那么该片断的下标为一个数列,取值为从下界开始,以步长为 间隔,递增直至不大于上界的最大下标值;这时如果下界大于上界,那么该片断的下 标数列为空集; ● 如果一个片断的下标数列为空集,那么该数组片断的尺度为0; ● 步长不能取0; ● 如果步长为负整数,那么该片断的下标为一个数列,取值为从下界开始,以步长为 间隔,递减直至不小于上界的最小下标值;这时如果下界小于上界,那么该片断的下 标数列为空集; ● 只要按照三元组所取的数组元素都包含在父数组的某个维度范围里面,那么该三元 组所给出的范围可以超出父数组所声明的相应维度范围。 【例6-28】 如果数组A声明如下: A(7,8,9) 而A的一个片断为: A(4:7,5,3:4) 那么该片断的秩为2,比其父数组A的秩小1,因为该片断引用当中的第2个维度出现了一个单独的下标,故维度减小了1;而片断的形状为(4,2),尺度为8,它的8个元素为: A(4,5,3) A(4,5,4) A(5,5,3) A(5,5,4) A(6,5,3) A(6,5,4) A(7,5,3) A(7,5,4) 【例6-29】 给出数组A(100),而它的一个片断为A(45:28:-5),那么该片断的秩为1,形状为(4),尺度为4,它的所有元素为: A(45) A(40) A(35) A(30) 而如果取其片断为A(45:28),这时默认步长为1,显然其尺度为0; 如果取其片断为A(25:110:35),尽管第2个下标超出了父数组A的下标范围,但是该片断的元素却没有超出父数组的元素范围,因此这种片断取法是合法的,它的所有元素为: A(25) A(60) A(95) 3. 下标向量 鉴于依靠下标三元组所截取的数组片断是具有规则形式的数组片断,当我们需要其他形式的数组片断时,三元组就无法胜任相应的描述数组的任务了,因此FORTRAN给出了一种具有最大的灵活性的截取数组片断的方式,即下标向量。 所谓下标向量,就是由一些下标值构成的一个向量,这些下标值取自父数组在某个维度上的下标范围,由与这些下标值相应的坐标就构成了父数组的一个数组片断。 【例6-30】 INTEGER I(10) REAL A(100) … I=(/ 23,35,11,46,57,65,78,89,90,1/) A(I)=3.34 这段例程里首先定义了2个秩为1的数组变量I(10)和A(100),也即向量,然后在后面用赋值语句给出I的一个具体赋值,接着以I为数组下标变量,给出了数组A的部分元素的一个具体赋值,实际上,这里I就是下标向量,而A(I)则是由下标向量I所给出的父数组A(100)的一个数组片断。 注意其中根据向量I的赋值,A的片断截取是任意顺序的,这就体现了利用下标向量来截取数组片断的特点。 不过虽然由下标向量来截取数组片断具有极大的灵活性,但是根据下标向量而得到的数组片断具有如下限制: 1.不能作为外部文件; 2.不能作为指针目标; 3.不能作为哑元INTENT(OUT)或INTENT(INPUT)的实元。 下标向量的构成允许出现重复的元素,例如在上面的例子里面,下标向量I可以赋值为: I=(/ 23,35,23,46,57,65,78,89,90,1/) 这样A(23)就会被访问2次,即A(I(1))和A(I(3)),这样得到的数组片断仍然是合法的,被称为多对一数组片断,这样的数组片断不能出现在如下场合: ● 赋值语句当中等号的左边; ● READ语句的输入项。 6.5.6下标,下标三元组和下标向量的混合使用 上面说明了如何分别使用下标,下标三元组和下标向量这三种方式来截取数组片断,实际上,这三种方式可以根据具体问题的要求来混合使用,下面是1个例子。 【例6-31】 给出父数组A如下: A(20,10,20) 可以构造A的一个数组片断如下: A(4:22:7, 9, 10:15:3) 该片断的所有元素如下: A(4,9,10) A(11,9,10) A(18,9,10) A(4,9,13) A(11,9,13) A(18,9,13) 然后我们还可以给出下标数组(向量): INTEGER DIMENSION(4)::I=(/3,7,3,9/) 运用这个下标向量可以构造数组片断: A(10:20:8, I, 14) 该数组片断的所有元素为: A(10,3,14) A(18,3,14) A(10,7,14) A(18,7,14) A(10,3,14) A(18,3,14) A(10,9,14) A(18,9,14) 可以看到其中包含了重复的元素,即多对一数组片断。 6.5.7数组元素序 由于计算机在实质上只能按照一维的串行顺序来处理数据,因此对于高于1维的数组,就产生一个如何表达其中数据的问题,这就需要约定一个统一的把多维数组转化为一个便于计算机处理的数据序列的方法,按照这个统一约定得到的数列就表示了数组元素的顺序,即数组元素序。 由于一个多维数组的各个维度是可以用序列的方式表示出来的,因此一个自然的表达数组元素序的方法就是,规定数组维度的顺序即各个维度被依次遍历的顺序, 【例6-32】 取如下2维数组: A(2,3) 按照它的形状的表示方法,它的第1维有2个下标,第2维有3个下标,那么这个数组的元素的排列顺序就是: A(1,1),A(2,1),A(1,2),A(2,2),A(1,3),A(2,3)。 即先遍历第1维的2个下标而保持第2维的下标不变,然后再遍历第2维的下标,直观地看,就是依照数组元素顺序排列下来后,第1维的下标的变化最快,而最后1维的下标变化最慢,其他维度的下标的变化依次类推。 对于按照数组元素序排列的数组元素数列,可以给以这个数列本身的下标标记,这个下标称为下标顺序值。 按照上面约定的从多维数组到串行序列的转换规则,可以给出一般的多维数组的元素的下标顺序值的计算公式。 当然,对于一个数组片断,它的元素的下标顺序值不能指定为其所在父数组里的下标顺序值,而只能是把该片断本身看成一个数组之后,重新给出的下标顺序值。 例如对于数组A(10),取其片断A(2:9:3),该片断元素包括A(2),A(5),A(8),对于1维数组A(10)来说,这些元素的下标顺序值就是它们的下标,即2,5,8。而对于数组A(2:9:3)来说,它们的下标顺序值却分别是1,2,3。 6.5.6数组构造器 所谓数组构造器就是给数组元素赋值时的句法形式,即数组元素的写法。由于语言的表述在本质上只能是一个字符串,因此数组元素的取值的写法只能是针对1维数组而言,因此数组构造器在形式上得到的是一个秩为1的数组的元素具体取值。 作为一个秩为1的数组的元素,自然的形式就是一个元素组成的序列,但是为了在句法上标志其构成一个数组,约定使用一对括号与斜线把所有元素值和其上下文区分开,因此一个数组构造器的句法形式(R432)为: (/ ac-value-list /) 数组构造器元素取值列(ac-value-list)中的元素取值作为数据对象,可以是表达式(R723),或者是隐性do变量取值(R434),所谓隐性do变量取值是直接以变量的形式来表示do结构含义的变量的具体取值。 【例6-33】 (SQRT(REAL(K)),K=10,13) 这里REAL(K)为变量形式,(SQRT(REAL(K)),K=10,20)则具有do结构的含义,经过K的具体取值,得到一个数组: (/ 3.162,3.317,3.464,3.606 /) 它的一般句法为: (ac-value-list,ac-do-variable=scalar-integer-expression, scalar-integer-expression ) 其中数组构造器值可能有三种形式: ● 标量表达式。 【例6-34】 /2.4,6.4,2.4,2.5,9.8/) ● 数组表达式。 【例6-35】 (/X(1:10,I),X(30:30,I-2)/) ● 隐式do变量。 【例6-36】 (/(SQRT(REAL(I)),I=0,10)/) 数组构造器的一般规则为: ●数组构造器里的每一个元素取值都必须是相同的数据类型,相同的种别参数, 相同的长度参数。 ● 数组构造器的数据类型与种别参数就是它的表达式取值的数据类型与种别参数。 ● 如果数组构造器不含有表达式或者其隐性do变量无法实际取值,那么得到一个 秩为0,尺度为0的数组。 ● 数组构造器do变量必须是标量整型命名变量,该变量具有数组构造器do变量的 作用域。 ● 如果一个数组构造器do变量包含在另一个数组构造器do变量里面,它们不能是 同一个变量
个人分类: 计算机|0 个评论
[转载]Fortran中公共块的运行和修改
liuph 2010-11-26 19:53
转自:编程爱好者论坛 Fortran区的一篇置顶文章 最近,好几个人问到公共块出错的问题,都是出于一个原因,特在这里把老贴整理一下供大家参考。 问题: 20924057 Compiling Fortran... D:\FRAME2D.FOR D:\FRAME2D.FOR(314) : Warning: Because of COMMON, the alignment of object is inconsistent with its type COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6) --------------------------^ D:\FRAME2D.FOR(314) : Warning: Because of COMMON, the alignment of object is inconsistent with its type COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6) ---------------------------^ 解答: 这是老Fortran的规则。看你的公共语句: COMMON /EM/LM(6),ND,ASA(6,6),RF(6),SA(6,6),SF(6),T(3,3) 根据程序的隐含规则,LM(6),ND一共是7个整型数,每个整型数4个字节,共28个字节,不是8字节的整倍数。紧跟着是双精度的ASA(6,6),每个数是8个字节,可是ASA(1,1)不能从8字节整倍数位置开始存储,这就是the alignment of object is inconsistent with its type 的意思。 对于警告错误,可以忽略,一般不致于影响执行结果。如果要改的话,有几种办法: 1)在公共语句中,把双精度数放在前边,整形数跟在后边; 2)在ASA()前插一个整型变量(哪怕是没用的),用来占用4个字节,以使得后面的双精度数可以从8字节整数倍位置开始存储。 准则:作为好的编程习惯,建议在公共块中,把实型变量排在前边,把整型数据放在后边,就不会有对位不整的错误! 仅供参考。 补充:(05.06.01) 公共块不是用堆栈实现的,是内存中的一段连续存储的数据。 按照Fortran的规定,当读取双精度数据时,总是假定前面的数据长度是双精度数字节长度(8个字节)的整数倍。 对于本例,ASA(1,1)从第29个字节开始存放8个字节;可是读取的时候,要从第33个字节(28不是8的倍数,32是28之后最小的8的倍数)开始读入8个字节,这就是定位(alignment)错误。 所以F90之后不提倡用公共块共享数据,而可用更为灵活的module来代替公共块共享数据。 公共块是过时的语言功能
个人分类: 生活点滴|5957 次阅读|0 个评论
[转载]如何在fortran中读写文件时不换行
热度 1 liuph 2010-10-12 22:21
如何在fortran中读写文件时不换行(上传供大家学习) 转自ifelseif的博客 如何在fortran中读写文件时不换行?这是个极简单又极复杂的问题,简单到只要一个字符,复杂到翻破了好几本语法书也没找见。fortran中默认一条read或者write结束之后就换一行,但是读和写还有些不太一样。 读文件时,read之后如果写了一个数组,就像这样: read(10,*)Y(1:n) 整整一行数就全都读到数组里了。但是如果用write,写到文件中却不是这个样子,会给你一个超级长的文件然后每行只有一个数。有一个选项叫ADVANCE='YES'/'NO',可以控制输入输出语句完了之后要不要换行,默认是'YES',很不幸,在intel的fortran中这个选项只对read起作用,write依旧不行。 在fortran的输入输出中,/表示换行,那么\表示什么意思呢,就是不换行。这是我在网上逛了老半天才看到的,为了防止忘记,写到博客里面,立此存照。 下面是一段fortran代码样例,要处理的数据20个数就会换一行,一般来讲最后一行是不满20个数的,需要用个同余判断一下 PROGRAM MAIN IMPLICIT NONE INTEGER I,J,NY,A,B REAL X,Y(60),Z(60) OPEN(UNIT=10,FILE='SX-RIVER.TXT') OPEN(UNIT=11,FILE='SX-OUTPUT.TXT') DO I=1,9 READ(10,*) END DO DO I=1,372 !!!!!!!!! DATA INPUT !!!!!!!!!!!!!!!! READ(10,(18X,I2,1X,F7.3))NY,X CALL MOD(20,NY,A,B) DO J=1,A READ(10,(20(1X,F7.2)))Y(20*(J-1)+1:20*J) END DO DO J=1,B READ(10,(1X,F7.2),ADVANCE='NO')Y(20*A+J) END DO READ(10,*) DO J=1,A READ(10,(20(1X,F7.2)))Z(20*(J-1)+1:20*J) END DO DO J=1,B READ(10,(1X,F7.2),ADVANCE='NO')Z(20*A+J) END DO READ(10,*) !!!!!!!!! DATA OUTPUT !!!!!!!!!!!!!!! !WRITE(11,(I4,1X,F7.3))NY,X WRITE(11,(1X,F7.2,\))Y(1:NY) WRITE(11,*) WRITE(11,(1X,F7.2,\))Z(1:NY) WRITE(11,*) END DO CLOSE(10) CLOSE(11) STOP END PROGRAM MAIN !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! SUBROUTINE FOR CONGRUENCE(TONGYU) ! ! Y=A*X+B ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! SUBROUTINE MOD(X,Y,A,B) IMPLICIT NONE INTEGER X,Y,A,B,TMP A=1 TMP=X DO WHILE(Y.GT.TMP) A=A+1 TMP=TMP+X END DO A=A-1 B=X-(TMP-Y) RETURN END SUBROUTINE MOD
个人分类: 生活点滴|11257 次阅读|0 个评论
[Fortran]用Unformatted格式生成的超大文件的另类通用读法
lefang 2010-10-1 16:40
有一个问题困扰我许多年了:同样是Fortran语言用Unformatted格式生成的数据文件,在32位和64位机器上大小不同。关于这一问题,在 这里 描述道: Update (Jan 2008): It would appear that on 64 bit machines the 2 header elements are each written as 4 bytes instead of 2 bytes each. 但也没有给出解决方案。 终于我想到一个另类做法,可以通用地读取这样的文件。 假设原来的文件是这样生成的: real*8 u_tmp(k1:kmax,k1:kmax,0:n/16-1) open(10,file=filename,form='unformatted') write(10)(((u_tmp(i,j,k),i=k1,k2),j=k1,k2),k=0,n/16-1) close(10) 如果原来文件是在64位机上生成的,那么不需要做特殊处理。否则,假设生成的文件是u.dat,则另外新建一个4字节的文件,比如叫tmp。执行 cat z u.dat u_new.dat 将4个字节插到u.dat的文件头,使得新文件的偏移量和原文件相同。 接着,用下面的程序读取 open(10,file=dat(ifn),access='direct', recl=8) do k = 0, n/16-1 do j = k1, k2 do i = k1, k2 read(10, rec = k*n*n+(j-k1)*n+i-k1+2) u_tmp(i,j,k) enddo enddo enddo 这仅仅是针对real*8的读法,但其他格式的做法也类似
个人分类: GNU Linux|8357 次阅读|0 个评论
[FP-LMTO方法] LmtART 7.04 的安装
热度 5 wonvein 2010-9-25 21:24
LmtART使用Fortran 90编写,用完全势线性muffin-tin(FP-LMTO)轨道方法计算电子结构。它在密度泛函理论的框架内进行键结构,总能量,和力的计算。 详细介绍见: 官方主页: http://www.fkf.mpg.de/andersen/docs/lmtoart_programs.html 官方主页: http://www.physics.ucdavis.edu/~mindlab/MaterialResearch/Scientific/index_lmtart.htm 最近用到lmtART这个软件,安装时遇到了问题,发现这个软件并不提供Makefile文件,而且是f和f90混编。为了编译LmtART,我只好自己写了Makefile文件,可以顺利编译。 其安装步骤为: 1 Edit the file man_lmtsetup.f (lines 119-122) and specify the path to the scratch and atomdat directories. Also check that other items match your computer settings. NAMEDENS='/public/home/wangwei/Apps/LmtART/den.' ! atomic density data NAMEATOM='/public/home/wangwei/Apps/LmtART/rat.' ! input atomic files NAMESTRC='/public/home/wangwei/Apps/LmtART/str.' ! structure files NAMELMTO='/public/home/wangwei/Apps/LmtART/lmt.' ! LMTO atom files 2. make Makefile for LmtART: # Makefile for LmtART v7.04 # Written by Wei Wang # 2010/07/26 #------------------------------------------------------------------------------- # Fortran compiler #------------------------------------------------------------------------------- FC=ifort FFLAGS = -O3 AR = ar #------------------------------------------------------------------------------- # Suffix rules #------------------------------------------------------------------------------- .SUFFIXES: .o .f .f.o: $(FC) $(FFLAGS) -c $ .SUFFIXES: .o .f90 .f90.o: $(FC) $(FFLAGS) -c $ #------------------------------------------------------------------------------- # Source files #------------------------------------------------------------------------------- SRC_mod_F77= mod_atoms.f mod_common.fmod_hubbard.fmod_models.f mod_qmc.f mod_supra.f\ mod_dimart.fmod_init.f mod_phonons.fmod_response.fmod_work.f SRC_mod_F90=mod_cls_l_lib.f90mod_cls_l_time.f90 mod_cls_l.f90 SRC_main=man_main.f SRC_F77=bnd_allocate.fchi_hubpar.f del_dtosint.f dmf_lmtqup.f frc_energy.f imp_hybint.f ini_makettr.f lib_ranw.f mdl_init.f opt_optpar.f pot_multasa.f rat_shletot.f \ bnd_bndpar.f chi_intchi.f del_dtospar.f dmf_lmtrat.f frc_forces.f imp_hyblev.f ini_readini.f lib_ratmom.f mdl_mixdmf.f opt_optpsi.f pot_multftr.f rat_shlfiles.f \ bnd_conv.f chi_inthiq.f del_dynbnd.f dmf_lmtrea.f frc_intvar.f imp_impfock.fini_readpnt.f lib_relharm.f mdl_modelhyb.fopt_optptb.f pot_multrho.f rat_shlmat.f \ bnd_coreny.f chi_lmtchi.f del_dynsym.f dmf_mixdmf.f frc_lmtvar.f imp_impfsig.fini_readscf.f lib_simq.f mdl_readmdl.f opt_opttet.f pot_potfiles.frat_siggrf.f \ bnd_fndfrm.f chi_lmtvec.f del_epibnd.f dmf_ordbnd.f frc_pulend.f imp_imphub1.fini_readstr.f lib_sphfun.f mdl_scfhub1.f opt_setopt.f pot_print2.f rat_slater.f \ bnd_ftrmat.f chi_magmat.f del_episym.f dmf_ordvec.f frc_pulint.f imp_impirat.fini_rpr48.f lib_sphharm.f mdl_scfirat.f opt_symopt.f pot_vcftr.f rat_storage.f \ bnd_getenr.f chi_magpar.f del_lmtvec.f dmf_setdos.f ftb_allocate.fimp_impqmcf.fini_storage.f lib_splin3.f mdl_scfqmcf.f phn_ctrlinf.f pot_vcoul.f rat_vcoul.f \ bnd_intmat.f chi_mttrd.f del_nmtfiles.fdmf_setfat.f ftb_denmat.f imp_impratf.fini_test.f lib_splkff.f mdl_scfratf.f phn_deltet.f pot_vexch.f rat_vexch.f \ bnd_intpar.f chi_mttrs.f del_psivec.f dmf_setfrs.f ftb_energy.f imp_impsbmf.flib_broy6.f lib_stnmat.f mdl_scfsbmf.f phn_dynmat.f pot_vxftr.f rho_emader.f \ bnd_lmtbnd.f chi_orbpar.f del_rintpar.f dmf_setgrf.f ftb_ftbbnd.f imp_setdos.f lib_calc.f lib_timel.f mdl_setgrf.f phn_epitet.f pot_vzero.f rho_mixro1.f \ bnd_lmtdft.f chi_printq.f del_rtosint.f dmf_sethyb.f ftb_ftbfat.f imp_sigrat.f lib_cinv.f lib_transcub.f mdl_storage.f phn_init.f qmc_allocate.frho_mixrob.f \ bnd_lmtfat.f chi_setchi.f del_sgmpar.f dmf_sgmdat.f ftb_ftbhub.f imp_sunhub1.flib_cmpdiag.f lib_wigmat.f phn_readlrt.f qmc_ctrlinf.f rho_orbmag.f \ bnd_lmtmat.f chi_sethiq.f del_strvec.f dmf_sgmmas.f ftb_ftbmft.f imp_sunirat.flib_conjgrad.f man_artinp.f phn_runtask.f qmc_getgrf.f rho_rencor.f \ bnd_lmtpar.f chi_setjey.f del_symdps.f dmf_sigmaw.f ftb_ftbpar.f imp_sunsbmf.flib_corsch.f man_artout.f phn_setepi.f qmc_init.f rho_renrov.f \ bnd_nmtpar.f chi_symchi.f del_symdrho.f dmf_tosint.f ftb_ftbstr.f imp_vertex.f lib_csplines.f man_atoms.f phn_setphn.f qmc_qmcpra.f rho_rhofiles.f \ bnd_orbpar.f cls_chem1.f dmf_allocate.fdmf_tospar.f ftb_hubint.f ini_blowmts.flib_cubharm.f man_electrons.fphn_spectra.f qmc_qmcyif.f rho_rhoful.f \ bnd_ovrpar.f cls_chem2.f dmf_denmat.f dmf_ttrint.f ftb_mixden.f ini_ctrlinf.flib_delrad.f man_ftbscf.f phn_storage.f qmc_readqmc.f str_dstr.f \ bnd_placeny.f cls_shellxnot.fdmf_energy.f dpt_delpot.f ftb_symden.f ini_getchi.f lib_deriv1.f man_impscf.f phn_symnuc.f qmc_setgrf.f str_hstr.f \ bnd_potpar.f del_allocate.f dmf_fndfrm.f dpt_delvhub.f hop_alpcon.f ini_getdos.f lib_det.f man_lmtchn.f plz_allocate.fqmc_storage.f str_sitegen.f \ bnd_seny.f del_d2intpar.f dmf_ftbfat.f dpt_dmultftr.fhop_findirr.f ini_getfat.f lib_dgemm.f man_lmtscf.f plz_plipar.f rat_agfmat.f str_strmsh.f \ bnd_setdos.f del_d2lmat.f dmf_ftbima.f dpt_dmultrho.fhop_gethtb.f ini_getfrs.f lib_dilog.f man_lmtsetup.f plz_plsbnd.f rat_allocate.fstr_vecgen.f \ bnd_setenr.f del_d2lmto.f dmf_ftbmas.f dpt_dvcftr.f hop_readhop.f ini_getgrf.f lib_drsub.f plz_plssym.f rat_cgfmat.f sup_ctrlinf.f \ bnd_seteny.f del_d2tosend.f dmf_ftbqup.f dpt_dvcoul.f hop_scrcon.f ini_gethiq.f lib_eigen1c.f man_mdlchn.f odf_allocate.fplz_plwcrd.f rat_clsgor.f sup_elifun.f \ bnd_setfat.f del_d2tosint.f dmf_ftbrat.f dpt_dvexch.f hop_scrstr.f ini_gethyb.f lib_erf.f man_mdlsetup.f odf_ftbodf.f plz_plzbnd.f rat_clsmat.f sup_init.f \ bnd_setfrs.f del_d2vint.f dmf_ftbrea.f dpt_dvxftr.f hop_stralp.f ini_getodf.f lib_forcesym.f man_models.f odf_lmtodf.f plz_plzend.f rat_clssav.f sup_omegaq.f \ bnd_strgnt.f del_delbnd.f dmf_grfbnd.f dpt_gradpot.f hub_hubpar.f ini_getopt.f lib_formt.f man_phead.f odf_odfbnd.f plz_plzfiles.frat_clsyin.f sup_phndos.f \ bnd_sum.f del_deleny.f dmf_grfexp.f dpt_potfiles.fhub_hubpot.f ini_groups.f lib_gradfun.f man_phnchn.f odf_odfint.f plz_plzinl.f rat_ctrlinf.f sup_readdyn.f \ bnd_symtos.f del_delexp.f dmf_grfgrp.f dpt_print3.f hub_mixhub.f ini_init.f lib_hubmat.f man_phnsetup.f odf_odfmat.f plz_plzint.f rat_energy.f sup_readepi.f \ bnd_tosend.f del_delgnt.f dmf_grflev.f dpt_scrpot.f hub_msbmesh.f ini_makeatm.flib_inverse.f man_phonons.f odf_odfpar.f plz_plzmat.f rat_getads.f sup_readsup.f \ bnd_tosint.f del_delmat.fdmf_grfloc.f dro_dbroy4n.f hub_readhub.f ini_makeenv.flib_lsqmom.f man_qmcchn.f odf_odfpsi.f plz_plzmto.f rat_getgrf.f sup_storage.f \ bnd_tospar.f del_delmto.f dmf_grfmat.f dro_delrho.f hub_readrep.f ini_makefft.flib_mklegw.f man_qmc.f odf_odfttr.f plz_plzmts.f rat_init.f sup_ttrint.f \ bnd_ttrint.f del_dhubpar.f dmf_grfpar.f dro_drofiles.fhub_rhohub.f ini_makegnt.flib_morefun.f man_qmcsetup.f odf_setodf.f plz_plzpar.f rat_matatm.f sup_widths.f \ chi_allocate.fdel_dinlmat.f dmf_grfwgt.f dro_gradrho.f imp_agfirat.f ini_makegrp.flib_order.f man_ratchn.f odf_symodf.f plz_plzpin.f rat_mixrat.f ttr_fermicof.f \ chi_chifiles.fdel_dintmat.f dmf_hubmat.f dro_gradrps.f imp_combrep.f ini_makehan.flib_pade.f man_ratsetup.f opt_allocate.fplz_plzsym.f rat_ratden.f ttr_fermiint.f \ chi_chimat.f del_dintpar.f dmf_imphyb.f dro_magdro.f imp_crfhub1.f ini_makeplw.flib_paulim.f man_supchn.f opt_ftbopt.f plz_polarz.f rat_ratmesh.f ttr_mcttrint.f \ chi_chipar.f del_dlmtpar.f dmf_implev.f dro_mixbrd.f imp_crfirat.f ini_makerad.flib_pcoefs.f man_supra.f opt_lmtopt.f plz_screen.f rat_ratscf.f ttr_ttrvel.f \ chi_chitet.f del_dnmtpar.f dmf_impmod.f dro_mixdps.f imp_crfqmcm.f ini_makescf.flib_prattpols.fman_supsetup.f opt_optdhk.f pot_exchcorr.frat_readrat.f \ chi_delmsh.f del_dovrpar.f dmf_lmtfat.f dro_mixdro.f imp_crfsbmf.f ini_makesmt.flib_pzeros.f mdl_allocate.f opt_opthan.f pot_gga91.f rat_setads.f \ chi_ftbchi.f del_dpotpar.f dmf_lmtima.f dro_spldps.f imp_fftsub.f ini_makesym.flib_qd.f mdl_ctrlinf.f opt_optint.f pot_gga96.f rat_setgrf.f \ chi_ftbvec.f del_dtosend.f dmf_lmtmas.f dro_spldro.f imp_hybfun.f ini_maketei.flib_radsch.f mdl_getgrf.f opt_optmat.f pot_lsda.f rat_shells.f SRC_F90=cls_angular1.f90 cls_exactdiag.f90 cls_fockvec1.f90cls_greenfun1.f90cls_hamilton2.f90cls_main1.f90 cls_subrtn.f90qmc_run.f90 qmc_sampling_PC_diag.f90 \ cls_angular2.f90cls_fill1.f90 cls_fockvec2.f90cls_greenfun2.f90cls_impurity.f90 cls_main2.f90lib_rapx.f90 qmc_run_switch.f90qmc_sampling_PC_random.f90 \ cls_bath.f90 cls_fill2.f90 cls_fun.f90cls_hamilton1.f90cls_l_diag.f90 cls_main.f90 qmc_fourier.f90 qmc_sampling.f90 OBJ_mod_F77 = $(SRC_mod_F77:.f=.o) OBJ_mod_F90 = $(SRC_mod_F90:.f90=.o) OBJ_F77 = $(SRC_F77:.f=.o) OBJ_F90 = $(SRC_F90:.f90=.o) OBJ_main = $(SRC_main:.f=.o) OBJ=$(OBJ_mod_F77) $(OBJ_mod_F90) $(OBJ_main) $(OBJ_F77) $(OBJ_F90) EXE = lmtart lmtart: $(OBJ) $(FC) $(FFLAGS) -o $(EXE) $(OBJ) $(LIB) clean: rm -f *.o *.mod *~ fort.* ifc* *.log $(EXE)
个人分类: 量化软件|9652 次阅读|4 个评论
这里有一本fortran编程的书
热度 3 huang840828 2010-9-25 20:24
我今天弄到了一本书,名字是:Sample page from NUMERICAL RECIPES IN FORTRAN 77 THE ART OF SCIENTIFIC COMPUTING 1100多页啊,刚才看了一下。这本书太牛了,几乎面面俱到。谁要是把这本书一个一个地练习一遍,我觉得吧:不但是fortran编程对他小菜一碟,就是有限元以后也不是什么难的东西了。有点夸张啊,太高兴了,原谅原谅! 呵呵,找了一个晚上,最后是有点巧取豪夺弄过来的。谁要的话,可以说一声。大约7M,最好是留QQ邮箱吧! 下面给出本书的目录吧: 目录 分卷压缩: part1 分卷压缩: part2 我压缩的时候没注意,这里下载之后注意要把扩展名.zip改为.rar
个人分类: Fortran|5342 次阅读|16 个评论
最简单的语言和最复杂的语言
热度 1 马红孺 2010-5-5 20:57
最近在学习 Matlab, 据说这是一个功能强大,易学易用的软件。学得很吃力,有那么多函数,怎么都记不住。 记得大学时开了一门课,叫FORTRAN程序设计,一学期,可能是36学时(或54?),没有见过计算机, 什么也没学会,只记得那位老师把FORTRAN读成 伐儿犬。大四做毕设,需要算一个定积分,开始在程序 纸上写程序,送给穿孔员做卡片,再把一叠卡片交给操作员,在机房外面等结果,才开始对Fortran有点认识。 研一时,导师买了一台IBM PC,8086 cpu,128k 内存(后扩充到384k),两个320k的5吋软驱, DOS1.0操作系统,带一个BASIC程序。开始用Basic算题。过了一阵,升级到dos2.0,又买了 Fortran编译器,突然发现用Fortran比用Basic快了很多,于是知道了编译语言和解释语言的差别。 在导师的带领下,我们一帮师兄弟在这台机器上制造了50余篇文章,还拿了一个自然科学四等奖。 过了一阵,感觉Fortran还是不够快,于是便拿着Dos2.0的说明书,用Debug写汇编程序,果然速度 又有提高。 再后来,计算机不断升级,但一直用Fortran写程序,从Fortran 4, 再到Fortran 77。 在用了10多年Fortran后,开始带学生了,突然发现学生竟然不懂Fortran,而是用C写程序。 为了与时俱进,借着给学生看程序的机会学会了C编程,后来为了赶时髦,又写过一点Java 程序。 比较起来,汇编语言是最简单的,学起来最容易,上手也最快,但要写一个大一点的程序, 需要付出的劳动最大。 Fortran和C差不多, Java稍为复杂一些,主要是比Fortran和C多了一些 概念。感觉最复杂的是 Matlab,折腾了两个星期了,还是记不住那些函数和命令。
个人分类: 胡言乱语|13866 次阅读|12 个评论
详论fortran格式化输出
dabing 2009-11-9 22:27
格式化输出的控制字符非常的丰富,但常用的并不多,一般说来: I 、F、E、A、X 是最常使用的几个格式,最好把它们都记下来。 Iw 以w个字符的宽度来输出整数,至少输出m个数字。 如:write(*,(I5)) 100 输出:_ _100 ; 前面两空格 Fw.d 以w个字符文本框来输出浮点数,小数部分占d个字符宽,输出文本框的设置不中会出现*号。如:write(*,(F9.3)) 123.45 输出:_ _123.450 ; 前面两空格,后补0 Ew.d 用科学计数法,以w个字符宽来输出浮点数,小数部分占d个字符宽,指数部分最少输出e个数字。如:write(*,(E15.7) 123.45 输出:_ _0.1234500E+03 ; 输出不中15个字符的部分补上空白,小数部分不足7位的会补0 Dw.d 使用方法同Ew.d,差别在于输出时用来代表指数的字母由E换成D。 Aw 以w个字符宽来输出字符串。 write(*,(A10)) Hello 固定用是为10我个字符段来输出字符串,不足的前面补空格 nX 输出位置向右移动n位。write(*,(5X,I3)) 100 ; 将先填5个空格,再输出整数。 Lw 以w个字符宽来输出T或F的真假值。write(*,(L4)) .true. ;程序会输出3个空格和一个T / 换行输出。write(*,(I3//3)) 10,10 程序会得出4行,中间两行空格是从除号/得到的。 Tc 把输出的位置移动到本行的第c个字节。 TLn 输出位置向左相对移动n个字节。 TRn 输出位置向左相对移动n个字节。 SP、SS 加了SP后,输出数字时如数值为正则加上+,SS则是用来取消SP的功能。 如 write(*,(SP , I5 , I5 , SS , I5)) 5 , 5 , 5 输出:+5 +55 BN、BZ BN定义在输入时没有数据的字节代表没有东西。BZ定义在没有数据的字节代表0 另有 kP 、Bw 、Ow 、Zw 用的较少,在需要时可以查询。 2009-11-09于承德
个人分类: Fortran文档|39006 次阅读|1 个评论

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-5-23 16:54

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部