《FORTRAN语法:章节篇》第1章 数据类型

Fortran默认将以字母i、j、k、l、m、n开头的变量认为是INTEGER类型,其他字母开头的变量认为是REAL型。

IMPLICIT INTEGER(A,B,C) !以A、B、C开头的变量都视为整型
IMPLICIT INTEGER(A-F,I,K) !以A到F、I、K开头的变量都视为整型
IMPLICIT NONE !关闭默认类型功能,所有变量都要事先声明

(1)如果两个整数的除法不是整数,计算机会自动截去答案的小数部分。
(2)在判断两个实型数据是否相等时,由于实数不能被精确表示,因此要特别小心。

REAL(KIND = 4),PARAMETER :: PI = 3.14159 !声明实型常量PI = 3.14159
CHARACTER,PARAMETER :: NAME = "Aiden Lee" !字符串常量不用声明其长度

1.1 实型

REAL(KIND = kind_number) :: real_var
计算机/编译器32字节实数对应的kind_number64字节实数对应的kind_number128字节实数对应的kind_number
PC/GNU Fortran4(默认)816
PC/Intel Visual Fortran4(默认)816
PC/NAGWare Fortran1(默认)2不支持

根据传统惯例,在任意指定计算机上的较短版本的REAL数据类型被看作是单精度的,较长版本的REAL数据类型被看作是双精度的。在大部分计算机上,单精度实数用4字节(32位)来存储,双精度实数用8字节(64位)来存储。而在一些64位处理器上,用8字节(64位)来存储单精度数,用16字节(128位)来存储双精度数。因此不能保证在不同处理器中单/双精度数具有同样的长度,这种可变性使得术语“单精度”和“双精度”很难用于描述一个实数的真正精度。大多数Fortran编译器也支持16字节(128位)实数类型,称为四倍精度

为了使程序在不同处理器的计算机之间可移植,应该始终为类别号(即kind_number)指定一个有名常量,并在所有的类型定义语句中使用这个有名常量,在不同的处理器中运行该程序的时候只需要修改有名常量对应的值即可:

INTEGER,PARAMETER :: SGL = 4 !对应单精度kind_number的取值
INTEGER,PARAMETER :: DBL = 8 !对应双精度kind_number的取值
REAL(KIND = SGL) :: single_real_var
REAL(KIND = DBL) :: double_real_var
!实型常量
3.14159 !默认实数类别
3.14159_4 !当4为合法的实数类别时才有效
3.14159_DBL !当DBL是一个整型常量时有效
3.14159E0 !单精度指数
3.14159D0 !双精度指数

此外,Fortran提供了内置函数SELECTED_REAL_KIND来自动选择合适的实型数据的类别号,这个函数返回适合或者超过指定取值范围和精度的实型数据的最小类别的类别号:

kind_number = SELECTED_REAL_KIND(P = precision,R = range)
!precision:所需精度(即有效数字位数)
!range:所需的指数范围(即10^range)

下面这个程序示例了如何使用这个函数来选择基于某个处理器的实型变量的类别,并用KIND函数查询了类别号,用PRECISION函数查询了可以表示的有效数字位数,用RANGE函数查询了可以表示的指数范围:

PROGRAM main
    IMPLICIT NONE
    INTEGER,PARAMETER :: SGL = SELECTED_REAL_KIND(P = 6,R = 37)
    INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P = 13,R = 200)
    REAL(KIND = SGL) :: single_real_var = 0.0
    REAL(KIND = DBL) :: double_real_var = 0.0_DBL
    WRITE(*,*) KIND(single_real_var),PRECISION(single_real_var),RANGE(single_real_var) !4 6 37
    WRITE(*,*) KIND(double_real_var),PRECISION(double_real_var),RANGE(double_real_var) !8 15 307
END PROGRAM main

Fortran还提供了一个称为iso_Fortran_env的内置模块,它包含相关给定处理器上可用数据类型的类别的信息,以及描述不同类型数据的常量的标准名称:

