言語要素の概要3

サブルーチン

特定の働きをするプログラムの部分を独立させて,プログラムのどこからでもそれを呼び出すようにすることができる.関数もその一つであったが,さらにそれを一般的にしたものがサブルーチン(副プログラムともいう)である.

関数とサブルーチンの違いは何か?関数は単一の値や配列を返すが,サブルーチンはそれ自体値を返さない点にある.しかし,じつはサブルーチンは値(計算結果など)を返せるのである.

関数とサブルーチンの使い分けは

となる.値を返される必要のないサブルーチンについて例を挙げる.例によってうるう年である.


実行結果

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属性の種類は,

次の例をみてみよう.


実行結果

 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を介した結合でも,配列のランクはそろえておく必要がある.


AGATASHI