[Fortranのはじめ方] Part13: SUBROUTINEとCALL文

Fortran

はじめに:サブルーチンとは? 🤔

プログラムを書いていると、「この一連の処理、さっきも書いたな…」「ここ、もう少しスッキリさせたいな」と感じることがあります。そんな時に役立つのがサブルーチン (Subroutine) です!

サブルーチンは、特定のまとまった処理に名前をつけて、プログラムの他の場所から呼び出して使うことができる機能です。同じ処理を何度も書く必要がなくなり、プログラム全体が読みやすく、修正しやすくなります。まさに、プログラムの部品化ですね! 🧩

このセクションでは、サブルーチンの基本的な作り方 (SUBROUTINE) と、その呼び出し方 (CALL文) について学んでいきましょう。

SUBROUTINEの基本的な書き方 ✍️

サブルーチンは SUBROUTINE キーワードで始まり、END SUBROUTINE で終わります。基本的な構造は以下のようになります。


SUBROUTINE subroutine_name ( argument1, argument2, ... )
  IMPLICIT NONE ! 型宣言を強制するおまじない

  ! --- 引数の型宣言 ---
  [型名], INTENT([属性]) :: argument1
  [型名], INTENT([属性]) :: argument2
  ...

  ! --- サブルーチン内で使う変数の宣言 ---
  [型名] :: local_variable1
  ...

  ! --- 実行したい処理 ---
  処理1
  処理2
  ...

END SUBROUTINE subroutine_name
      

重要なポイントは以下の通りです。

  • subroutine_name: サブルーチンの名前です。分かりやすい名前をつけましょう。
  • ( argument1, argument2, ... ): 仮引数(Dummy Argument) と呼ばれるもので、サブルーチンが外部から受け取る値や、外部に返す値の窓口となります。引数がない場合は () も省略できます。
  • IMPLICIT NONE: 変数を使う前に必ず型宣言をすることを強制します。バグを防ぐために必ず書くようにしましょう!
  • 引数の型宣言: 仮引数がどのような種類のデータ(整数、実数など)なのか、そしてサブルーチン内でどのように扱われるか(入力専用、出力専用、入出力両用)を INTENT 属性 で指定します。(INTENT については後ほど詳しく説明します。)
  • ローカル変数の宣言: サブルーチンの中だけで使う変数を宣言します。
  • 実行したい処理: このサブルーチンが行う具体的な計算や操作を記述します。
  • END SUBROUTINE subroutine_name: サブルーチンの終わりを示します。名前を省略することも可能ですが、対応が分かりやすいように書いておくことを推奨します。

簡単な例:挨拶を表示するサブルーチン

名前を受け取って、挨拶を表示する簡単なサブルーチンを見てみましょう。


SUBROUTINE print_greeting( name )
  IMPLICIT NONE
  CHARACTER(LEN=*), INTENT(IN) :: name ! 文字列型、入力専用

  WRITE(*,*) 'こんにちは、', TRIM(name), 'さん!' ! TRIM関数で前後の空白を除去

END SUBROUTINE print_greeting
      

このサブルーチンは name という名前の文字列を受け取り、画面に挨拶メッセージを出力します。CHARACTER(LEN=*) は任意の長さの文字列を受け取れることを意味し、INTENT(IN) は受け取った name の値をサブルーチン内で変更しない(入力専用である)ことを示しています。

CALL文によるサブルーチンの呼び出し 📞

作成したサブルーチンを使うには、プログラムの本体や他のサブルーチン、関数から CALL を使って呼び出します。


CALL subroutine_name ( actual_argument1, actual_argument2, ... )
      

CALL の後に呼び出したいサブルーチン名を書き、() の中に 実引数(Actual Argument) を指定します。実引数は、サブルーチン定義の仮引数に対応する具体的な値や変数です。

例:先の挨拶サブルーチンを呼び出す

先ほど作成した print_greeting サブルーチンを呼び出すメインプログラムは次のようになります。


PROGRAM main_program
  IMPLICIT NONE
  CHARACTER(LEN=20) :: user_name

  user_name = 'Fortran学習者'

  ! サブルーチン print_greeting を呼び出す
  CALL print_greeting( user_name )

  user_name = '田中 太郎'
  CALL print_greeting( user_name )

CONTAINS ! メインプログラム内にサブルーチンや関数を定義する場合

  ! --- ここにサブルーチンの定義を置く ---
  SUBROUTINE print_greeting( name )
    IMPLICIT NONE
    CHARACTER(LEN=*), INTENT(IN) :: name

    WRITE(*,*) 'こんにちは、', TRIM(name), 'さん!'

  END SUBROUTINE print_greeting

END PROGRAM main_program
      

このプログラムを実行すると、以下の出力が得られます。


 こんにちは、Fortran学習者さん!
 こんにちは、田中 太郎さん!
      

このように、CALL 文を使うことで、print_greeting という処理を必要な場所で簡単に再利用できます。