PROGRAM main
    USE iso_Fortran_env
    IMPLICIT NONE
    INTEGER(KIND = INT8) :: int8_var !请求当前处理器上的8位整型变量
    INTEGER(KIND = INT16) :: int16_var !请求当前处理器上的16位整型变量
    INTEGER(KIND = INT32) :: int32_var !请求当前处理器上的32位整型变量
    INTEGER(KIND = INT64) :: int64_var !请求当前处理器上的64位整型变量
    REAL(KIND = REAL32) :: real32_var !请求当前处理器上的32位实型变量
    REAL(KIND = REAL64) :: real64_var !请求当前处理器上的64位实型变量
    REAL(KIND = REAL128) :: real128_var !请求当前处理器上的128位实型变量
    WRITE(*,"('All category values supported by INTEGER:',*(I3))") INTEGER_KINDS !□□1□□2□□4□□8
    WRITE(*,"('All category values supported by REAL:',*(I3))") REAL_KINDS !□□4□□8□16
    WRITE(*,"('All category values supported by CHARACTER:',*(I3))") CHARACTER_KINDS !□□1
    WRITE(*,"('All category values supported by LOGICAL:',*(I3))") LOGICAL_KINDS !□□1□□2□□4□□8
END PROGRAM

如果需要一个双精度的运算,那么应该很小心地确保参与运算的每个中间值都是双精度的,所有中间结果都应该存在双精度变量中。如果用于初始化变量的常量是以单精度格式写的,那么变量将会被初始化成单精度的,而不管常量中所写的有效数字的个数:

PROGRAM main
    IMPLICIT NONE
    INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P = 13)
    REAL(KIND = DBL) :: a1 = 6.666000666000666 !a1 = 6.66000
    REAL(KIND = DBL) :: a2 = 6.666000666000666_DBL
    WRITE(*,*) a1 !6.66600084304810
    WRITE(*,*) a2 !6.66600066600067
END PROGRAM

所有支持单精度实数的通用函数也支持双精度实数,如果输入值是单精度的,那么函数将会计算出单精度结果,如果输入值是双精度的,那么函数将会计算出双精度结果。DBLE函数可以将任意数值转化为双精度数。

通常有以下三种情况需要使用双精度数:
(1)当计算所需数据的绝对值的动态范围小于 1 0 − 39 10^{-39} 1039或者大于 1 0 39 10^{39} 1039的时候。
(2)当需要对大小非常不同的数据进行相加或者相减的时候。
(3)当需要对两个大小非常接近的数进行相减的时候。

1.2 整型

INTEGER(KIND = kind_number) :: int_var
计算机/编译器8字节整数对应的kind_number16字节整数对应的kind_number32字节整数对应的kind_number64字节整数对应的kind_number
PC/GNU Fortran124(默认)8
PC/Intel Visual Fortran124(默认)8
PC/NAGWare Fortran123(默认)4

Fortran提供了内置函数SELECTED_INT_KIND来自动选择合适的整型数据的类别号,这个函数返回适合于当前计算机中所指定范围的整型数值的最小类别的类别号:

kind_number = SELECTED_INT_KIND(R = range)
!range:所需的指数范围(即10^range)
INTEGER,PARAMETER :: SHORT = SELECTED_INT_KIND(3)
INTEGER,PARAMETER :: LONG = SELECTED_INT_KIND(9)
INTEGER(KIND = SHORT) :: short_int_var
INTEGER(KIND = LONG) :: long_int_var
!整型常量
10 !默认的整型类别
10_4 !当4为合法的整型类别时有效
10_LONG !当LONG是一个整型常量时有效

此外,也可以用内置模块iso_Fortran_env,直接指定整型变量的字节数,前已述及,这里不再赘述。

取整函数含义
int_var = INT(real_var)截尾取整
int_var = NINT(real_var)四舍五入取整
int_var = CEILING(real_var)向上取整
int_var = FLOOR(real_var)向下取整

1.3 字符型

