以下为整理后的笔记,英文原文 Introduction to Modern Fortran for the Earth System Sciences

英文翻译 https://www.cnblogs.com/jiangleads/articles/16089427.html

派生数据类型

在数值模式中,会经常要用到一些实体(entities),它们比单个变量或由同类型变量组成的数组更加复杂。为了能搞满足这种需求,Fortran支持了用户定义类型(user-defined types)(也叫派生数据类型(Derived Data Types (DTS)或absctact data types (ADTS))。这提供了将不同类型的实体打包成一个逻辑单元的方法,这类似于传统的OOP类,提供了基本的用于封装(encapsulation)的工具(vehicle)。

定义派生类型

派生类型的定义的方式为 type DtName - end type DtName.

 5 ! 派生数据类型通常被包含在modules中
6 module Vec2D_class
7 implicit none
8
9 type Vec2D ! 以下: 对数据成员的声明
10 real :: mU = 0., mV = 0.
11 contains ! 以下: 对类绑定过程的声明
12 procedure :: getMagnitude => getMagnitudeVec2D
13 end type Vec2D
14
15 contains
16 real function getMagnitudeVec2D( this )
17 class(Vec2D), intent(in) :: this
18 getMagnitudeVec2D = sqrt( this%mU**2 + this%mV**2 )
19 end function getMagnitudeVec2D
20 end module Vec2D_class
21
22 program test_driver_a
23 use Vec2D_class
24 implicit none
25
26 type(Vec2D) :: A ! Implicit initialization
27 type(Vec2D) :: B = Vec2D(mU=1.1, mV=9.4) ! can use mU&mV as keywords
28 type(Vec2D), parameter :: C = Vec2D(1.0, 3.2)
29
30 ! Accessing components of a data-type.
31 write(*, '(3(a,1x,f0.3))') &
32 "A%U =", A%mU, ", A%V =", A%mV, ", A%magnitude =", A%getMagnitude(), &
33 "B%U =", B%mU, ", B%V =", B%mV, ", B%magnitude =", B%getMagnitude(), &
34 "C%U =", C%mU, ", C%V =", C%mV, ", C%magnitude =", C%getMagnitude()
35 end program test_driver_a

Listing 3.27 src/Chapter3/dt_basic_demo.f90

在很多方面,派生类型很像module:

  • 首先,它由一个声明部分(specification part)(上面的第10行),指定了数据的部分。

在本例中,每个Vec2D实体有两个real-类型变量。假设myVec是一种Vec2D类型的变量,我们可以通过myVec%mU和myVec2D%mV来访问这两个分量。

  • 其次,有一个contains声明,将过程部分(procedures part)(上面的第12行)分隔开来。

这并不完全和module一样,因为在这里仅有procedure部分出现——procedure的具体实现在出现在代码的其他位置。Fortran2003标准支持上述的这种过程(也叫类型绑定过程(type-bound procedures)或方法(methods))。上述方法的接口需要是显式的(所以它既可以是模块过程(module procdures),如本例;也可以是具有接口块(interface-block)的外部过程(external procedures))。

在我们定义的最初版本的Vec2D中,只有一个方法(method)——函数getMagnitude,它是函数getMagnitudeVec2D(第16-19行)的别名。函数getMagnitudeVec2D的定义看起来和正常函数一样,除了第17行,虚参(this)的声明语句为class(Vec2D)之外。这个参数,又叫做传递对象虚参(passed-object dummy argument)。当关联到特定的派生类型时,它将对应于调用这个方法的对象

在本例中,虚参的绑定是在第12行发生。这类绑定的基本语法是:

procedure [( interfaceName )] [ListOfBindAttrs ::] bindName [=> procedureName ]

 其中:

  • interfaceName ,接口名,(可选参数),实现类似抽象基类(abstract base classes)的功能。相关讨论超出本文范畴,详见 Clerman and Spector [Clerman, N.S., Spector, W.: Modern Fortran: Style and Usage. Cambridge University Press, Cambridge (2011) ]
  • ListOfBindAttrs,绑定属性,(可选参数),一个由逗号分隔开的一系列属性。这些属性可以是public或private(和信息隐藏有关),pass或nopass(和传递对象的虚参有关),以及non_overridable(和继承(inheritance)有关)。
  • bindName,绑定名,唯一必需的参数,代表使用这个派生类型的程序的名称。如果没有指定procedureName,那么bindName需要对应程序的实际名称。
  • procddureName,当指定时,表示实际程序的名称(于它相对应,此时bindName将会是一个别名)

关于this,需要注意的是,默认情况下,它不会出现在调用行,因为它会被编译器默认加入。通常情况下,它对应于实际程序定义中的第一个参数。然而,对于ListOfBindAttrs的设置,需要好好地调试:

  • 当使用nopass绑定参数时,对象将不再传递到程序。这并不十分常见,但是作为优化时将会很有用,比如出现以下这种情况时:当方法并不实际需要访问到对象的数据(或者没有实际的数据)时
  • 通过使用pass(dummyArgName)绑定属性,可以选取其他的参数而不是第一个参数,用来指向作为传递对象的虚参的程序。很显然,dummyArgName需要用程序中虚参的实际名称代替。这种技巧对于运算符重载(operator overloading)的情形下很有用(参见3.3.4节)。

最后,请注意,传递对象虚参的名字可以随便起。通常,为了和其他面向对象语言保持一致,约定俗成的名称是this

使用派生类型

定义常量和变量

包含了派生类型的模块可被程序使用,用来定义变量和常量,如下:

22 program test_driver_a
23 use Vec2D_class
24 implicit none
25
26 type(Vec2D) :: A ! 隐式初始化(Implicit initialization)
27 type(Vec2D) :: B = Vec2D(mU=1.1, mV=9.4) ! 声明变量时候就进行初始化。使用隐式构造函数(implicit constructors)
28 type(Vec2D), parameter :: C = Vec2D(1.0, 3.2)
29
30 ! Accessing components of a data-type.
31 write(*, '(3(a,1x,f0.3))') &
32 "A%U =", A%mU, ", A%V =", A%mV, ", A%magnitude =", A%getMagnitude(), &
33 "B%U =", B%mU, ", B%V =", B%mV, ", B%magnitude =", B%getMagnitude(), &
34 "C%U =", C%mU, ", C%V =", C%mV, ", C%magnitude =", C%getMagnitude()
35 end program test_driver_a

Listing 3.28 src/Chapter3/dt_basic_demo.f90 (节选)

变量的初始化

有两种方式

1. 可以直接在声明变量时候进行初始化(第27行)。PS:对于常量,这是必须的(第28行)

2. 在声明变量时候也可以不赋初值(隐式初始化,第26行)。对于派生型的变量,可以在定义派生类型时候就指定初值(mU=mV=0——参见Listing 3.27中的第10行)。

Fortran提供了隐式构造函数(implicit constructors)。这些看起来像函数调用,其中派生类型的数据成员的名称可以用作关键字,以提高可读性(上面第27行)。如果默认构造函数不够,可以编写自定义构造函数(但有一些重要的观察结果,将在下面讨论)。

类似地,其他语言中类似的析构函数也是终止(final)-的过程。当使用指针时,或者当派生类型不存在时需要特殊操作时,析构函数应该被写入。析构器(finalizer)也在派生类型定义中的contains-statement之后指定(尽管严格来说,它们不是类型绑定过程)。它们的语法是:

final :: ListOfProcedures

派生类型的方法调用

调用方式:对象名%方法名(参数)。

例如,在Listing 3.28的第32-34行中,我们称之为Vec2D的getMagnitude方法。虽然在本例中似乎没有为方法指定任何参数(在getMagnitude()的括号里面没有值),但我们从方法的定义中知道,应该有一个参数——this由编译器悄悄的添加了(接收A、B和C作为实际参数,分别参见第32、33和34行)。

变量访问控制和信息隐藏

信息隐藏。使用private和public关键词实现信息的隐藏/共享。

 1 ! 注意: 这个派生类型的访问是有限制的(参见下面的讨论)
2 type , public : : Vec2D ! DT explicitly declared"public"
3 private ! Make internal data "private" by default.
4 real :: mU = 0., mV = 0.
5 contains
6 private ! Make methods "private" by default.
7 ! (good practice for the case when we have
8 ! implementation -specific methods , that the user
9 ! does not need to know about ).
10 procedure , public : : getMagnitude
11 end type Vec2D

Listing 3.29 使用限制性访问的方法,创建Vec2D

请注意,我们在派生类型定义的两个部分中都添加了private语句(数据和方法具有独立的访问控制),改变了默认策略。然而,访问限制确实带来了一小部分成本:现在我们有责任设计与派生类型交互的适当机制。

注意,当我们设置了数据成员为private之后,编译器将不能提供隐式构造函数。此时,默认的初始值已经给指定,不能再进行修改。

解决以上问题,有两种方式:1 定义一个自定义构造函数,2 声明一个普通的类型绑定过程

1. 定义一个自定义构造函数

Fortran中实现这一点的机制与其他语言相比有所不同,构造函数与类型的绑定是通过一个命名的接口块(在Fortran中也称为“generic interface")实现的。该接口块的名称是我们希望构造的派生类型。

 7 module Vec2D_class
8 implicit none
9 private ! Make module-entities "private" by default.
10
11 type, public :: Vec2D ! DT explicitly declared "public"
12 private ! Make internal data "private" by default.
13 real :: mU = 0., mV = 0.
14 contains
15 private ! Make methods "private" by default.
16 ! . . . more methods (ommitted in this example) . . .
17 end type Vec2D
18
19 ! Generic IFACE, for type-overloading
20 ! (to implement user-defined CTOR)
21 interface Vec2d !通过命名的接口块,实现构造函数和类型的绑定
22 module procedure createVec2d ! 译者总结:interface中的过程需要加module procedure关键字
23 end interface Vec2d
24
25
26 contains
27 type(Vec2d) function createVec2d( u, v ) ! CTOR 构造函数
28 real, intent(in) :: u, v
29 createVec2d%mU = u
30 createVec2d%mV = v
31 end function createVec2d
32 end module Vec2d_class

使用上述module,去声明和初始化Vec2D:

42 program test_driver_b
43 use Vec2D_class
44 implicit none
45
46 type(Vec2D) :: A, D
47 ! 错误:不能在声明时候对私有数据进行初始化
48 !type(Vec2D), parameter :: B = Vec2D(1.0, 3.2)
49 ! 错误:不能在声明时候使用自定义的构造函数!
50 !type(Vec2D) :: C = createVec2D(u=1.1, v=9.4)
51
52 ! Separate call to CTOR.
53 A = Vec2D(u=1.1, v=9.4)
54 end program

与隐式构造器由编译器提供相比,用户定义的构造函数是有限制的它既不能用于定义基于派生类型的常量,也不能在变量的声明行进行初始化。唯一允许的用法是将初始化的语句放在(子)程序的声明部分之外。

应当强调,用户构造函数会导致(取决于编译器)不必要的临时对象被创建,这将会在一些情况下降低性能。当依赖大型对象(例如地球系统模式中封装模型数组的对象)的自定义构造函数时,或者当需要在耗时的循环中重复重新初始化对象时,建议谨慎使用(更好的是基准测试)。

因此,用户构造函数的唯一好处是他们的语法更加方便,并且,作为函数,他们可以被直接在表达式中使用

2. 声明一个普通的类型绑定过程(方法)

(在本例中,名为init)。它通过参数接受初始化数据,并相应地修改对象的状态。与自定义构造函数相比,它的优点是不需要制作对象的临时副本,缺点是调用语法不太方便。

 7 module Vec2D_class
8 implicit none
9 private ! Make module-entities "private" by default.
11 type, public :: Vec2D ! DT explicitly declared "public"
12 private ! Make internal data "private" by default.
13 real :: mU = 0., mV = 0.
14 contains
15 private ! Make methods "private" by default.
16 procedure, public :: init => initVec2D !类型绑定过程(方法)
17 ! . . . more methods (ommitted in this example) . . .
18 end type Vec2D
25
26 contains

33 subroutine initVec2D( this, u, v ) ! init-subroutine
34 class(Vec2D), intent(inout) :: this
35 real, intent(in) :: u, v
36 ! copy-over data inside the object
37 this%mU = u
38 this%mV = v
39 end subroutine initVec2D
40 end module Vec2D_class

使用上述module,去声明和初始化Vec2D:

42 program test_driver_b
43 use Vec2D_class
44 implicit none
45
46 type(Vec2D) :: A, D
47 ! 错误:不能对私有数据进行初始化!
48 !type(Vec2D), parameter :: B = Vec2D(1.0, 3.2)
54
55 ! 将调用初始化函数Separate call to init-subroutine
56 call D%init(u=1.1, v=9.4)
57 end program

Listing 3.31 src/Chapter3/dt_constructor_and_initializer.f90 (节选)

使用类型绑定过程(使用“init”子程序——参见第56行)同样存在以下限制:它们既不能用于定义基于派生类型的常量,也不能在变量的声明行进行初始化。唯一允许的用法是将初始化的语句放在(子)程序的声明部分之外。

访问派生类型变量的分量

可以通过添加两个类型绑定函数(方法),实现访问派生类型变量的分量,如下所示。

这些方法也称为访问器(accessor)方法(或getters,因为它们的名称通常是通过连接“get”和分量的名称来形成的)。此外,我们还重新介绍了getMagnitude函数:

 6 module Vec2d_class
7 implicit none
8 private ! Make module-entities "private" by default.
9
10 type, public :: Vec2d ! DT explicitly declared "public"
11 private ! Make internal data "private" by default.
12 real :: mU = 0., mV = 0.
13 contains
14 private ! Make methods "private" by default.
15 procedure, public :: init => initVec2d
16 procedure, public :: getU => getUVec2d
17 procedure, public :: getV => getVVec2d
18 procedure, public :: getMagnitude => getMagnitudeVec2d
19 end type Vec2d
20 26
27 contains
28 type(Vec2d) function createVec2d( u, v ) ! CTOR
29 real, intent(in) :: u, v
30 createVec2d%mU = u
31 createVec2d%mV = v
32 end function createVec2d
33
34 subroutine initVec2d( this, u, v ) ! init-subroutine
35 class(Vec2d), intent(inout) :: this
36 real, intent(in) :: u, v
37 ! copy-over data inside the object
38 this%mU = u
39 this%mV = v
40 end subroutine initVec2d
41
42 real function getUVec2d( this ) ! accessor-method (GETter)
43 class(Vec2d), intent(in) :: this
44 getUVec2d = this%mU ! direct-access IS allowed here
45 end function getUVec2d
46
47 real function getVVec2d( this ) ! accessor-method (GETter)
48 class(Vec2d), intent(in) :: this
49 getVVec2d = this%mV
50 end function getVVec2d
51
52 real function getMagnitudeVec2d( this ) result(mag)
53 class(Vec2d), intent(in) :: this
54 mag = sqrt( this%mU**2 + this%mV**2 )
55 end function getMagnitudeVec2d
56 end module Vec2d_class

Listing 3.32 src/Chapter3/dt_accessors.f90 (节选)

派生类型可以被用于public-版本中(但需要注意从A%mU变成A%getU(),对于v也是如此):

67   ! Accessing components of DT through methods (type-bound procedures).
68 write(*, '(3(a,1x,f0.3))') "A%U =", A%getU(), &
69 ", A%V =", A%getV(), ", A%magnitude =", A%getMagnitude()

Listing 3.33 src/Chapter3/dt_accessors.f90 (节选)

译者总结:

  1. 为了实现对数据访问权限的控制,可以设置private属性。派生类型设置为private的代价是:不能定义基于该类型的常量。
  2. 区分类型绑定过程(process)和函数的方法:在定义语句,括号里有this的,是方法(类型绑定过程);没有this的,是函数(如用户构造函数)。
  3. 在本例中,在命名的接口块中的procedure前面需要加module

Fortran笔记 派生类型-整理版的更多相关文章

  1. Entity Framework with MySQL 学习笔记一(关系整理版)

    1-1 设置 //DataAnnotation 1-1 | 1-0 table //SQLtable : member , columns : memberId, name //SQL basic l ...

  2. iOS:以前笔记,未整理版。太多了,先放着吧。。。。。。。

    1. -(void)timetick { _d = 0; NSTimer *newtime =[NSTimer scheduledTimerWithTimeInterval:1 target:self ...

  3. 一个项目涉及到的50个Sql语句(整理版)

    /* 标题:一个项目涉及到的50个Sql语句(整理版) 说明:以下五十个语句都按照测试数据进行过测试,最好每次只单独运行一个语句. */ --1.学生表Student(S,Sname,Sage,Sse ...

  4. django models 类型整理 version:1.8.3

    django models 类型整理 version:1.8.3 网上百度到的最上面的一篇已经是11年的了,django变化很大,现在把1.8.3版的models类型大致整理了下贴出来 普通键部分 F ...

  5. 【转帖】Flink 核心技术浅析(整理版)

    Flink 核心技术浅析(整理版) https://www.cnblogs.com/swordfall/p/10612404.html 分类: Flink undefined 1. Flink简介 A ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (25) ------ 第五章 加载实体和导航属性之加载完整的对象图和派生类型上的导航属性

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-5  加载完整的对象图 问题 你有一个包含许多关联实体的模型,你想在一次查询中, ...

  7. CLR via C#深解笔记二 - 类型设计

    类型基础 所有类型都从System.Object派生   CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Para ...

  8. 【转】Java基础笔记 – 枚举类型的使用介绍和静态导入--不错

    原文网址:http://www.itzhai.com/java-based-notes-introduction-and-use-of-an-enumeration-type-static-impor ...

  9. Oracle与mysql的字段类型整理

    Oralce的字段类型整理如下: Mysql的字段类型整理如下: 最后面一栏是对应JAVA的基本类型.希望对初学者有用,初学者在学习JAVA的时候,不知道怎么把JAVA的对象指向到ORALCE或者MY ...

  10. "MySql.Data.MySqIClient.MySqlProviderSevices”违反了继承安全 性规则。派生类型必须与基类型的安全可访问性匹配或者比基类型的安 全可访问性低。 "解决方法

    写Code First 时(使用的是MySql数据库),添加好EntityFrame.MySql.Data .MySql.Data.Entity后 ,写好TestDbContext类. 运行时报出一个 ...

随机推荐

  1. Canonical为所有支持的Ubuntu LTS系统发布了新的Linux内核更新

    Canonical近日为所有支持的Ubuntu LTS系统发布了新的Linux内核更新,以解决总共19个安全漏洞. 新的Ubuntu内核更新仅适用于长期支持的Ubuntu系统,包括Ubuntu 22. ...

  2. kali日常快捷命令(用到就更新)

    什么BASH命令? 在Linux上采用bash作为标准,基本上它描述了对带有".sh"扩展名的vi编辑器等文本的处理并执行. 与编程一样,它有许多函数,如变量,函数和算术处理,所以 ...

  3. spring 特性

    1.Aware系列接口 spring 6.0提供了一系列的Aware接口,方便我们在Bean加载时获取信息 如 @Service public class study implements BeanN ...

  4. 修复gitlab权限(docker方式搭建)

    docker exec -it gitlab update-permissions docker restart gitlab

  5. MATLAB批量处理表格类型文件

    MATLAB版本2020b 问题背景:预处理数据,有若干变压器温度表格,要将变压器运行温度数据提取出来 通过LSTM模型进行温度预测

  6. python使用win32gui操作窗口

    激活指定窗口 import win32gui import win32con def match_windows(win_title): """ 查找指定窗口 :para ...

  7. phpstudy 配置域名解决index.php 过多重定向

    server { listen 80; server_name www.cqtldd.com cqtldd.com; root "D:/phpstudy_pro/WWW/tongliangd ...

  8. Vuex扫描自定义文件夹下的所有文件

    解决问题:当我们规范 model 统一放在某个文件夹下,且可以通过子文件夹分类.当新加model时又不想去修改别的地方. 代码 // /src/sotre/index.js 1 import { cr ...

  9. Linux系统Shell脚本第六章:文件三剑客之sed

    Shell编程三剑客之sed 前言 一.Sed编辑器 (1)Sed概述 (2)Sed工作流程 (3)Sed基本语法 二.Sed用法示例 (1)Sed输出指定行 (2)插入符合条件的行 (3)删除符合条 ...

  10. 在Excel中创建随机数据集

    1.随机小数0-1之间 =RAND() 2.随机整数1-100之间 =RANDBETWEEN(1,100) 3.生成一定比例的随机数0或1 =IF(RAND()>=0.8,1,0) 4.生成一定 ...