天天看點

【Fortran】過程設計之一(子例程SUBROUTINE)

目錄

    • 前言
    • Fortran子例程(SUBROUTINE)
      • 1) 使用方式
      • 2) 子例程示意
      • 3) INTENT屬性
      • 4) 傳遞數組給子例程
      • 4) 傳遞可配置設定數組給子例程
      • 5) 傳遞字元變量給子例程
      • 6) 子例程作為參數傳遞
      • 7) 其它注意事項

前言

程式單元包括主程式、子例程、子產品、函數子程式。

在Fortran中,大型程式可以拆分成多個獨立運作和調試的子任務,即程式單元(亦稱為外部過程)。

Fortran中有兩種外部過程:子例程和函數子程式。這種機制的優點是:

  • 子任務單獨測試,互相之間不影響;
  • 避免重複造輪子,調用即可;
  • 将實作某一功能的代碼封裝起來,避免不經意修改導緻代碼錯誤而不自知。

Fortran子例程(SUBROUTINE)

1) 使用方式

子例程(亦可稱為子程式)是一個Fortran過程,通過

CALL

語句進行調用,并通過參數表擷取輸入數值和傳回結果。

定義語句格式:

SUBROUTINE subroutine_name( argument_list_dum )   ! 定義子例程名和相關參數表
	(聲明部分)
	...
	(執行部分)
	...
RETURN
END SUBROUTINE [ subroutine_name ]  ! []表示可選
           

注意事項:

  • subroutine_name

    由字母、數字和下劃線組成 ,最大長度可達63個字元,第一個字元為字母;
  • argument_list_dum

    ,形參,一系列變量和/或數組,從調用程式傳遞給子例程;
  • 子例程是一個獨立的程式單元,開始于

    SUBROUTINE

    ,結束于

    END SUBROUTINE

    ,其中的局部變量名和語句标号(“行号”)可以在其它地方複用(不用擔心重名);
  • 實際上沒有給形參配置設定記憶體。

調用語句格式:

CALL subroutine_name( argument_list_act )

注意事項:

  • 任何可執行程式單元都可以調用子例程,但不能調用自身(除非定義為遞歸類型);
  • argument_list_act

    ,實參。實參的個數、順序與類型必須和形參的個數、順序與類型相比對。
  • 主程式和子例程之間采用位址傳遞進行參數傳遞,具體過程是:由于主程式中的實參有具體的記憶體存儲位置,當調用子例程時,主程式将生成多個指針來指向各個實參所對應的存儲位置,并将指針傳遞給子例程,子例程調用的是參數的記憶體位置,而非實參資料本身。

2) 子例程示意

已知三角形的兩條直邊,計算斜邊。要求計算過程用子例程,主程式直接輸入相應資料後直接調用。

PROGRAM calc_hypotenuse_test   ! 主程式
IMPLICIT NONE
REAL :: s1
REAL :: s2
REAL :: hypot

WRITE(*,*) '測試計算斜邊的子例程'
WRITE(*,*)'輸入第一條直邊的長度:'
READ(*,*) s1
WRITE(*,*)'輸入第二條直邊的長度:'
READ(*,*) s2

CALL calc_hypotenuse( s1, s2 , hypot )   ! 調用子例程

WRITE( *, 100 ) hypot
100 FORMAT('斜邊長度為:' , F10.4)

STOP
END PROGRAM calc_hypotenuse_test
           
SUBROUTINE calc_hypotenuse(side_1 , side_2 , hypotenuse)  ! 子例程
IMPLICIT NONE
REAL , INTENT(IN)::side_1    ! 第一條直邊長度,輸入,INTENT用法見下述
REAL , INTENT(IN)::side_2    ! 第二條直邊長度,輸入
REAL , INTENT(OUT)::hypotenuse   ! 斜邊長度,輸出

REAL::temp    ! 聲明局部變量
temp = side_1**2 + side_2**2
hypotenuse = SQRT( temp )
END SUBROUTINE calc_hypotenuse
           

3) INTENT屬性

INTENT

屬性在子例程的形參聲明時使用。

INTENT

屬性的格式如下:

  • INTENT(IN)

    ,形參僅用于向子程式傳遞輸入資料;
  • INTENT(OUT)

    ,形參僅用于将結果傳回給調用程式;
  • INTENT(INOUT)

    INTENT(IN OUT)

    ,形參既用來向子程式輸入資料,也用來向調用程式傳回結果。

屬性特點:

  • 對于每一個形參來說,都應該聲明一個合适的

    INTENT

    屬性;
  • INTENT

    屬性僅對過程的形參有效,如果用來聲明子例程的局部變量或主程式的變量則會出錯;
  • 對于每一個過程,都應該聲明每一個形參的

    INTENT

    屬性。形參的

    INTENT

    屬性也可以用獨立的語句來聲明,如:

    INTENT(IN) :: arg1 , arg2,...

4) 傳遞數組給子例程