💡 Tips: サブルーチンは、メインプログラムの CONTAINS 文の後、または モジュール 内に定義するのが一般的です。(モジュールについては後のステップで学びます。)

引数の受け渡しと INTENT 属性 🤝

サブルーチンで引数を扱う際に非常に重要なのが INTENT 属性 です。これは、その引数がサブルーチンにとってどのような役割を持つかを明確に示すものです。

Fortran では、デフォルトで引数は 参照渡し(Pass by Reference) のように動作することが多いです。これは、サブルーチン内で仮引数の値を変更すると、呼び出し元の実引数(変数)の値も変更されてしまう可能性があることを意味します。意図しない値の変更はバグの原因となるため、INTENT 属性を使って引数の役割を明示することが強く推奨されます。

INTENT 属性には主に以下の3種類があります。

INTENT属性意味説明注意点
INTENT(IN)入力 (Input)サブルーチンはこの引数の値を参照するだけで、変更することはできません。値を変更しようとするとコンパイルエラーになります。安全! ✅
INTENT(OUT)出力 (Output)サブルーチンはこの引数に値を設定(代入)して、結果を呼び出し元に返します。サブルーチンに入った時点での値は未定義(不定)となります。サブルーチン内で必ず値を代入する必要があります。呼び出し前の値は期待できません。
INTENT(INOUT)入出力 (Input/Output)サブルーチンはこの引数の値を参照することも、変更することもできます。変更した値は呼び出し元に反映されます。値が変更されることを意識して使う必要があります。
(指定なし)(デフォルト)INTENT(INOUT) と同様の振る舞いをすることが多いですが、コンパイラや状況によって異なる可能性もあります。非推奨! 意図しない動作を防ぐため、必ず INTENT を指定しましょう。

INTENT 属性を使った例:値の交換

2つの整数値を交換するサブルーチンを考えてみましょう。この場合、両方の引数を読み取り、かつ変更する必要があるため INTENT(INOUT) を使います。


PROGRAM swap_example
  IMPLICIT NONE
  INTEGER :: a, b

  a = 10
  b = 20
  WRITE(*,*) '交換前: a = ', a, ', b = ', b

  CALL swap_integers(a, b)

  WRITE(*,*) '交換後: a = ', a, ', b = ', b

CONTAINS

  SUBROUTINE swap_integers( x, y )
    IMPLICIT NONE
    INTEGER, INTENT(INOUT) :: x, y ! 入出力両用
    INTEGER :: temp ! 一時的な保管用変数

    ! 値を交換する処理
    temp = x
    x = y
    y = temp

  END SUBROUTINE swap_integers

END PROGRAM swap_example
      

実行結果:


 交換前: a =           10 , b =           20
 交換後: a =           20 , b =           10
      

INTENT(INOUT) を指定したことで、サブルーチン内での xy の変更が、呼び出し元の変数 ab に正しく反映されていることがわかります。もしここで INTENT(IN) を指定していたら、コンパイルエラーになります。もし INTENT(OUT) を指定していたら、交換前の値 (10, 20) を参照できず、意図した動作になりません。

このように、INTENT 属性はプログラムの意図を明確にし、安全性を高めるために不可欠な機能です。積極的に活用しましょう! ✨

サブルーチンを使うメリット 🎉

サブルーチンを使うことには、たくさんのメリットがあります。

  • コードの再利用性向上: 同じ処理を何度も書く必要がなくなり、一度書いたサブルーチンを必要な場所で呼び出すだけで済みます。
  • 可読性の向上: 複雑な処理をサブルーチンとしてまとめることで、プログラム全体の流れが分かりやすくなります。「何をしたいのか」が名前で表現されるため、読みやすくなります。
  • 保守性の向上: 特定の処理に変更が必要になった場合、そのサブルーチンだけを修正すれば良いため、修正箇所が限定され、デバッグや機能追加が容易になります。
  • モジュール化: 関連する処理をサブルーチンとしてまとめることで、プログラムを機能ごとに部品化(モジュール化)できます。

大規模なプログラム開発はもちろん、比較的小さなプログラムでも、サブルーチンを効果的に使うことで、より品質の高いコードを書くことができます。

まとめと次のステップ 🚀

今回は、Fortran における処理の部品化の基本である SUBROUTINE と、それを呼び出すための CALL について学びました。

特に、引数の役割を明確にし、プログラムの安全性を高める INTENT 属性 の重要性を理解できたでしょうか? INTENT(IN), INTENT(OUT), INTENT(INOUT) を適切に使い分けることが、バグの少ない、読みやすいコードを書くための鍵となります。

サブルーチンは値を返すのではなく、引数を通して結果を返したり、何らかの操作(画面出力など)を行ったりするのに使われます。

次のステップでは、サブルーチンと似ていますが、計算結果として一つの値を返すことに特化した FUNCTION(関数) について学んでいきます。サブルーチンと関数の違いを理解し、適切に使い分けられるようになりましょう!

参考情報 📚

コメント

タイトルとURLをコピーしました