Fortran 入門

前へ   上へ   次へ

11 サブルーチンと関数(自作手続)

いくつかの場所で何度も利用される処理は手続(サブルーチン、もしくは関数)として定義しておくと便利です。 手続は返却値を持つ場合の関数と、返却値を持たない場合のサブルーチンに分けられます。

11.1 手続の定義

11.1.1 サブルーチンの定義

サブルーチンの定義は以下のように行います。
  subroutine サブルーチン名([引数,,...])
    引数の定義
   処理
  end [subroutine]

サブルーチンを呼び出す場合には、call 文を用いて行います。 例えば mysub というサブルーチンを呼び出すには、以下のように行います。

  call mysub(a,b,c)

11.1.2 関数の定義

関数の定義は以下のように行います。
  型名 function 関数名([引数,,...])
         引数の定義
      処理
       end [function]

もしくは、

  function 関数名([引数,,...])
    引数の定義(ここに関数の型名も記述)
    処理
  end [function]

例1)
  integer function addone(i)
    integer i
    addone = i+1
  end function

例2)
  function addone(i)
    integer i, addone
    addone = i+1
  end function

関数を呼び出す場合には、式として記述します。 例えば myfunc という関数を呼び出すには、以下のように行います。

  x = myfunc(a,b,c)
  ! もしくは例えば print 文に直接以下のように記述することも可能です
  print *, myfunc(a,b,c)

関数からの戻り値を返すには、以下のように関数と(デフォルトで)同じ名前を持つ結果変数に代入を行います。

  function mysum(a,b)
    integer a, mysum, b   ! 順番は関係ありません
    mysum = a+b           ! a+b が戻り値となります
  end function

11.1.3 手続引数の定義

引数の定義は以下のように行います。 引数は関数もしくはサブルーチン名のあとの括弧内に名前を列記します。

例)
subroutine mysub(a,b,c)

その下に各引数の型を宣言します。 その際の順番は特に関係ありません。 下記の例1も例2も同じ意味となります。

例1)
subroutine mysub(a,b,c)
integer a
real b
logical c
...

例2)
subroutine mysub(a,b,c)
logical c
integer a
real b
...

手続内の引数(仮引数と呼ばれています)の名前は、呼び出し側と一致させる必要はありません。

【例】
...
a = 10
b = 20
c = 30
call mysub(a,b,c)
...
contains
  subroutine mysub(x,y,z)
    integer x,y,z
    print *, x,y,z   ! x が呼び出し側の a に、y が b に、z が c にそれぞれ対応します
  end subroutine
end program

11.2 手続を属させる(contains 文)

手続は、メインプログラム、他の手続、もしくはモジュールに属させる事が推奨されています。 手続を何にも属させない事も可能で、このような手続は外部手続と呼びます。 外部手続は、引数の整合性のチェックが不十分になるなど、発見しづらいバグの原因となりますので、特別な理由がある場合を除きその利用は推奨されません。 以下は引数の整合性チェックが不十分になる例です。

[ extern-error-main.f90, extern-error-sub.f90 ] - 外部手続の引数の整合性チェックが不十分になるサンプル
【主プログラム】
program extern_error
  implicit none
  real :: a = 0.0
  call add_one(a)
  print *, a          ! 1.0 が出力されるはず??
end program extern_error

【外部サブルーチン(別ファイル)】
subroutine add_one(a)
  implicit none
  integer a
  a = a + 1
end subroutine

出力例:
   1.4012985E-45

手続をメインプログラム、他の手続、モジュールに属させるには contains 文を利用します。 以下にいくつかの例を示します。

[ sub-contained-in-main.f90 ] - メインプログラムに含まれるサブルーチンのサンプル

program contained_in_main
  implicit none
  real val
  val = 1.0
  call mysub(val)
contains
  subroutine mysub(x)
    real x
    print *, x
  end subroutine
end program contained_in_main

出力例:
   1.0000000

[ func-contained-in-sub.f90 ] - 外部サブルーチンに含まれる関数のサンプル
subroutine mysub01(x)  ! 外部サブルーチン(どこにも含まれて(contains されて)いない)
  implicit none   ! 強く推奨(外部手続では必要)
  real,intent(inout) :: x
  x = addone(x)