如前所述,調用參數實際上是通過傳遞指向該實參的記憶體位置指針來傳遞給子例程。對于實參是一個數組,其指針是指向數組中的第一個值。然而,子例程需要同時知道數組的位址和大小,保證不會發生越界,才能進行數組操作。

在子例程中有三種方式來指明形參數組的大小:

  1. 顯式結構形參數組

    數組的次元大小需要作為參數進行傳遞,一維數組為例:

    SUBROUTINE process(data1 , data2 , n , nvals)
    INTEGER , INTENT(IN) :: n , nvals           ! n為數組的大小, nvals是數組操作的個數
    REAL , INTENT(IN) , DIMENSION(n)::data1   
    REAL , INTENT(OUT) , DIMENSION(n)::data2  
    INTEGER::i
    
    DO i = 1 , nvals
        data2( i ) = 3.*data1( i )   ! 将data1數組的數值乘以3,指派為data2數組
    END DO
    
    END SUBROUTINE process
               
    二維數組為例:
    SUBROUTINE process1(data1 , data2 , m , n)
    INTEGER , INTENT(IN) :: m ,n           ! m×n為數組的大小
    REAL , INTENT(IN) , DIMENSION(m,n)::data1   
    REAL , INTENT(OUT) , DIMENSION(m,n)::data2  
    
    data2 = 3.*data1   ! 将data1數組的數值乘以3,指派為data2數組,直接對數組進行操作
    
    END SUBROUTINE process1
               
    由于形參數組的大小和結構都已經清晰,可以對形參數組進行數組操作,以及切片操作。
  2. 不定結構形參數組

    把子例程中的所有形參數組聲明為不定結構(數組的每個下标用

    :

    來代替)的形參數組,隻有當子例程具有顯式接口時,才能使用這種數組。是以,顯示接口能夠給編譯器提供每個數組的大小、結構等詳細資訊,在調用的時候不會出錯。

    需注意的是,定義形參數組時隻有它的結構(但用

    :

    替代),沒有具體下标範圍。是以,在把實參數組傳遞至形參數組時,隻傳遞了結構,并沒有傳遞實參數組每個次元的下标取值範圍。此時,可以用查詢函數擷取不定結構數組的結構。

    如果不需要将數組的每個次元下标邊界從調用程式傳遞給子例程,則不定結構形參數組比顯式結構形參數組更友善使用。 二維數組的例子:

    MODULE module_process
    CONTAINS
    	SUBROUTINE process2(data1 , data2 )
    	REAL , INTENT(IN) , DIMENSION(:,:)::data1   
    	REAL , INTENT(OUT) , DIMENSION(:,:)::data2  
    
    	data2 = 3.*data1   ! 将data1數組的數值乘以3,指派為data2數組,直接對數組進行操作
    
    	END SUBROUTINE process2
    END MODULE module_process
               
  3. 不定大小形參數組

    古老且過時的方法,用星号

    *

    來聲明形參數組的長度,表示大小不确定。是以不清楚數組的實際大小和結構,容易運作錯誤,且很難調試,建議不要使用。例子如下:
    SUBROUTINE process(data1 , data2 , nvals)
    INTEGER , INTENT(IN) ::  nvals           ! nvals是數組操作的個數,是以數組的資料個數至少要為nvals
    REAL , INTENT(IN) , DIMENSION(*)::data1   ! 不定大小
    REAL , INTENT(OUT) , DIMENSION(*)::data2    ! 不定大小
    INTEGER::i
    	
    DO i = 1 , nvals
        data2( i ) = 3.*data1( i )   ! 将data1數組的元素乘以3,指派為data2數組
    END DO
    
    END SUBROUTINE process
               

4) 傳遞可配置設定數組給子例程

可配置設定數組作為參數傳遞給子例程時,必須要結合顯式接口。

注意事項:

  • 當傳遞的參數是可配置設定數組時,子例程中形參聲明 和 調用子程式的實參聲明必須都是可配置設定的;
  • 可配置設定形參可以使用

    INTENT

    屬性,但

    INTENT

    屬性的具體參數可能會影響子例程中的操作:
    • INTENT(IN)

      ,在子例程中不允許對輸入可配置設定數組進行重配置設定或者釋放記憶體空間;
    • INTENT(INOUT)

      ,調用子例程時,如果參數隻有數組這一個,那麼實際上會将數組的狀态(是否可配置設定)和相應資料傳遞至子例程中(實參——>形參),在子例程中可以對形參修改、釋放記憶體、重配置設定等操作。形參的最終狀态和資料傳回至調用程式中(相應實參結果被修改了)。具體見下例;
    • INTENT(OUT)

      ,調用子例程時,實參在入口處被自動釋放掉,相應的資料清除掉,可以在子例程中對其進行操作,然後将形參的最終狀态和資料傳回至調用程式中。

例子:

取自《Fortran for Scientists and Engineers(4th) by Stephen J. Chapman》中的9-5例題,有少量修改,複制可運作。

