2.2 派生类型:把物理对象装进代码


2.2 派生类型:把物理对象装进代码

本节摘要:内置类型是原子,派生类型把原子组装成分子——一个粒子、一个网格单元、一种边界条件,从数据到行为都装进同一个类型。本节讲清 type 定义、嵌套组件、类型绑定过程、private 可见性控制,以及 allocatable 组件与 finalizer 的生命周期管理。这是第 5 章面向对象的直接地基,也是让 Fortran 代码"自己会说话"的关键一步。

从一个粒子说起

写分子动力学模拟,最朴素的做法是三个数组:px(n), py(n), pz(n) 存坐标,vx(n), vy(n), vz(n) 存速度。数组一多,传参列表越来越长,调用顺序错一个就是灾难——update(vx, py, ...) 这种事故迟早发生。派生类型解决的就是这个:把"一个粒子的所有属性"捆成一个整体,位置、速度、质量不再散落天涯。

module particle_mod use iso_fortran_env, only: real64 implicit none type :: particle_t real(real64) :: x, y, z ! 位置 real(real64) :: vx, vy, vz ! 速度 real(real64) :: mass ! 质量 contains procedure :: kinetic => particle_kinetic end type particle_t contains real(real64) function particle_kinetic(self) result(e) class(particle_t), intent(in) :: self e = 0.5_real64 * self%mass * (self%vx**2 + self%vy**2 + self%vz**2) end function particle_kinetic end module particle_mod program particle_demo use particle_mod, only: particle_t use iso_fortran_env, only: real64 implicit none type(particle_t) :: p p%x = 0.0_real64 p%y = 0.0_real64 p%z = 0.0_real64 p%vx = 1.0_real64 p%vy = 2.0_real64 p%vz = 0.0_real64 p%mass = 2.0_real64 write(*,*) 'kinetic energy =', p%kinetic() ! 调用绑定过程,像方法调用 end program particle_demo

类型定义与嵌套:让数据结构自己说话

派生类型可以嵌套:一个类型里放另一个类型,构建出层次化的数据模型。访问路径 cell%state%pressure 比孤立的 pressure(i,j,k) 语义清晰得多——前者自带上下文。

module grid_mod use iso_fortran_env, only: real64 implicit none type :: thermo_state_t real(real64) :: pressure real(real64) :: temperature real(real64) :: density end type thermo_state_t type :: cell_t real(real64) :: coords(3) ! 坐标 type(thermo_state_t) :: state ! 嵌套的物理状态 integer :: neighbors(6) ! 六个相邻单元索引 end type cell_t end module grid_mod

嵌套带来的结构即文档:读代码的人不用翻遍子程序,就知道一个网格单元"有哪些东西"。内存布局上,嵌套类型是连续存储的——编译器逐层计算偏移量,访问路径长一点,但换来的是缓存友好的布局。

类型绑定过程:数据与行为不再分离

类型绑定过程(type-bound procedure)是 F2003 的里程碑。把操作挂到类型上后,调用从 call update_particle(p, dt) 变成 call p%update(dt)。前者是"函数作用于对象",后者是"对象自己会做事"。语义变化背后是职责归属:数据与操作它的逻辑封在一起,外部代码通过接口交互,不触碰内部细节。

写法 风格 问题
call update(p, dt) 过程式 操作与数据分离,接口列表随字段增多而膨胀
call p%update(dt) 对象式 操作归属清晰,调用者只需知道对象能做什么

绑定过程里用 class(particle_t), intent(in) :: self 接收对象自身——class 而非 type 是为了给第 5 章的多态留门,现在只需记住这是标准写法。

可见性控制与抽象类型

类型内部字段默认公有,但工程上应控制可见性。把关键组件声明为 private,外部只能通过绑定过程读写,防止某个角落的代码悄悄改掉引力常数这类关键值。做法是在类型里加一行 private

module safe_mod use iso_fortran_env, only: real64 implicit none private ! 模块默认私有 type, public :: constants_t private real(real64) :: gravity = 9.80665_real64 contains procedure :: get_gravity => constants_get_gravity end type constants_t contains real(real64) function constants_get_gravity(self) result(g) class(constants_t), intent(in) :: self g = self%gravity end function constants_get_gravity end module safe_mod

抽象类型(type, abstract)则是"接口契约":不能被实例化,只能被继承,延迟绑定(deferred)过程只给签名不给实现。第 5 章会用它搭求解器框架,这里先认识概念。

生命周期:allocatable 组件与 finalizer

类型里装 allocatable 数组是常态——粒子数量运行时才知道。关键区别:allocatable 组件在对象销毁时自动释放,pointer 组件不会。所以类型内部优先用 allocatable;如果必须用指针组件(比如实现多态),就需要 finalizer 在对象销毁时手动收尾。

module mesh_mod use iso_fortran_env, only: real64 implicit none type :: mesh_t real(real64), allocatable :: nodes(:,:) ! 节点坐标,规模运行时确定 integer :: nnode = 0 contains final :: mesh_destroy end type mesh_t contains subroutine mesh_destroy(self) type(mesh_t), intent(inout) :: self if (allocated(self%nodes)) deallocate(self%nodes) end subroutine mesh_destroy end module mesh_mod

finalizer 会在对象离开作用域时被自动调用,负责释放指针组件持有的资源。注意所有权语义:对象是"拥有"这块资源还是"引用"别人家的资源——只有拥有的才在 finalizer 里释放,引用别人的绝不能释放,否则就是悬空指针。

设计判断:什么时候用派生类型

判断标准很简单:如果一组数据"永远一起出现、一起传递、一起被操作",就该合成一个派生类型;如果它们生命周期各不相同、几乎没有一起出现的时刻,别硬凑。经验法则是优先组合而非继承(F77 年代的并列数组就是隐形耦合的源头),但也别为单个标量造类型——过度封装会让代码读起来像套娃。保持类型单一职责,一个类型只描述一个物理或逻辑概念。

学习目标

阅读完本节,你应当能够:

  1. 写出一个含嵌套组件与私有字段的派生类型定义
  2. 用类型绑定过程把操作挂到类型上,调用形式变为对象方法
  3. 解释 public/private 在类型内部与模块层面的作用
  4. 说出 allocatable 组件与 finalizer 如何配合管理资源
  5. 判断何时该用派生类型而非并列的多个数组

本节要点回顾

  • 派生类型把物理对象装进代码:数据与行为封装成一个整体,传参列表大幅收敛
  • 嵌套组件建立数据层级cell%state%pressure 自带上下文,结构即文档
  • 类型绑定过程改写作范式:从"函数作用于对象"到"对象自己会做事"
  • private 是默认防线:关键组件私有化,外部只能走接口,防误改
  • allocatable 组件自动释放:优先于指针组件,指针必须配 finalizer
  • 组合优于继承、类型保持单一职责:别造套娃类型,也别把该聚合的数据散成数组

类型有了,还得管好它们占的内存——下一节看 allocatable 与 pointer 的所有权之争。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U