前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >fortran中的数组

fortran中的数组

作者头像
自学气象人
发布2023-06-20 16:13:36
4070
发布2023-06-20 16:13:36
举报
文章被收录于专栏:自学气象人自学气象人

本文由知乎答主木子穿叶提供

在前三篇笔记,学习了Fortran作为一个编程语言,最基本的内容:变量,输入输出,流程控制和程序结构。接下来是Fortran的数组,我认为这是Fortran语言最有价值的精华部分,因此特意放在了学习笔记靠后的部分,在学习了基本的语法和子程序等之后。注意,Fortran的字符集不包括中括号[],因此与c语言的风格不同,Fortran对数组分量的操作全都是使用小括号()的。

因为这部分内容比较重要,不像前几篇对Fortran 77的上古语法大部分进行了忽略,这一篇对于Fortran 77的语法也进行介绍。

一维数组

最基本的一维数组声明如下

代码语言:javascript
复制
integer :: nums(10)
integer, parameter :: len = 20
real :: datas(len)

一维数组的类型可以是integer, real, complex, logical四种基本类型,(也可以是字符或者自定义类型,暂时不管)一维数组的长度可以是字面值常量,也可以是声明为parameter的整数——和c语言一样,数组的长度需要在编译时确定。(与c/c++语言不同,我们不需要纠结Fortran声明和定义的区别,全部称为声明)

代码语言:javascript
复制
nums(1) = 0
a = 2
nums(a) = nums(1) + 1

数组分量的用法如上,数组分量的索引可以是整数常量或者整数变量,编译器不会进行索引的越界检查,越界检查需要程序员自行负责。

可以使用其他语法进行数组的声明,在Fortran 77中没有双冒号,而且需要两条命令分别确定数组元素的类型和数组的尺寸。

代码语言:javascript
复制
! 基本的用法
integer :: a(10)

! 这是另一种用法
integer, dimension(10) :: a

! 这是Fortran 77 的语法
integer a
dimension a(10)

二维数组与高维数组

与一维数组同理,二维数组的定义如下

代码语言:javascript
复制
! 基本的用法
real :: a(5,10)

! 这是另一种用法
real, dimension(5,10) :: a

! 这是Fortran 77的语法
read a
dimension a(10,10)

Fortran原生支持最多7维的数组。

代码语言:javascript
复制
real :: a(2,2)
a(1,1) = 1

特别需要注意的是,Fortran的下标从1开始!Fortran对于高维数组在内存中的连续存储方式和c语言是相反的,分别为列优先和行优先。Matlab对数组的处理继承了Fortran的风格,也是下标从1开始,列优先。 列优先:只有第一个分量变化的元素在内存中连续排列;行优先:只有最后一个分量变化的元素在内存中连续排列。

代码语言:javascript
复制
integer :: a(3,2)
! 数据在内存中的连续分布
! a(1,1) => a(2,1) => a(3,1) => a(1,2) => a(2,2) => a(3,2)

自定义索引

索引默认从1开始,但是也支持显式指定数组的合法索引范围,范围的左右是闭区间。例如

代码语言:javascript
复制
integer a(0:5)
! 合法元素 a(0) a(1) a(2) a(3) a(4) a(5)

integer b(2:3,-1:1) ! 甚至允许负值作为索引
! 合法元素
! b(2,-1) b(2,0) b(2,1)
! b(3,-1) b(3,0) b(3,1)

integer c(1:5)
! 等效于基本的 integer c(5) 把从1开始省略
! 合法元素 c(1) c(2) c(3) c(4) c(5)

批量设置初值

Fortran 77使用data命令赋初值,例如

代码语言:javascript
复制
integer a
dimension a(5)
data a /1,2,3,4,5/
! a(1)=1 a(2)=2 a(3)=3 a(4)=4 a(5)=5

integer i
integer b
dimension b(10)
data (b(i), i=2,4) /10,20,30/ ! 一种隐式循环语法
! b(2)=10 b(3)=20 b(4)=30

Fortran 90可以抛弃data命令,对隐式循环语法也有更强的支持。

代码语言:javascript
复制
integer i
integer :: a(5) =(/ (i,i=1,5) /)
! a(1)=1 a(2)=2 a(3)=3 a(4)=4 a(5)=5

integer :: b(3) =(/ 10,20,30 /)
! b(1)=10 b(2)=20 b(3)=30

这里使用(/ /)结构省略了data命令,直接批量赋初值。

对数组的所有元素赋同一个初值,有如下的语法糖

代码语言:javascript
复制
integer :: a(3) = 5
! a(1)=5 a(2)=5 a(3)=5

数组整体运算

Fortran 90 提供了很多语法糖——原生支持对数组整体进行的运算,相比于c语言更加方便,不需要依赖循环语句实现。

