40
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Fortranにおける配列の宣言方法と関連機能

Last updated at Posted at 2018-12-01

概要

先日の記事では,Fortranの整数,実数,複素数型の型宣言についてまとめました.この記事では,整数・実数型の配列についてまとめます.
配列と親和性の高い動的割付や,手続(関数あるいはサブルーチン)の引数としての受け渡しについても言及します.
coarrayには言及しません.

先日の記事で書けなかった文字および文字列,ユーザ定義派生型も含めた記事にしたかったのですが,配列について書くことが意外に多かったので再び先送りです.

使用環境

コンパイラ バージョン
intel Parallel Studio XE Composer Edition for Fortran 17.0.4.210
PGI Visual Fortran for Windows 18.7
gfortran 7.3.0 (for Ubuntu on WSL)

更新履歴

配列の宣言

Fortranで配列を宣言するには,型宣言の際にdimension属性を付与します.配列要素数は,dimensionに続けて()で囲んで書きます.多次元配列を宣言するときは,同一カッコ内に2次元目以降の要素数を,カンマで区切って書きます.次元の数だけカッコを書かなくていいので手軽です.

,dimension(要素数[,要素数,...])[,dimension以外の属性,...] :: 変数名 !dimension属性を使った宣言

変数名に続けて配列要素数を書いても,同様に配列となります.

[,属性,...] :: 変数名(要素数[,要素数,...]) !変数名+(配列要素数)

dimension属性で配列要素数を指定し,かつ変数名の後ろに配列要素数を書いた場合は,変数名の後ろに書いた配列要素数が優先されます.このことから,著者はごく一部の場合1を除いてdimension属性を書かず,変数名の後ろに配列要素数を書いて配列を宣言します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32),dimension(10)  :: a ! 4バイト整数型の配列(10要素) 
    real(real32)  ,dimension(5,2) :: b ! 単精度実数型の2次元配列(1次元目5要素,2次元目2要素)
    
    integer(int32) :: c(10)  ! 4バイト整数型の配列(10要素)
    real(real32)   :: d(5,2) ! 単精度実数型の2次元配列(1次元目5要素,2次元目2要素)

    real(real32),dimension(5) :: d(2) ! 単精度実数型の1次元配列(2要素)
end program main

配列要素の並び

Fortranでは,配列要素は第1次元から順番に並んでいます.
下の例では,1,2,3次元にそれぞれ2個の要素を持つ1バイト整数型の配列を宣言し,各要素のアドレスを関数loc()で取得,表示しています.a(1,1,1), a(2,1,1), ...と1次元目から順次配列要素番号を変化させていくと,メモリアドレスが1バイトずつ連続的に変化していることが確認できます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    integer(int8) :: a(2,2,2)

    print *,loc(a(1,1,1)) !I 13613600 P 5369696704 g 140736634309672
    print *,loc(a(2,1,1)) !  13613601   5369696705   140736634309673
    print *,loc(a(1,2,1)) !  13613602   5369696706   140736634309674
    print *,loc(a(2,2,1)) !  13613603   5369696707   140736634309675
    print *,loc(a(1,1,2)) !  13613604   5369696708   140736634309676
    print *,loc(a(2,1,2)) !  13613605   5369696709   140736634309677
    print *,loc(a(1,2,2)) !  13613606   5369696710   140736634309678
    print *,loc(a(2,2,2)) !  13613607   5369696711   140736634309679
end program main

配列要素の範囲の指定

Fortranの配列の要素番号は1から始まります.integer :: a(100)と宣言すると,配列要素はa(1)からa(100)までの100要素です.
一方で,配列要素番号の下限と上限を指定して宣言することもできます.その場合は,配列要素数の代わりに下限:上限と書きます.

 :: 変数名(下限要素番号:上限要素番号)

配列要素番号には,0も負の値も許容されます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    integer(int32) :: a(0:999,-499:500) !1次元目は0から999まで1000要素,2次元目は-499から500まで1000要素
end program main

一括代入と配列構成子

一括代入