ASCII(America Standard Code for Information Interchange)和Unicode(ISO 10646)是两种基本的字符集。ASCII字符集是一个系统,该系统中的每个字符按一个字节来存储,这种字符集可以容纳256个字符,标准ASCII定义了其中的前128个可取值,剩余的128个字符在不同的国家有不同的定义,这些定义取决于特定国家使用的编码页。Unicode字符集用两个字节来表示每个字符,最多允许1112064个可能的字符,它几乎涵盖了地球上所有语言用到的字符。在没有特别说明的情况下,今后默认用ASCII字符集。

CHARACTER(KIND = kind_number,LEN = len_number) :: char_var
!kind_number:所需的字符集的类别号

Fortran2003提供了一个叫做SELECTED_CHAR_KIND的新函数用于返回指定字符集的类别号:

kind_number = SELECTED_CHAR_KIND(name)
!name:取值为"DEFAULT"、"ASCII"、"ISO_10646"(Unicode)

Fortran标准不需要编译器能够支持Unicode字符集,但是它为使用Unicode字符集的需提供了支持函数。GNU Fortran支持ASCII和Unicode两个字符集,Intel Fortran仅支持ASCII字符集。

(1)str_var(2:2)表示str的第二个字符,不能写成str_var(2)。
(2)一个英文字母占一个字符长度,一个汉字占两个字符长度。
(3)Fortran中“字符型”和“字符串型”是一个意思,它们都由单/双引号括起来,今后在没有特别声明的情况下统称为“字符型”。
(4)如果字符串中包含单/双引号,那么必须用双/单引号来括住它,否则必须用两个连续的单/双引号来表示字符串中的单/双引号。

PROGRAM main
    IMPLICIT NONE
    CHARACTER(LEN = 5) :: str1,str2,str3
    str1 = "abc" !str1 = "abc□□"
    str2 = "ABCDEFG" !str2 = "ABCDE"
    str1(4:5) = "de" !str1 = "abcde"
    str3 = str1(1:2) // str2(3:5) !str3 = "abCDE"
END PROGRAM main
内置字符函数含义
char_var = CHAR(int_var)返回处理器所用的排序序列中对应于int_var的字符
char_var = ACHAR(int_var)返回ASCII排序序列中对应于值int_var的字符
int_var = ICHAR(char_var)返回处理器所用排序序列中对应于char_var的整数值
int_var = IACHAR(char_var)返回ASCII排序序列中对应于char_var的整数值
int_var = LEN(str_var)求str_var的声明长度
int_var = LEN_TRIM(str_var)求str_var去掉尾部空格的长度
str_var2 = TRIM(str_var1)将str_var1去掉尾部空格后赋值给str_var2
int_var = INDEX(str_var1,str_var2[,log_var])求str_var2在str_var1中第一次出现的位置,log_var可以改变查找方式,如果取值为.TRUE.表示从后往前查找,反之从前往后查找
log_var = LLT(str_var1,str_var2)根据ASCII排序序列,如果str_var1<str_var2,则返回.TRUE.
log_var = LLE(str_var1,str_var2)根据ASCII排序序列,如果str_var1<=str_var2,则返回.TRUE.
log_var = LGT(str_var1,str_var2)根据ASCII排序序列,如果str_var1>str_var2,则返回.TRUE.
log_var = LGE(str_var1,str_var2)根据ASCII排序序列,如果str_var1>=str_var2,则返回.TRUE.

ACHAR函数和IACHAR函数与CHAR函数和ICHAR函数的功能相同,只是前者不考虑特定处理器采用的字符集,而是基于ASCII排序序列进行,其运行结果不论在什么计算机上都是相同的,因此应该用它们来替代后者,以提高程序的可移植性。

字符型数据可以和字符型数据进行比较运算,但是字符型数据不能和数值型数据进行比较运算。字符型数据在比较时,从每个字符串的第一个字符开始,如果它们是相同的,那么再比较第二个字符,直到发现两个字符串之间存在的第一个差别为止;如果两个字符串在比较到其中一个结束时始终没有差别,那么就认为另一个字符串为大。如果程序有可能在具有不同字符集的计算机上运行,在比较两个字符串的时候,应当用逻辑函数LLT(字符串小于)、LLE(字符串小于等于)、LGT(字符串大于)、LGE(字符串大于等于)代替普通的逻辑运算符。