代码语言:javascript
复制
integer :: a(10)
integer :: b(10)
integer :: c(10)

! 对所有元素都赋值为5
a = 5 ! a(i)=5

! 逐个分量操作
! 要求a,b或者a,b,c为尺寸完全相同的数组,自动遍历所有元素
a = b ! a(i)=b(i)
a = b+c ! a(i)=b(i)+c(i)
a = b-c ! a(i)=b(i)-c(i)
a = b*c ! a(i)=b(i)*c(i)
a = b/c ! a(i)=b(i)/c(i)
a = sin(b) ! a(i) = sin(b(i)) 内置函数如sin等支持此类操作

以上对于高维数组也是一样的。

数组部分运算

这也是Fortran 90之后的语法,和python的numpy等的数组切片操作很类似,或者说numpy的切片继承了Fortran的语法风格。

代码语言:javascript
复制
integer :: a(10)
integer :: b(20)
integer :: c(10,10)

a(3:5)=5 ! a(3)=5 a(4)=5 a(5)=5
a(3: )=5 ! a(3)=...=a(10)=5 可以缺省一侧的下标范围
a(3:5)=(/ 3,4,5 /) ! a(3)=3 a(4)=4 a(5)=5 左右必须一样多

a(1:3)=b(4:6) ! a(1)=b(4) a(2)=b(5) a(3)=b(6) 左右必须一样多
a(:)=b(11:) ! a(1)=b(11) ... a(10)=b(20)

a(:)=c(:,1) ! a(1)=c(1,1) ... a(10)=c(10,1) 限制c的第二个分量为1对a进行赋值

还可以有更复杂的,使用步长,步长"a : b : c"相当于c语言风格的

代码语言:javascript
复制
// c>0
for(i=a;i<=b;i=i+c){ ... }

// c<0
for(i=a;i>=b;i=i+c){ ... }

示例如下

代码语言:javascript
复制
integer :: a(2,3)
integer :: b(2,3)

b = a(2:1:-1, 3:1:-1)
! 对于a的前面的维度视作内层循环,多层循环依次赋值
! 对b视作b(:,:)按照内存的列优先顺序依次被赋值
! b(1,1) = a(2,3)
! b(2,1) = a(1,3)
! b(1,2) = a(2,2)
! b(2,2) = a(1,2)
! b(1,3) = a(2,1)
! b(2,3) = a(1,1)

上述针对数组的整体运算和部分运算放在赋值的左侧和右侧均可,相当于隐含的循环展开。write(,)语句也支持。

代码语言:javascript
复制
integer :: a(2,3)
! 对a赋值,运算

write(*,*) a
! 输出a(1,1) a(2,1) a(1,2) a(2,2) a(1,3) a(2,3)与内存顺序一致

write(*,*) a(1,:)
! 输出a(1,1) a(1,2) a(1,3)

write(*,*) a(1,3:1:-1)
! 输出a(1,3) a(1,2) a(1,1)

动态数组

Fortran 77不支持动态数组,数组尺寸必须在编译期间确定,只能在代码中使用足够大的N作为数组尺寸。Fortran 90开始支持动态数组(可变长数组),数组尺寸可以在运行期间确定。

使用allocatable声明一个动态数组

代码语言:javascript
复制
integer, allocatable :: a(:) ! 声明一个一维数组a, 尺寸待定
integer, allocatable :: b(:,:) ! 声明一个二维数组b, 尺寸待定

在源代码的声明部分不需要明确数组的尺寸,在源代码的运算部分使用该数组之前,使用allocate命令明确数组尺寸,分配相应的内存。(相当于c语言的malloc)

代码语言:javascript
复制
integer :: len
integer, allocatable :: a(:)
! 也可以写作如下形式
integer, allocatable, dimension(:) :: a

read(*,*) len ! 获取动态数组需要的尺寸
allocate(a(len)) ! 为动态数组分配内存

! 可以正常使用数组a

和c语言一样,Fortran在运行期间分配内存allocate存在是否成功的问题,以及使用完成后及时释放内存deallocate的问题。

代码语言:javascript
复制
integer :: error ! 事先声明好的整型变量,用来记录标识
integer :: len1,len2
integer, allocatable :: a(:,:)

... ! 获取len1,len2

! 完整的allocate语句,包含一个标识记录是否成功分配内存
! allocate会通过stat传递给error一个数值
! error == 0 代表成功分配,error /= 0 代表失败
allocate(a(len1,len2), stat=error)
if(error /= 0)
    ! 未成功对数组a分配内存
end if

! 也可以使用allocated语句,判断当前动态数组是否成功分配内存,返回一个逻辑值
if(.not. allocated(a))
    ! 未成功对数组a分配内存
