数组

在实际的程序编写过程中,我们有时候需要存储和操作一长串数字,而不是单个标量变量,例如存储100个粒子的位置,50个城市的经纬度。在计算机编程中,此类数据结构称为数组array

数组的声明

我们可以声明任意类型的数组,数组的声明需要用到dimension属性,这是我们学到的第二个属性

如果数组的大小在编写程序的时候就是已知的,那么就可以按照如下的方式定义,称为静态数组

program arrays
  implicit none

  ! 一维 integer 数组
  integer, dimension(10) :: array1

  ! dimension的语法糖,和上面的等价
  integer :: array2(10)

  ! 二维数组 
  real(8), dimension(10, 10) :: array3,array4

  ! 自定义数组的上标和下标
  real :: array5(0:9)
  real(8) :: array6(-5:5)

 ! 也可以使用parameter来定义
  integer,parameter::n=10
  real(8) :: array7(n)
  write(*,*)array7(3)   !输出单个数组元素
end program arrays
  • Fortran支持数组的最高维度是15维
  • Fortran的数组下标从1开始,且定义的时候是左闭右闭,和其他语言不同
  • 此类静态数组声明时的大小只能是常数或者具有parameter属性的常量
  • 数组的上下标不能超过定义时的大小,这类错误称为数组越界,在科学计算中十分常见。

数组的列优先

Fortran中数组是按照列优先的方式存储的,即左边的指标更接近

  integer::a(2,2)

排列的顺序为a(1,1),a(2,1),a(1,2),a(2,2)

数组初始化与构造器

数组的初始化有多种方式。

首先,Fortran支持将数组整体赋值,例如 a=1的形式就是将数组a中的元素全部赋值为1

其次就是数组构造器,语法是[]

同时数组还支持整体操作,包括运算符+-*/以及**,还有所有的逻辑运算符。对应的就是数组的相应位置做对应的算数操作,因此数组整体操作的时候数组大小和维度必须相同,或者为标量

program arrays
  implicit none
  integer :: array1(10)
  real::array2(3)
  array1=1 !整体初始化
  array1=[1,2,3,4,5,6,7,8,9,10] !数组构造器
  array2=[real::1.0,2.1,3.2]    !使用real::语句可以设定数组构造器的类型
  array2=array2+[1.0,1.0,2.0]   
  array2=array2+2.3             !也可以是标量 
end program arrays

注意 ,Fortran中数组构造器只能产生一维数组,如果要给多维数组赋值,则需要使用reshape函数,此时依旧是列优先.使用order关键字,可以进行行优先赋值

program arrays
  implicit none
  integer :: a(2,2),b(2,2)
  a=reshape([1,2,3,4],shape=[2,2])
  b=reshape([1,2,3,4],shape=[2,2],order=[2,1])
  write(*,*)"---------- a ------------"
  write(*,*)a(1,1),a(1,2)
  write(*,*)a(2,1),a(2,2)
  write(*,*)"---------- b ------------"
  write(*,*)b(1,1),b(1,2)
  write(*,*)b(2,1),b(2,2)
end program arrays
 ---------- a ------------
           1           3
           2           4
 ---------- b ------------
           1           2
           3           4

数组的切片

数组的切片使用的语法是(start:end[:step]),其中step可以是任意的整数,包括负数。

  • step=1时,可以省略
  • end表示的是数组末尾的时候,可以省略
  • start表示的是数组开头的时候,可以省略

同时数组切片还可以和数组的整体操作运算结合起来,这为Fortran的数组提供了强大的语法糖。

program arrays
  implicit none
  integer :: array1(10)
  array1=[1,2,3,4,5,6,7,8,9,10]
  write(*,*)array1(1:10:2)               !输出所有的奇数位
  array1(6:)=array1(:5)+array1(1:10:2)     !利用数组切片加整体运算
  write(*,*)array1(10:1:-1)              !倒序输出
end program arrays
$ fpm run
           1           3           5           7           9
          14          11           8           5           2           5           4           3           2           1

同时Fortran还支持使用数组作为数组的下标,此时会产生临时数组

program arrays
  implicit none
  integer :: array1(10),idx(3)
  array1=[10,9,8,7,6,5,4,3,2,1]
  idx=[4,6,1]
  write(*,*)array1(idx) 
end program arrays
$ fpm run
           7           5           10

可分配数组(动态数组)

有时候,数组的大小需要在计算中才能得出,前面使用的静态数组由于大小是固定的,有时候并不能满足相应的需求,这时候就需要动态数组。 定义动态数组需要用allocatable属性

program allocatable
  implicit none

  integer, allocatable :: array1(:)  ! 定义一维的可分配数组
  integer, allocatable :: array2(:,:)! 定义二维的可分配数组

  allocate(array1(10))   !分配大小
  allocate(array2(10,10))

  ! ...

  deallocate(array1) !释放
  deallocate(array2)

end program allocatable
  • 可分配数组的大小可以不确定,但是维数必须确定
  • 可分配数组分配之后就可以像普通数组一样使用,但是有一个重分配的机制,即a=[1,2,3]如果a是一个可分配数组,那么此时a就会重新分配内存,变成一个具有3个元素的数组

Fortran为带有可分配属性的变量提供了一个内置的子程序call move_alloc(from,to),使用这个子程序,可以将from的分配属性转移to,此时from将被释放

program arrays
   implicit none
   real,allocatable:: a(:),b(:)
   a=[real::1,2,3]
   write(*,*)allocated(a),allocated(b) !使用allocated函数检查分配属性
   call move_alloc(a,b)
   write(*,*)allocated(a),allocated(b)
   write(*,*)b
end program arrays
$ fpm run
 T F
 F T
   1.00000000       2.00000000       3.00000000

数组常量

因为常量在定义之后就不能更改了,所以数组常量的定义可以不写具体的大小,只使用(*)代替

program allocatable
  implicit none
  integer,parameter::a(3)=[1,2,3]
  integer,parameter::b(*)=[1,2,3]  !使用*代替
  integer,parameter::c(0:*)=[4,5,6]!也可以自定义起点
end program allocatable

思考题

  • 对二维数组使用数组切片操作,观察其输出
  • 对二维数组使用数组作为下标,观察其输出