特定の働きをするプログラムの部分を独立させて,プログラムのどこからでもそれを呼び出すようにすることができる.関数もその一つであったが,さらにそれを一般的にしたものがサブルーチン(副プログラムともいう)である.
関数とサブルーチンの違いは何か?関数は単一の値や配列を返すが,サブルーチンはそれ自体値を返さない点にある.しかし,じつはサブルーチンは値(計算結果など)を返せるのである.
関数とサブルーチンの使い分けは
となる.値を返される必要のないサブルーチンについて例を挙げる.例によってうるう年である.
実行結果
Year : 1990: not a leap year Year : 1991: not a leap year Year : 1992: leap year Year : 1993: not a leap year Year : 1994: not a leap year Year : 1995: not a leap year Year : 1996: leap year Year : 1997: not a leap year Year : 1998: not a leap year Year : 1999: not a leap year Year : 2000: leap year(400) Year : 2001: not a leap year
後半部にサブルーチンが一つある.引数year
を受け取り,それがうるう年かどうか判定してprint
し,サブルーチンの中で処理が完結する.一切値を返していないことに注意.
サブルーチンを呼び出すのは,call文である.関数とは違って型の概念がないので,宣言は本来不要であるが,例のようにexternal文を使って宣言する場合もある.
サブルーチンは,subroutine
宣言の後に関数の名前(leap_year
),そして次のカッコの中に,受け取る値(year
)を,複数あるときはコンマで区切って書く. サブルーチンブロックはこのsubroutine
文から,end subroutine文までである.
引数も変数だから,型宣言をしなければならない,というのは関数と同じ.intent属性も使用可能.
INTENT
属性の種類は,
INTENT(in)
:サブルーチンが「受け取る」のみの引数INTENT(out)
:サブルーチンが計算結果を格納して返すための引数INTENT(inout)
:INとOUTの両者の性格を併せ持つ引数次の例をみてみよう.
実行結果
before calling : a,b=100, 10 first and second parameter : 100, 10 after calling : a,b=10, 10
「after calling」で,a
の値が10に変わってしまっていることに注意.why?
これは,サブルーチンの中で引数を変更する(s=t
)と,その変更された引数を,call
した側(この場合はprogram ex11
も受け取ってしまうからである.
というより,サブルーチンを使って何かの値を返すのは,こういう方法を用いるのである.この方法を使えば,いくつの値でも返すことができる.
便利な方法には,もちろん欠点もある.たとえば,あるサブルーチンの中で,変えるつもりのない引数が,何かの間違いで変わってしまっていたらどうなるか?影響はそのサブルーチンだけにとどまらず,そのサブルーチンをcall
したほうも引数として与えた変数の値が変わるということで影響を受けてしまうのである.
このようなサブルーチンを超えた値の受け渡しにからむバグは極めて発見しにくい.
だからこそ,予期せぬ引数改変が起きないようにINTENT
属性が導入されたのである.
もう一つ注意すべきなのは,call modify(2*a, b)
のようにコールした場合は,サブルーチンmodify
内で第一引数を変更してもaには影響がない点である. 単独の変数をもって引数とした場合のみ,サブルーチン内の引数とcall
で使われる変数が1対1に対応し,同時に変化するのである.
例題1:ex11のサブルーチンmodifyにおいて,第一引数に
INTENT(in)
属性をつけて宣言するとどうなるか.
例題2:ex11でcall modiry(a*2, b)
とするとどうなるか.
(答1:コンパイルエラーとなる 答2:call
後もa=100, b=10
となる.)
配列を関数やサブルーチンに渡すことは多い.この場合配列のシェイプを関数やサブルーチンにどう伝えるかが問題になる.
第一の方法は,完全にサイズを決めうちしてしまう方法である.
! !function ! integer function array_sum(x, n) integer, intent(in) :: n real, intent(in) :: x(1:100) integer :: i real :: ans !!... ans = 0 do i = 1, 100 ans = ans + x(i) enddo array_sum = ans end function array_sum ! ! 呼び出し側 ! program test !... integer :: sum real :: data(1:100) !... print *, 'sum of array = ', array_sum(data) !... end program test
原始的で確実だけど結構不便かもしれない.が,「とりあえず」プログラムには結構有効.
第二の方法は整合配列(形状明示配列)である.これは,関数などの引数にシェイプに関する整数をも組み込み,それで配列のシェイプを明示的に指定する方法である.
! !function ! integer function array_sum(x, n) integer, intent(in) :: n real, intent(in) :: x(1:n) integer :: i real :: ans !!... ans = 0 do i = 1, n ans = ans + x(i) enddo array_sum = ans end function array_sum ! ! 呼び出し側 ! program test !... integer :: sum real :: data(1:10) !... print *, 'sum of array = ', array_sum(data, 10) !... end program test
これも確実なやり方.
第三が少し難しくて,形状引継ぎ配列というものである.これは,INTERFACEという文を使って,配列のランクだけを呼び出し側でも宣言しておく.
! !function ! integer function array_sum(x) real, intent(in) :: x(:,:) ! 2-dimension integer :: n1, n2 integer :: i1, i2 real :: ans n1 = SIZE(x, 1) n2 = SIZE(x, 2) !!... ans = 0 do i1 = 1, n1 do i2 = 1, n2 ans = ans + x(i1, i2) enddo enddo array_sum2 = ans end function sum_array ! ! 呼び出し側 ! program test !... interface real function array_sum2( arr ) real, intent(in) :: arr(:, :) ! 2-dimension end function end interface integer :: sum real :: data(1:10, 1:10) !... print *, 'sum of array = ', array_sum2(data) !... end program test
INTERFACE
文は,呼び出し側で関数やサブルーチンの受け取る引数を指定することができる(こうすると引数受け渡しミスが更に減る).しかしINTERFACE
を介した結合でも,配列のランクはそろえておく必要がある.