1.4 复数型

COMPLEX(KIND = kind_number) :: complx_var

复数型常量的表示格式是:(r,i),r表示实部,i表示虚部。因此每个复数需要两个实数空间。在任意给定的处理器中,默认复数类别总是和默认实数类别相同,因此内置函数SELECTED_REAL_KIND也可以用于指定处理器无关情况下复数的大小。

在格式化输入、输出复数的时候,第一个格式描述符用于描述复数的实部,第二个格式描述符用于描述复数的虚部。从键盘格式化读取复数的时候输入行不包含括号,从键盘表控读取复数的时候,输入行复数必须包含括号和逗号。格式化输出复试的时候只有实部和虚部的数值被输出,表控输出复数的时候复数的括号和逗号也一并被输出:

PROGRAM main
    IMPLICIT NONE
    COMPLEX(KIND = 4)  :: c
    READ(*,*) c !表控输入:(1.0,2.0)
    WRITE(*,*) c !表控输出:□(1.000000,2.000000)
    READ(*,"(2F5.2)") !格式化输入:1.0□2.0
    WRITE(*,"(2F5.2)") c !格式化输出:□1.00□2.00
END PROGRAM

如果一个实数表达式被赋给一个复数变量,那么表达式的值将被放在复数变量的实部,复数变量的虚部被设定为0;当一个复数值要赋给一个实型或整型变量时,复数的实部赋给变量,虚部被丢弃。复数之间只能比较是否相等,不能比较大小。

复数内置函数含义
complx_var = CMPLX(r,i,KIND = kind_number)把实数或整数r、i转换为实部为r虚部为i的复数,kind_number用于指定复数类别号
int_var = INT(complx_var,KIND = kind_number)将复数的实部转化为整数,kind_number用于指定整数类别号
real_var = REAL(complx_var,KIND = kind_number)将复数的实部转化为实数,kind_number用于指定实数类别号
real_var = DBLE(complx_var)将复数的实部转化为双精度实数
real_var = AIMAG(complx_var)将复数的虚部转化为实数
real_var = CABS(complx_var)计算复数的模
complx_var2 = CONJG(complx_var1)计算复数的共轭复数

Fortran标准规定如果没有在输入参数中显式的指明类别号,那么函数CMPLX返回默认的复数类别,这样就可能会在不知情的情况下意外地损失精度:

PROGRAM main
    IMPLICIT NONE
    INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P = 13)
    COMPLEX(KIND = DBL) :: c1,c2
    REAL(KIND = DBL) :: r = 3.333333333333333_DBL
    REAL(KIND = DBL) :: i = 6.666666666666666_DBL
    c1 = CMPLX(r,i)
    c2 = CMPLX(r,i,KIND = DBL)
    WRITE(*,*) c1 !□(3.33333325386047,6.66666650772095)
    WRITE(*,*) c2 !□(3.33333333333333,6.66666666666667)
END PROGRAM

1.5 逻辑型

LOGICAL(KIND = kind_number) :: log_var

(1)kind_number可以取1、2、4、8,缺省值为4。
(2)逻辑常量只能是.TRUE..FALSE.
(3)在输出逻辑变量时,输出值只能是T和F,分别代表逻辑真和逻辑假。
(4)在输入逻辑变量时必须是.TRUE./.FALSE.或以T/F开头的字符或字符串。

1.6 派生数据类型

派生数据类型(Derived Data Type)是用户利用Fortran内置数据类型或者另外一个派生数据类型的组合自行创建出的一个新的数据类型:

!创建派生数据类型
TYPE :: person
    CHARACTER(LEN = 10) :: name
    INTEGER(KIND = 4) :: age
END TYPE person
!第一种成员初始化方法
TYPE(person) :: Aiden_Lee
Aiden_Lee%name = "Aiden Lee" !派生数据类型的成员用%或.访问,处于本人习惯,今后统一用%
Aiden_Lee%age = 23
!第二种成员初始化方法
TYPE(person) :: Aiden_Lee = person("Aiden Lee", 23)
!第三种成员初始化方法
TYPE(person) :: Aiden_Lee
DATA Aiden_Lee /person("Aiden Lee"23)/
!第四种成员初始化方法
TYPE(person) :: Aiden_Lee
DATA Aiden_Lee%name,Aiden_Lee%age /"Aiden Lee",23/