配列の要素番号を指定せず変数名のみを利用すると,配列全要素に対する処理になり,全要素への代入や全要素に対する同一の演算を簡潔に記述できます.
このとき,変数名だけでは文を見てスカラー変数に対する処理か配列に対する処理を行っているのかわからないので,(:)を用いて配列の全要素に対する処理であることを明記します.(:)を付け忘れると致命的な問題を引き起こす場合があるので,後の章でそれを説明します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32) :: a(5)
    real(real32) :: b(5)
    real(real32) :: c(5)
    real(real32) :: d(5)

    a(:) = 1.0 !a(1)~a(5)に1.0を代入
    b(:) = 2.0 !b(1)~b(5)に2.0を代入
    c(:) = 0.0 !c(1)~c(5)に0.0を代入
    d(:) = 0.0 !d(1)~d(5)に0.0を代入

    c(:) = a(:) + b(:) !配列の各要素同士の和を計算
    print *,c(:)
    !   3.000000       3.000000       3.000000       3.000000       3.000000
    
    d(:) = b(:) * c(:) !内積ではなく,配列の各要素同士の積(アダマール積)を計算
    print *,d(:)
    !   6.000000       6.000000       6.000000       6.000000       6.000000
end program main

部分配列

配列要素の下限と上限を定めたように,始点:終点とコロンの左右に配列要素番号を書くことで,その範囲の配列要素への処理を記述できます.コロンで指定された範囲を部分配列とよびます.部分配列の始点と終点に加えて,始点:終点:増分というように増分を指定することもできます.部分配列の範囲が配列要素の下限あるいは上限と一致する場合は,記述を省略できます.
増分には負の値を指定することもできますが,その場合に始点<終点であると,どの要素も処理されません.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32) :: a(5)
    real(real32) :: b(5)
    real(real32) :: c(5)
    real(real32) :: d(5)

    a(:) = 1.0
    b(:) = 2.0
    c(:) = 0.0
    d(:) = 0.0

    c(2:4) = a(2:4) + b(2:4) !配列の2,3,4番の要素同士の和を計算
    print *,c(:)
    !   0.000000       3.000000       3.000000       3.000000       0.000000

    d(1:5:2) = a(::2) + b(5:1:-2) !d(1)=a(1)+b(5), d(3)=a(3)+b(3), d(5)=a(5)+b(1)を計算 
    print *,d(:)
    !   3.000000       0.000000       3.000000       0.000000       3.000000
end program main

配列構成子

