Fortran笔记 派生类型-整理版
以下为整理后的笔记,英文原文 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 (节选)
译者总结:
- 为了实现对数据访问权限的控制,可以设置private属性。派生类型设置为private的代价是:不能定义基于该类型的常量。
- 区分类型绑定过程(process)和函数的方法:在定义语句,括号里有this的,是方法(类型绑定过程);没有this的,是函数(如用户构造函数)。
- 在本例中,在命名的接口块中的procedure前面需要加module
Fortran笔记 派生类型-整理版的更多相关文章
- Entity Framework with MySQL 学习笔记一(关系整理版)
1-1 设置 //DataAnnotation 1-1 | 1-0 table //SQLtable : member , columns : memberId, name //SQL basic l ...
- iOS:以前笔记,未整理版。太多了,先放着吧。。。。。。。
1. -(void)timetick { _d = 0; NSTimer *newtime =[NSTimer scheduledTimerWithTimeInterval:1 target:self ...
- 一个项目涉及到的50个Sql语句(整理版)
/* 标题:一个项目涉及到的50个Sql语句(整理版) 说明:以下五十个语句都按照测试数据进行过测试,最好每次只单独运行一个语句. */ --1.学生表Student(S,Sname,Sage,Sse ...
- django models 类型整理 version:1.8.3
django models 类型整理 version:1.8.3 网上百度到的最上面的一篇已经是11年的了,django变化很大,现在把1.8.3版的models类型大致整理了下贴出来 普通键部分 F ...
- 【转帖】Flink 核心技术浅析(整理版)
Flink 核心技术浅析(整理版) https://www.cnblogs.com/swordfall/p/10612404.html 分类: Flink undefined 1. Flink简介 A ...
- 《Entity Framework 6 Recipes》中文翻译系列 (25) ------ 第五章 加载实体和导航属性之加载完整的对象图和派生类型上的导航属性
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-5 加载完整的对象图 问题 你有一个包含许多关联实体的模型,你想在一次查询中, ...
- CLR via C#深解笔记二 - 类型设计
类型基础 所有类型都从System.Object派生 CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Para ...
- 【转】Java基础笔记 – 枚举类型的使用介绍和静态导入--不错
原文网址:http://www.itzhai.com/java-based-notes-introduction-and-use-of-an-enumeration-type-static-impor ...
- Oracle与mysql的字段类型整理
Oralce的字段类型整理如下: Mysql的字段类型整理如下: 最后面一栏是对应JAVA的基本类型.希望对初学者有用,初学者在学习JAVA的时候,不知道怎么把JAVA的对象指向到ORALCE或者MY ...
- "MySql.Data.MySqIClient.MySqlProviderSevices”违反了继承安全 性规则。派生类型必须与基类型的安全可访问性匹配或者比基类型的安 全可访问性低。 "解决方法
写Code First 时(使用的是MySql数据库),添加好EntityFrame.MySql.Data .MySql.Data.Entity后 ,写好TestDbContext类. 运行时报出一个 ...
随机推荐
- Canonical为所有支持的Ubuntu LTS系统发布了新的Linux内核更新
Canonical近日为所有支持的Ubuntu LTS系统发布了新的Linux内核更新,以解决总共19个安全漏洞. 新的Ubuntu内核更新仅适用于长期支持的Ubuntu系统,包括Ubuntu 22. ...
- kali日常快捷命令(用到就更新)
什么BASH命令? 在Linux上采用bash作为标准,基本上它描述了对带有".sh"扩展名的vi编辑器等文本的处理并执行. 与编程一样,它有许多函数,如变量,函数和算术处理,所以 ...
- spring 特性
1.Aware系列接口 spring 6.0提供了一系列的Aware接口,方便我们在Bean加载时获取信息 如 @Service public class study implements BeanN ...
- 修复gitlab权限(docker方式搭建)
docker exec -it gitlab update-permissions docker restart gitlab
- MATLAB批量处理表格类型文件
MATLAB版本2020b 问题背景:预处理数据,有若干变压器温度表格,要将变压器运行温度数据提取出来 通过LSTM模型进行温度预测
- python使用win32gui操作窗口
激活指定窗口 import win32gui import win32con def match_windows(win_title): """ 查找指定窗口 :para ...
- phpstudy 配置域名解决index.php 过多重定向
server { listen 80; server_name www.cqtldd.com cqtldd.com; root "D:/phpstudy_pro/WWW/tongliangd ...
- Vuex扫描自定义文件夹下的所有文件
解决问题:当我们规范 model 统一放在某个文件夹下,且可以通过子文件夹分类.当新加model时又不想去修改别的地方. 代码 // /src/sotre/index.js 1 import { cr ...
- Linux系统Shell脚本第六章:文件三剑客之sed
Shell编程三剑客之sed 前言 一.Sed编辑器 (1)Sed概述 (2)Sed工作流程 (3)Sed基本语法 二.Sed用法示例 (1)Sed输出指定行 (2)插入符合条件的行 (3)删除符合条 ...
- 在Excel中创建随机数据集
1.随机小数0-1之间 =RAND() 2.随机整数1-100之间 =RANDBETWEEN(1,100) 3.生成一定比例的随机数0或1 =IF(RAND()>=0.8,1,0) 4.生成一定 ...