对于使用派生数据类型的大型程序而言,应当将所有派生数据数据类型的创建统一写入一个模块当中,然后在每个需要访问该派生数据类型的过程中使用该模块。

当Fortran编译器为派生数据类型的变量分配内存空间时,编译器并不需要为该类型变量的每个元素分配连续的空间。事实上,它们在内存中的位置是随机的,只要能够保证I/O操作时元素之间保持原有的顺序即可。然而,有时如果想要将派生数据类型的变量传给由其他语言编写的过程,就必须严格限制该变量各元素的内存顺序,这时可以在类型定义中使用SEQUENCE语句,使得派生数据类型的元素被放在连续的内存空间中:

TYPE :: vector
    SEQUENCE
    INTEGER(KIND = 4) :: x
    INTEGER(KIND = 4) :: y
    INTEGER(KIND = 4) :: z
END TYPE vector

当需要创建有派生数据类型的函数的时候,需要给这个函数提供显式接口,最简单的方法就是将函数放在模块中:

MODULE module_for_vecter
    IMPLICIT NONE
    TYPE :: vecter
        SEQUENCE
        REAL(KIND = 4) :: x
        REAL(KIND = 4) :: y
    END TYPE vecter
    CONTAINS
    TYPE(vecter) FUNCTION vecter_add(v1,v2)
        IMPLICIT NONE
        TYPE(vecter),INTENT(IN) :: v1
        TYPE(vecter),INTENT(IN) :: v2
        vecter_add%x = v1%x + v2%x
        vecter_add%y = v1%y + v2%y
    END FUNCTION vecter_add
END MODULE module_for_vecter

PROGRAM main
    USE module_for_vecter
    IMPLICIT NONE
    TYPE(vecter) :: v1 = vecter(1.0,2.0)
    TYPE(vecter) :: v2 = vecter(3.0,4.0)
    WRITE(*,*) vecter_add(v1,v2)
END PROGRAM main

正如Fortran允许多种整数或实数类别,用户也可以使用参数定义派生数据类型,这种方式叫做参数化派生数据类型。有两种参数可以用来定义派生数据类型,第一种在编译时已知(称为类别类型参数),另一种在运行时获取(称为长度类型参数),它们对应的形式参数值被称为哑元值

PROGRAM main
    IMPLICIT NONE
    INTEGER,PARAMETER :: SGL = SELECTED_REAL_KIND(P = 6)
    INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P = 13)
    TYPE :: vector(kind_number,len_number)
        INTEGER,KIND :: kind_number = SGL !默认为单精度
        INTEGER,LEN :: len_number = 10 !默认为10个元素
        REAL(KIND = kind_number),DIMENSION(len_number) :: v
    END TYPE vector
    TYPE(vector) v1 !单精度10元素
    TYPE(vector(kind_number = DBL,len_number = 5)) :: v2 !指双精度5元素
    TYPE(vector(kind_number = DBL,len_number = 5)),DIMENSION(10) :: v3 !双精度5元素的数组
END PROGRAM main

没有SEQUENCEBIND(C)属性的自定义数据类型是可以扩展的,这就有点类似于C++的继承关系:

PROGRAM main
    IMPLICIT NONE
    TYPE :: point_2D
        REAL(KIND = 4) :: x
        REAL(KIND = 4) :: y
    END TYPE point_2D
    TYPE,EXTENDS(point_2D) :: point_3D
        REAL(KIND = 4) :: z
    END TYPE point_3D
    TYPE(point_3D) :: p
    p%x = 1.0 !或者p%point_2D%x
    p%y = 2.0 !或者p%point_2D%y
    p%z = 3.0
    WRITE(*,*) p
END PROGRAM main

Fortran还允许将过程与派生数据类型绑定,称为类型绑定,类似于C++的类的方法的功能,实现面向对象程序设计:

MODULE module_for_point
    IMPLICIT NONE
    TYPE :: point
        REAL(KIND = 4) :: x
        REAL(KIND = 4) :: y
        CONTAINS
        PROCEDURE,PASS :: add !优先使用
        PROCEDURE,NOPASS :: minus
        !PASS属性指明调用此过程的point类型变量会被当作第一调用参数自动传递到这一过程
    END TYPE
    CONTAINS
    !过程的实现必须写在和派生数据类型定义相同的模块中并且派生数据类型必须使用CLASS关键字声明
    TYPE(point) FUNCTION add(this_point,another_point)
        IMPLICIT NONE
        CLASS(point),INTENT(IN) :: this_point
        CLASS(point),INTENT(IN) :: another_point
        add%x = this_point%x + another_point%x
        add%y = this_point%y + another_point%y
    END FUNCTION add
    TYPE(point) FUNCTION minus(this_point,another_point)
        IMPLICIT NONE
        CLASS(point),INTENT(IN) :: this_point
        CLASS(point),INTENT(IN) :: another_point
        minus%x = this_point%x - another_point%x
        minus%y = this_point%y - another_point%y
    END FUNCTION minus
END MODULE module_for_point

PROGRAM main
    USE module_for_point
    IMPLICIT NONE
    TYPE(point) :: p1,p2,p3,p4
    p1 = point(1.0,2.0)
    p2 = point(3.0,4.0)
    p3 = p1%add(p2)
    p4 = p1%minus(p1,p2)
    WRITE(*,*) p3
    WRITE(*,*) p4
END PROGRAM main

Fortran有一个叫做ASSOCIATE的结构,可以在一个代码段的执行过程中,临时将变量或表达式和某个名字关联,简化拥有长名字或长表达式的代码段。举个例子,假设雷达在跟踪一系列目标,每个目标的坐标都存于trak_file的数据结构中,雷达本身的坐标存于rader_loc的数据结构中,现在要计算跟踪到的某个目标的距离和方位:

MODULE type_def
    IMPLICIT NONE
    TYPE :: track_file
        REAL(KIND = 8) :: x !目标的横坐标(m)
        REAL(KIND = 8) :: y !目标的纵坐标(m)
        REAL(KIND = 8) :: dist !离目标的距离(m)
        REAL(KIND = 8) :: bearing !目标的方位(rad)
    END TYPE track_file
    TYPE :: radar_loc
        REAL(KIND = 8) :: x !雷达的横坐标(m)
        REAL(KIND = 8) :: y !雷达的纵坐标(m)
    END TYPE radar_loc
END MODULE type_def
    
PROGRAM main
    USE type_def
    IMPLICIT NONE
    TYPE(track_file) :: track = track_file(1.0,2.0,0.0,0.0) !距离和方位暂时记为0.0
    TYPE(radar_loc) :: radar = radar_loc(3.0,4.0)
    !track%dist = DSQRT((radar%x - track%x)**2 + (radar%y - track%y)**2)
    !track%bearing = DATAN((radar%y - track%y) / (radar%x - track%x))
    ASSOCIATE(dist => track%dist,bearing => track%bearing,&
              &x1 => radar%x,y1 => radar%y,&
              &x2 => track%x,y2 => track%y)
        dist = DSQRT((x1 - x2)**2 + (y1 - y2)**2)
        bearing = DATAN((y1 - y2) / (x1 - x2))
    END ASSOCIATE
END PROGRAM main

《 F O R T R A N 语 法 》 系 列 博 客 创 作 参 考 资 料 来 源 《FORTRAN语法》系列博客创作参考资料来源 FORTRAN

  1. 《Fortran95程序设计》.彭国伦.中国电力出版社.
  2. 《工程分析程序设计》.陈斌、周屈兰.西安交通大学出版社.
  3. 《Fortran程序设计(第四版)》.Stephen J.Chapman.中国电力出版社.

博 客 创 作 : A i d e n   L e e 博客创作:Aiden\ Lee Aiden Lee
特别声明:文章仅供学习参考,转载请注明出处,严禁盗用!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值