配列の要素に異なる値を一括で代入したい場合には,配列構成子が利用できます.配列構成子を用いて配列を記述するには,[]あるいは(//)の間に数値リテラルや変数(配列を含む)を記述します.[ ](/ /)の上位互換なので,前者の使い方を覚えるだけで十分です.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32) :: a(5)
    real(real32) :: b(5)
    real(real32) :: c(5)

    a(:) = [0.1, 0.2, 0.3, 0.4, 0.5]
    b(:) = [1, 2, 3, 4, 5]                   !暗黙の型変換
    c(:) = [real(real32) :: b(1), a(2:4), 0] !明示的な型変換
    print *,c(:)
    !    1.000000       0.2000000       0.3000000       0.4000000    0.00000
end program main

すべて数値リテラルを用いて配列を構成する場合(上記プログラムのb(:)),記述は適当でも暗黙の型変換によって適切な型に変換されます.一方で,配列構成子に変数を用いる場合,変数の型が混在していると,コンパイルエラーになります(PGIコンパイラではエラーになりませんでした).そのような場合には,配列構成子[]内の一番左に型名 ::を置いて,明示的に型変換を行う必要があります.

配列構成子[]内に型名だけを書いた場合は,長さ0の配列が得られます.

[integer :: ] !整数型の長さ0の配列

配列構成子は1次元配列しか作ることができません.入れ子にできますが,入れ子にしても多次元配列にはならず,1次元配列に展開されます.多次元配列への値の代入に配列構成子を利用する場合,Intelコンパイラだけはよろしくやってくれますが,他のコンパイラでは後で紹介するreshape()関数を利用して,配列の形状を明示的に指定する必要があります.

配列の形状に関する組込関数

配列の形状や要素番号の下限,上限を調べるための組込関数が用意されています.

関数 機能
size(array=配列[,dim=次元,kind=種別]) 指定した次元の要素数をinteger(kind=種別)で返す
省略した場合は配列の全要素数を,integerで返す
shape(array=配列[,kind=種別]) 配列の各次元の要素数をinteger(kind=種別)で返す
lbound(array=配列[,dim=次元,kind=種別]) 指定した次元の配列要素番号の下限をinteger(kind=種別)返す
次元を省略すると,すべての次元の下限値を返す
ubound(array=配列[,dim=次元,kind=種別]) 指定した次元の配列要素番号の上限をinteger(kind=種別)返す
次元を省略すると,すべての次元の上限値を返す
program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: a(100,100)
    integer(int32) :: b(0:999,-499:500)
    integer(int32),dimension(5) :: c(2)

    print *,size(a)                             !   10000
    print *,shape(a)                            !     100          100
    print *,size(b)                             ! 1000000
    print *,shape(b)                            !    1000         1000
    print *,lbound(b,1),ubound(b,1)             !       0          999
    print *,lbound(b,2,int32),ubound(b,2,int64) !    -499                      500
    print *,lbound(b)                           !       0         -499
    print *,ubound(b)                           !     999          500

    print *,size(c)                             !       2
    print *,shape(c)                            !       2

    ! 配列構成子を入れ子にしても,多次元配列はできない
    print *,shape( [[1,2,3],[4,5,6]] )          ! 6
end program main

dimension属性を使う方法と変数名の後ろに配列要素数を書く方法を併用して宣言した配列cは,配列要素数も配列の形状も2になっており,変数名の後ろに置いた要素数が優先されていることがわかります.

配列の形状を変更する関数もあります.

関数 機能
reshape(source=配列,shape=形状[,pad=パディング,order=次元の順序]) 配列要素を指定の形状に変更した配列を返す
変形後の配列が元の配列よりも大きい場合はパディングで指定した数字が使われる
次元の順序のオプションでは,入力配列の次元をどの順序で読むのかを指定する
program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: a(3,2) = reshape([1,2,3,4,5,6], [3,2])
    integer(int32) :: b(2,3)
    integer(int32) :: c(3,4)

    print *,a(:,1) !   1  2  3
    print *,a(:,2) !   4  5  6

    b = reshape(a, shape(b))
    print *,b(:,1) !   1  2 
    print *,b(:,2) !   3  4
    print *,b(:,3) !   5  6

    b = reshape(a, shape(b), order=[2,1]) !2次元目を先に変化させた上で形状を変更する
    print *,b(:,1) !   1  4
    print *,b(:,2) !   2  5
    print *,b(:,3) !   3  6

    c = reshape(a, shape(c), pad=[0]) !パディングに使う数は必ず配列
    print *,c(:,1) !   1  2  3
    print *,c(:,2) !   4  5  6
    print *,c(:,3) !   0  0  0
    print *,c(:,4) !   0  0  0

    c = reshape(a, shape(c), pad=[10,11,12,13]) !パディングに使う配列は繰り返し使われる
    print *,c(:,1) !   1  2  3
    print *,c(:,2) !   4  5  6
    print *,c(:,3) !  10 11 12
    print *,c(:,4) !  13 10 11
end program main

配列の順序を逆順にすると,Fortranの配列要素の順序をC言語などの配列要素の順序に変更できます.配列を転置するtransposeという関数も存在していますが,transposeは2次元配列しか処理できません.

配列定数

parameter属性を付与すると定数になります.定数は宣言時に値の代入が必須なので,配列構成子やreshape()関数を利用します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32),parameter :: a(6) = [real(real32) :: 1,2,3,4,5,6]
    real(real32),parameter :: b(3,2) = reshape([real(real32) :: 1,2,3,4,5,6], shape(b))

end program main

配列定数の暗黙形状宣言

配列定数では,配列の要素数や形状はあらかじめ決まります.Fortran 2008では,この性質を利用して,配列の定数を宣言する際にその要素数の記述を簡略化できます.それが,暗黙の形状配列宣言です.

暗黙の形状配列宣言は,配列要素番号の下限と上限を指定する形式に似ていますが,上限を*として,上限は右辺から決められるようにします.多次元配列にも対応しています.

,parameter :: 変数名(下限要素番号:*[,下限:*,...]) = 配列
program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer,parameter :: a(1:*) = [1,2,3,4]
    integer,parameter :: b(1:*,-1:*) = reshape([1,2,3,4,5,6],[3,2])

    print *,size(a),shape(a) ! 4 4
    print *,a(:)             ! 1 2 3 4

    print *,size(b),shape(b)    ! 6  3  2
    print *,lbound(b),ubound(b) ! 1 -1  3  0
    print *,b(:,:)              ! 1  2  3  4  5  6
end program main

なお,調査したコンパイラのうち,PGIコンパイラのみが対応していません.

配列の動的割り付け

