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
処理手順例
-
変数を宣言する
例)
real kg, minute - 体重を入力してほしい旨を画面に出力する(print 文)
- 体重をキーボードから入力する(read 文)
- ジョギング時間(分)を入力してほしい旨を画面に出力する(print 文)
- ジョギング時間をキーボードから入力する(read 文)
-
カロリーを出力する。
その際にカロリー計算をする関数を呼びだす(カロリー計算の関数も定義する)
例)
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 ] - 消費カロリーを計算するプログラム例
前へ 上へ 次へ