end if

... ! 使用数组a

! 释放内存,此后仍然可以继续allocate与deallocate,相当于重新设置数组尺寸。也可以使用标识
deallocate(a,stat=error)
! 或者直接deallocate(a)

固定尺寸的数组和动态数组的本质区别,就像c/c++中的一样:固定尺寸的数组在栈上分配内存,不需要手动释放;动态数组在堆上分配内存,需要手动释放,相比于栈可使用的空间更多。对大规模的数据存储需求,倾向于在主程序中使用动态数组,由主程序负责分配和释放。

注:之前的笔记遗漏了一部分——显式指定参数,以改变多个参数的匹配顺序。

代码语言:javascript
复制
subroutine fun(x1,x2,x3)
...
end subroutine fun

! 使用fun(a,b,c)调用,则默认按照顺序对应
! x1=a x2=b x3=c

! 可以如下显式改变参数的匹配
! fun(x1=a,x3=b,x2=c)

数组作为参数传递

和c语言类似,直接把数组a作为实参传递给子程序subroutine或者函数function等,相当于把第一个元素的内存地址传递过去。如果子程序把这个形参定义为整数,则子程序得到的是内存地址对应的整数。此时对整数的修改会导致调用者丢失整个数组,非常危险。如果子程序把这个形参定义为数组,则会根据形参数组的尺寸处理实参对应的部分内存,实质还是传地址,因此对分量的修改会反馈给调用者。

以一个例子说明,主程序

代码语言:javascript
复制
program main
    implicit none
    integer :: a(5) = (/ 1,2,3,4,5 /)

    call sub_num(a) ! 把a第一个元素的地址当作整数传过去
    call sub_array5(a) ! 把a当作一个尺寸为5的一维数组传过去
    call sub_array22(a) ! 把a当作一个尺寸为2*2的二维数值传过去
end program main

三个子程序为

代码语言:javascript
复制
subroutine sub_num(a)
    implicit none
    integer :: a
    write(*,*) a ! 读出的是a(1)的内存地址
end subroutine sub_num

subroutine sub_array5(a)
    implicit none
    integer :: a(5)
    write(*,*) a ! 读出的是a在内存中的全部元素
    ! a(1) a(2) a(3) a(4) a(5)
end subroutine sub_array5

subroutine sub_array22(a)
    implicit none
    integer :: a(2,2)
    write(*,*) a ! 读出的是a在内存中的前4个元素
    ! a(1,1) a(2,1) a(1,2) a(2,2)
end subroutine sub_array22

将数组作为参数传递,本质上是把数组变量(也就是连续内存部分的第一个元素的地址)以址传递的形式传过来,而子程序/函数的接收和处理方式,取决于自己对形参的定义:如果视作一个整数则只能访问和修改地址,如果视作数组则会进一步访问到数组中的连续内存部分,依照自己理解的尺寸进行处理。

通常为了安全,将数组作为参数传递时,也会把尺寸作为若干整数变量一起传递给子程序/函数。

指针

Fortran实际上还有指针pointer,与c语言的指针相比感觉非常鸡肋:1. 我们没有用Fortran建立链表之类的动态需求,动态数组完全够用。2. 语法比c语言更繁琐而且更弱,需要target形容的变量才能被指针指向,也没有*p这种运算。3. 各种Fortran编译器对于指针的实现可能有差异或麻烦,我们倾向于完全避免使用指针。

Fortran的指针pointer需要配套target使用,target表明变量可以被指针指向,pointer表明这个变量是指针。

一个指针的简单例子如下

代码语言:javascript
复制
program main
    implicit none
    integer, target :: a=1 ! 声明一个可以被当作目标的整数变量
    integer, pointer :: p  ! 声明一个可以指向整数的指针
    logical :: b

    ! 把指针初始化时赋给null, 可以更安全, 表明这个指针是不可访问的
    integer, pointer :: p2 => null()

    p=>a ! => 将指针p指向目标变量a,

    ! 可以通过指针直接访问目标变量
    write(*,*) p ! 1

    a=2 ! 对目标变量的修改也会体现在指针访问时
    write(*,*) p ! 2

    p=3 ! 基于指针的修改也会体现在原始变量上
    write(*,*) a ! 3

    ! 可以检查当前的指针是否可以访问
    b = associated(p)
    ! 可以检查当前的指针是否绑定到当前的目标变量
    b = associated(p,a)
    ! 可以用nullify命令把指针设置为不可访问的
    nullify(p)

    stop
end program main
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 自学气象人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一维数组
  • 二维数组与高维数组
  • 自定义索引
  • 批量设置初值
  • 数组整体运算
  • 数组部分运算
  • 动态数组
  • 数组作为参数传递
  • 指针
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档