contains
  real function addone(x)
    real x
    addone = x + 1.0
  end function addone
end subroutine mysub01

program contained_in_sub
  implicit none
  real val
  val = 5.0
  call mysub01(val)
  print *, val
end program contained_in_sub

出力例:
   6.0000000

モジュール内の手続(モジュール手続)についてはモジュールのセクションで説明します。

11.3 オプショナル引数

Fortran では省略可能な引数を持たせる事ができます。 省略可能な引数は特に内部手続やモジュール手続で簡単に利用することができます。 ※ 外部手続で利用する場合には別途 INTERFACE(引用仕様宣言)が必要となります。 (引用仕様宣言についてはここでは説明しません。)

省略可能な引数を利用するには仮引数の宣言で以下のように OPTIONAL 属性を指定します。

subroutine mysub(x,y,z)
  real x, y
  real,optional :: z   ! z がオプショナルであることを示す
  ...
end subroutine mysub

省略可能な引数を持つ手続の呼び出しは省略する引数を与えないことにより行います。 この時省略したい引数が「ある引数以降すべて」である場合には「ある引数」の直前までを与えてそれ以降を記述しない方法が利用可能です。

またもし省略したい引数が「ある引数以降すべて」ではなく引数並びの中にバラバラに存在するような場合には、キーワード引数(仮引数名 = 与える引数)により記述することができます。

例)
例えば下記のサブルーチン abc があった場合:
subroutine abc(x,y,z)
  real,optional :: x, y, z
  ...
end subroutine abc

呼び出し例
call abc()                ! すべて省略する例
call abc(1.0)             ! y, z を省略する例(y 以降すべてを省略)
call abc(1.0,2.0)         ! z のみを省略する例(z 以降すべてを省略)
call abc(x=1.0,z=3.0)     ! y のみを省略する例
call abc(z=3.0)           ! x, y を省略する例
call abc(1.0,z=3.0)       ! y のみを省略する例
call abc(z=3.0,x=1.0)     ! キーワードで引数を指定する場合順番は関係ない例

省略可能な引数が実際に省略されたかどうかをサブルーチンもしくは関数の中で知りたい場合には、組み込み関数 PRESENT を使用します。 例えば以下のように利用されます。

subroutine mysub(z)
  real,optional :: z
  if ( present(z) ) then
    ...
  else
    ...
  end if
  ...

以下にオプショナル引数と PRESENT の利用例を示します。

[ optional.f90 ] - オプショナル引数と PRESENT の使用を示すサンプル

program optional
  call mysub(1,2,3)   ! このように通常通りの利用も可能
  call mysub(1,z=3)   ! このような混在していても可能
  call mysub()        ! すべてを省略した例
  call mysub(z=3,y=2)
contains
  subroutine mysub(x,y,z)
    integer,optional :: x, y, z    ! optional 属性を付加する
    character*80 line
    if ( present(x) ) then         ! PRESENT の使用例
       print *, "x =", x
    else
       print *, "x is not present..."
    end if
    if ( present(y) ) then
       print *, "y =", y
    else
       print *, "y is not present..."
    end if
    if ( present(z) ) then
       print *, "z =", z
    else
       print *, "z is not present..."
    end if
    print *
  end subroutine
end program

出力例:
 x = 1
 y = 2
 z = 3

 x = 1
 y is not present...
 z = 3

 x is not present...
 y is not present...
 z is not present...

 x is not present...
 y = 2
 z = 3

11.4 引数授受特性

サブルーチンもしくは関数の引数は、入力引数、出力引数、入出力引数の3種類があります。 Fortran では、より安全なプログラミングが可能となるように、各引数の授受特性(入力、出力、入出力)を明示することができます。 この指定は省略可能ですが、コンパイラに人的誤りを発見してもらうことと、最適化を行うための追加情報として役立つため、その利用が強く推奨されています。

授受特性の明示は以下のように intent キーワードを用いて行います。

