概要
先日の記事では,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) |
更新履歴
- 2018年12月5日 配列定数の暗黙形状宣言を追記.
配列の宣言
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の配列は非常に扱いやすく柔軟です.
-
配列を指すポインタ変数を宣言するときのみ,
dimension
属性を利用しています.Fortranにはポインタ変数の配列は存在しないので,配列を指すポインタ変数であることを明示するためです. ↩