配列要素数を型宣言時に決めず,プログラム実行時に動的に割り付けたい場合には,変数にallocatable属性を付与します.このとき,要素数の代わりにコロン:を用います.そのため,配列の次元だけは宣言時に決めておかなければなりません.

,allocatable[,その他の属性,...] :: 変数名(:[,:,...])

allocatable属性をつけた配列の割り付けには関数allocateを用い,解放にはdeallocateを用います.配列が既に割り付けられているかは,関数allocatedを利用して確認します.

関数 機能
allocate([型 :: ]動的割付け属性付き変数[(要素数,...)][,{source=クローン元,mold=見本},stat=割付状態]) 配列を動的に割り付ける
クローン元が指定されればそれを基に同じ形状かつ同じ値を持つ配列を割り付ける
配列要素数が指定され,かつクローン元にスカラー値が与えられると,配列の全要素がそのスカラー値となる
moldに配列が与えられると,配列の形状は引き継がれるが値はコピーされない
statに整数型変数を指定すると,allocate()の実行結果がその変数に代入されるので,実行時エラーを検出できる
deallocate(動的割付け属性付き変数[,stat=割付状態]) 動的に割り付けた配列を解放する
allocated(array=動的割付け属性付き変数) 変数が割り付けられていれば.true.,割り付けられていなければ.false.を返す

allocatableな配列は,deallocateを忘れたとしても,手続の終端あるいはプログラムの終端で自動的に解放されます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: a(3,2) = reshape([1,2,3,4,5,6], [3,2])
    integer(int32),allocatable :: b(:,:)
    integer(int32),allocatable :: c(:,:)
    
    print *,allocated(b) ! F
    allocate(b(2,3))     ! 各次元の要素数を指定して割付け.値は不定
    print *,allocated(b) ! T
    print *,shape(b)     ! 2 3
    deallocate(b)

    allocate(b,source=a) ! aのクローン(要素数,値)を作成
    print *,shape(b)     ! 2 3
    print *,b(:,1)       ! 1 2 3
    print *,b(:,2)       ! 4 5 6
    deallocate(b)
     
    allocate(b,source=reshape([6,5,4,3,2,1],[2,3])) ! 配列構成子も利用可能
    print *,b(:,1)                                   ! 6 5
    print *,b(:,2)                                   ! 4 3
    print *,b(:,3)                                   ! 2 1
    deallocate(b)
   
    allocate(b(3,2),source=0) ! 要素数を指定して割り付け,全要素を0で初期化
    print *,b                 ! 0 0 0 0 0 0
    deallocate(b)

    allocate(c,mold=a) ! aの要素数を引き継いだ配列を割付け
    print *,shape(c)   ! 3 2
    print *,c          !
    deallocate(c)
end program main

自動再割付

一括代入の節において,

(:)を付け忘れると致命的な問題を引き起こす場合がある

と書きました.それがこの動的再割付です.動的割付配列に異なる要素数の配列を代入すると,配列が自動的に右辺の配列に合わせて再割付けされます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: a(3,2) = reshape([1,2,3,4,5,6], [3,2])
    integer(int32),allocatable :: b(:,:)
    integer(int32) :: c(2,2) = reshape([4,3,2,1], [2,2])
    
    allocate(b,source=a) ! bを要素数(3,2)の配列として割り付ける
    print *,shape(b) ! 3 2
    print *,b(:,1)   ! 1 2 3
    print *,b(:,2)   ! 4 5 6

    b(:,:) = c(:,:)  !b(1:2,1:2) = c(1:2,1:2)という代入が行われる
    print *,shape(b) ! 3 2
    print *,b(:,1)   !I 4 3 3  P 4 3 2 g 4 3 3
    print *,b(:,2)   !  2 1 6    2 1 1   2 1 6
    deallocate(b)

    b = c            !bがb(3,2)からb(2,2)に再割付けされる
    print *,shape(b) ! 2 2
    print *,b(:,1)   ! 4 2
    print *,b(:,2)   ! 2 1
    deallocate(b)
end program main

b=cとした場合,自動再割付けによってbの要素数が[3,2]から[2,2]に変化しています.一方,b(:,:)=c(:,:)とした場合,少なくとも配列の形状は変化しません.代入は正しく行われるため,bの値が変化しています.Intelコンパイラとgfortranは納得のいく変化ですが,PGIコンパイラだけ挙動がおかしいようです.配列同士の代入や演算を記述する際,再割付けをする意思がないのであれば,(:[,...])は必ず書くようにしましょう.