入力用引数:intent(in)
出力用引数:intent(out)
入出力引数:intent(inout)

[ intent.f90 ] - 授受特性(intent)の正しい使用を示すサンプル

program show_intent
  implicit none
  real x, y, z
  x = 1.0
  y = 2.0
  z = 3.0
  call mysub(x,y,z)
  print *, x, y, z
contains
  subroutine mysub(a,b,c)
    real,intent(in) :: a       ! a が入力引数であることを明示
    real,intent(out) :: b      ! b が出力引数であることを明示
    real,intent(inout) :: c    ! c が入出力引数であることを明示
    b = a * 10.0
    c = c + 10.0
  end subroutine
end program show_intent

出力例:
   1.0000000  10.0000000  13.0000000
[ intent-error.f90 ] - 授受特性(intent)の使用意義を示すサンプル1(エラーになる例)
program intent_error
  implicit none
  real x, y
  x = 1.0
  y = 2.0
  call mysub(x,y)
  print *, x, y
contains
  subroutine mysub(a,b)
    real,intent(in) :: a
    real,intent(out) :: b
    a = 5.0                    ! Error - 入力引数であるのに値が設定された
    print *, b                 ! 警告  - 値設定前に参照が行われた
  end subroutine
end program intent_error

[ intent-optimization.f90 ] - 授受特性(intent)の使用意義を示すサンプル2(パフォーマンス比較例)

program intent_optimization
  implicit none
  integer, parameter :: n = 200
  integer i
  real t1, t2
  real, dimension (n,n*2,n) :: dst
  real, dimension (n*2,n,n) :: src

  src = 99

  call cpu_time(t1)
  do i = 1, 10
    call mycopy_with_intent(src(::2,:,:),dst(:,::2,:),i)
  end do
  call cpu_time(t2)
  print *, 'with intent:', t2 - t1

  call cpu_time(t1)
  do i = 1, 10
    call mycopy_without_intent(src(::2,:,:),dst(:,::2,:),i)
  end do
  call cpu_time(t2)
  print *, 'without intent:', t2 - t1

contains
  subroutine mycopy_with_intent(src,dst,i)
    real, dimension (n,n,n), intent (in) :: src  ! サブルーチン復帰時のコピーが必要なくなる
    real, dimension (n,n,n), intent (out) :: dst ! サブルーチン呼び出し時のコピーが必要なくなる
    integer i
    dst = src + i
  end subroutine mycopy_with_intent

  subroutine mycopy_without_intent(src,dst,i)
    real, dimension (n,n,n) :: src, dst
    integer i
    dst = src + i
  end subroutine mycopy_without_intent
end program intent_optimization

出力例:
 with intent:   2.1684139
 without intent:   3.2604210

11.5 ★ 練習課題:消費カロリーの計算

体重(kg)とジョギングを行った時間(分)を入力し、消費カロリーを計算するプログラムを作成して下さい。 この際の消費カロリーを計算は自作の関数で行うものとします。 また消費カロリーの計算は以下の式を用いて計算するものとします。
  消費カロリー(kcal)= 体重(kg)× 走った時間(時間)× 8

処理手順例

  1. 変数を宣言する
     例)
     real kg, minute
  2. 体重を入力してほしい旨を画面に出力する(print 文)
  3. 体重をキーボードから入力する(read 文)
  4. ジョギング時間(分)を入力してほしい旨を画面に出力する(print 文)
  5. ジョギング時間をキーボードから入力する(read 文)
  6. カロリーを出力する。 その際にカロリー計算をする関数を呼びだす(カロリー計算の関数も定義する)
     例)
     print *, calc_kcal(kg, minute)
実行例:
 Please Enter TAIJU(kg):
55
 Please Enter Jogging Duration(minutes):
10
 Congulatulations!   You have just burned  73.3333359 kcal!
[ kadai-kcal.f90 ] - 消費カロリーを計算するプログラム例


前へ   上へ   次へ
大学生協限定キャンペーン情報
ご案内
関連情報
© 日本ニューメリカルアルゴリズムズグループ株式会社 2025
Privacy Policy  /  Trademarks