PROGRAM test_allocatable_arguments   ! 主程式

USE test_module   ! 先調用子產品
IMPLICIT NONE

REAL,ALLOCATABLE,DIMENSION(:) :: a_main ! 定義舊版的可配置設定數組.
INTEGER :: istat         ! 配置設定的狀态
                     ! 指定大小,配置設定
ALLOCATE( a_main(6), STAT=istat )
                     ! 初始化數組
a_main = [ 1., 2., 3., 4., 5., 6. ]
                     ! 輸出調用前主程式中可配置設定數組
WRITE (*,'(A,6F4.1)') ' 調用子程式前主程式中的數組:', a_main
                     ! 再調用子例程
CALL test_allocate(a_main)
                     ! 輸出調用後主程式中可配置設定數組
WRITE (*,'(A,6F4.1)') '調用子程式後主程式中的數組: ', a_main
END PROGRAM test_allocatable_arguments
           
MODULE test_module   ! 顯式接口
    CONTAINS
    SUBROUTINE test_allocate(array)  ! 子例程
        IMPLICIT NONE
                         ! 過程中形參變量       
        REAL,DIMENSION(:),ALLOCATABLE,INTENT(INOUT) :: array   ! 作為形參的可配置設定一維數組,這是屬于自動配置設定記憶體的定義

                          ! 過程中局部變量
        INTEGER :: i                       ! 循環下标
        INTEGER :: istat                   ! 配置設定的狀态
                                       ! 判斷數組的狀态
        IF ( ALLOCATED(array) ) THEN
        WRITE (*,'(A)') '子程式配置設定成功!'
        WRITE (*,'(A,6F4.1)') '子程式輸入為: ', array
        ELSE
        WRITE (*,*) '子程式沒有被配置設定'
        END IF
                             ! 釋放可配置設定數組的記憶體(因為在聲明部分已經配置設定好了)
        IF ( ALLOCATED(array) ) THEN
        DEALLOCATE( array, STAT=istat )
        END IF
                             ! 重新配置設定,按照5個元素的一維數組
        ALLOCATE(array(5), STAT=istat )
                             ! 往重配置設定的一維數組填入資料
        DO i = 1, 5
        array(i) = 6 - i
        END DO
                             ! 展示重配置設定後的數組結果
        WRITE (*,'(A,6F4.1)') '子程式中輸出的數組 ', array
                          
    END SUBROUTINE test_allocate
END MODULE test_module
           

相應的結果為:

調用子程式前主程式中的數組: 1.0 2.0 3.0 4.0 5.0 6.0
子程式配置設定成功!
子程式輸入為:  1.0 2.0 3.0 4.0 5.0 6.0
子程式中輸出的數組  5.0 4.0 3.0 2.0 1.0
調用子程式後主程式中的數組:  5.0 4.0 3.0 2.0 1.0
           

5) 傳遞字元變量給子例程

當一個字元變量被作為子例程的形參時,用

*

号來聲明字元變量的長度。當調用子例程時,形參的長度将是實參的長度。如:

SUBROUTINE example( string )
CHARACTER( len = * ) , INTENT(IN) :: string   ! *号表示完全複制實參的長度
WRITE(*,*) 'The lengrh of string : ',LEN(string)   ! 可以在子例程内部實時傳回實參的長度
END SUBROUTINE example   
           

如自動數組一般,建立自動字元變量:

SUBROUTINE sample ( string )
CHARACTER(len=*) :: string
CHARACTER(len=len(string)) :: temp   ! 一個與形參相同大小的臨時變量,子例程調用結束時會被銷毀
           

6) 子例程作為參數傳遞

當子例程

a

作為實參時,傳遞給過程

A

(如另一子例程)一個指向該子例程

a

的指針。執行該過程

A

時,參數表中的子例程

a

将作為形參進入到過程

A

的編譯當中。

要想實作子例程傳遞功能,必須要使用

EXTERNAL

屬性,将子例程聲明為外部,此時編譯器才會知道參數表中傳遞的是獨立的已編譯子例程,而不是正常變量。

EXTERNAL

屬性需要在聲明部分中使用,格式如下:

TYPE, EXTERNAL ::sub_1 , sub_2  !  TYPE是指具體資料類型
           

或者

EXTERNAL ::sub_1 , sub_2
           

同時,在過程中需要結合

CALL

語句,以調用過程中子例程形參。

7) 其它注意事項

  • 不要在子例程中使用

    STOP

    語句,因為一旦調用子例程時,會停止程式。如果調用多個子例程(每個子例程都有

    STOP

    語句),則程式永遠不會執行成功;
  • 如果子例程中存在可能引發錯誤的條件,應該對錯誤進行檢測,并設定正确/錯誤标志,傳回給調用程式(即作為形參其中之一),用于實時判斷預設條件的執行成功與否(即可定義為一種狀态用于判斷)。

繼續閱讀