この自動再割付け機能を用いると,簡単に配列の要素を増やすことができます.あまり融通は利きませんが,行数のわからないファイルから数値データを読み込む場合など,使いどころはあります.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32),allocatable :: a(:)
    
    a = [integer ::] !空の整数型配列a
    print *,a        !

    a = [a, 1]       !配列aに一つ値を追加
    print *,a        ! 1

    a = [2, a]       !配列aに一つ値を追加
    print *,a        ! 2 1

    deallocate(a)
end program main

動的割付け配列同士のコピーとコピー元の解放

動的割付け配列をallocateで割り付ける際に,source指定子を用いることで配列の形状や値をコピーできました.コピー元の配列がそれ以降必要ない場合は解放することになりますが,コピーと解放をまとめて行うサブルーチンmove_alloc()が用意されています.

関数 機能
move_alloc(from=コピー元動的割付け配列,to=コピー先動的割付け配列) コピー元の配列の形状と値に基づいてコピー先の配列を割り付ける
コピー元配列は解放される
program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32),allocatable :: a(:)
    integer(int32),allocatable :: b(:)
    
    print *,allocated(a), allocated(b) ! F F
    allocate(a,source=[1,2,3,4])       ! aを動的に割付け
    print *,allocated(a), allocated(b) ! T F
    print *,a(:)                       ! 1 2 3 4

    call move_alloc(from=a,to=b)       ! aを基にbを割り付け,aを解放
    print *,allocated(a), allocated(b) ! F T
    print *,b(:)                       ! 1 2 3 4
    deallocate(b)
end program main

仮引数

Fortranでは,配列を手続(サブルーチン,関数)に渡すとき,手続内における仮引数の定義の方法が何通りかあります.

配列の利便性を損なわないのは,形状引継配列とよばれる書き方です.動的割付け属性の配列と同様に,仮引数となる配列の要素数を:で表します.形状引継配列を用いると,手続の中でもsize()shape(){l,u}bound()で配列の情報を取得できます.実引数と仮引数で配列の次元数が異なる場合,コンパイルエラーとしてそれを検出してくれます.

大きさ引継配列は,仮引数となる配列の最も右側の次元の要素数を*とします.大きさ引継配列では,手続内で配列の形状に関する情報を取得することはできません.値を参照するためには,範囲や要素番号を明記する必要があります.実引数と仮引数で配列の次元が異なっていても,それを検出することはできません.例えば下記プログラムでは,実引数aは2次元配列,仮引数yは1次元配列ですが,コンパイルエラーにも実行時エラーにもなりません.

仮引数が形状引継配列であれば,部分配列を渡すこともできます.その場合,手続内部では配列要素の下限は1になります.一方で,手続の引数を手続内で配列の定義に用いることが可能です.それを利用して,配列要素番号の上限と下限を定めることができます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: a(4,3)

    call checkArray(a, a, a(1:3,2:3), -1,1,4,5)

    contains
    subroutine checkArray(x,y,z,ms,me,ns,ne)
        implicit none
        integer(int32) :: x(:,:)         !形状引継配列
        integer(int32) :: y(*)           !大きさ引継配列(古い書き方)
        integer(int32) :: z(ms:me,ns:ne) !形状引継配列(下限と上限を明示)
        integer(int32),intent(in) :: ms  !1次元目の下限
        integer(int32),intent(in) :: me  !1次元目の上限
        integer(int32),intent(in) :: ns  !2次元目の下限
        integer(int32),intent(in) :: ne  !2次元目の上限

        print *,shape(x)           ! 4 3
        !print *,shape(y)          !エラー
        !print *,shape(y(1:4,1:3)) !エラー
        print *,shape(y(1:12))     !12
        print *,shape(z)           ! 3 2
        print *,lbound(z)          !-1 4
        print *,ubound(z)          ! 1 5
    end subroutine checkArray
end program main

まとめ

配列に関わる処理は,Fortranにしては珍しく,コンパイラ間の挙動の違いはほとんどありません.自動再割付にさえ気をつければ,Fortranの配列は非常に扱いやすく柔軟です.

  1. 配列を指すポインタ変数を宣言するときのみ,dimension属性を利用しています.Fortranにはポインタ変数の配列は存在しないので,配列を指すポインタ変数であることを明示するためです.

40
33
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?