9. 構造型,ポインタ,モジュール 章全体が [90] 目次へ 9.1. 構造型 9.1.1. 構造型の定義と宣言 9.1.2. 構造型の演算と代入 9.2. ポインタ 9.2.1. ポインタ変数 9.2.2. ポインタ演算 9.2.3. ポインタ配列 9.2.4. ALLOCATE文 9.2.5. 結合状態の調査と解除 9.2.6. ポインタ引数 9.3. モジュール 9.3.1. 型宣言の共通化 9.3.2. 手続きモジュール 9.3.3. ユーザ定義演算子 9.3.4. ユーザ定義代入文 この章では [90]で新たに拡張された,やや高度な使い方である上記の3つの機能に ついて解説する。 9.1. 構造型 [90] システムに用意されているデータの型は,整数型,実数型,複素数型,文字型,論理 型(およびそれぞれの型のいくつかの精度の異なる型)であるが,[90]では複数の型の データから構成される複合データを定義することができる。配列と大きく異なる点は, 構成成分の型が同じでなくてもよいことである。その代償として,配列の並列演算のよ うな構造型変数名のままの演算式は原理的に許されていない。これは,モジュールを使 ってユーザが定義することになる。(→9.3.3.) 9.1.1. 構造型の定義と型宣言 構造型宣言に先立ち,TYPE構文 により型の定義を行う必要がある: TYPE 構造型名 型 :: 成分名 型 :: 成分名 ..... END TYPE [構造型名] 構造型の成分として構造型が含まれていてもよい。このあとTYPE文により,これを用い る変数名の型宣言を行う。 TYPE(構造型名) :: 変数名や配列名のリスト 例1 3次元ベクトル TYPE vector REAL :: x, y, z END TYPE vector TYPE(vector) :: a, b,c 例2 三角形(構造型を成分にもつ構造型) TYPE triangle TYPE(vector) :: p1, p2, p3 END TYPE triangle TYPE(triangle) :: t 例3 成績リスト(構造型を要素に持つ配列) TYPE mark_list INTEGER :: number CHARACTER(LEN=12) :: name INTEGER :: ent_year CHARACTER(LEN=3) :: class INTEGER :: id, mark END TYPE mark_list TYPE(mark_list) :: physics(1:456) これで以下のようなデータ成分のセットを要素とする配列を定義できる。 number name ent_year class id mark 1 山田花子 97 H3 1234 98 2 鈴木一朗 97 S1 5678 65 ..... 456 橋本一太郎 95 J1 0123 25 例4 5科目の成績表(配列を成分にもつ構造型) TYPE kamoku_seiseki INTEGER :: number CHARACTER(LEN=12) :: name INTEGER :: mark(1:5) END TYPE kamoku_seiseki TYPE(kamoku_seiseki) :: pmejh(1:456) (データ例) Phys. Math. Eng. Jpn. His. 1 山田花子 98 100 85 80 75 2 鈴木一朗 65 35 80 75 60 ....... 456 橋本一太郎 25 40 95 90 85 9.1.2. 構造型の演算と代入 成分の表し方 構造型変数名%成分名 例1 ベクトルaのx,y,z成分は,a%x, a%y, a%z 例2 三角形tの第一頂点の位置ベクトル t%p1 のx成分は t%p1%x 例3 physics(2)%name( physics%name(2) ではない)が '鈴木一朗', physics(2)%mark が 65 例4 pmejh(2)%name が'鈴木一朗',pmejh(1)%mark(2) が 100 演算 この成分名を用いて書き,構造型変数名のままの演算は書けない。 例2 残念ながら,三角形 t の重心ベクトル TYPE(vector) :: g を g = (t%p1 + t%p2 + t%p3)/3.0 と書くことはできず, g%x = (t%p1%x + t%p2%x + t%p3%x)/3.0 etc としなければならない。(→「ユーザ定義演算/代入」9.3.3., 9.3.4.) 代入 成分名に対して対応する型の個々のデータを代入できるほかに,同じ型の構造 型であれば,構造型変数名のまま代入文を書くことができる。 例1 b = a 注)b = - a は演算を含むから,許されない。 例2 t%p1 = a 定数 TYPE構文で定義された順に成分の値(対応する型の定数や変数名)を,以下の 形で並べたものを代入文などで使うことができる: 構造型名(成分の値,....) 例2 t%p1 = vector( a%x, b%y, c%z) 例3 physics(1) = mark_list( 1,'山田花子', 97, 'H3', 1234, 98) 例4 pmejh(2) = kamoku_seiseki( 2,'鈴木一朗', (/ 25,40,95,90,85 /) ) 注)3番目の成分は「配列定数」 入出力リスト個々の成分名または,まとめて構造型変数名を書くことができる。 例題 9_1 「2つのベクトルのスカラー積とベクトル積を求める。」[ex9_1.f90] ! --- Scalar and Vector Products of Two vectors --- PROGRAM main TYPE vector REAL :: x, y, z END TYPE vector TYPE(vector) :: a, b, c REAL :: s PRINT*,"Input 3 components of the first vector:" READ*, a PRINT*,"Input 3 components of the second vector:" READ*, b s = a%x * b%x + a%y * b%y + a%z * b%z ! s = a*b とは書けない PRINT "('a.b = ', F6.3)", s c = vector_product(a,b) PRINT "('a^b = (', F6.3, ',', F6.3, ',' F6.3, ')' )", c ! CONTAINS FUNCTION vector_product( u, v) RESULT(w) TYPE(vector) :: u, v, w w%x = u%y * v%z - u%z * v%y w%y = u%z * v%x - u%x * v%z w%z = u%x * v%y - u%y * v%x END FUNCTION END
(実行例) Input 3 components of the first vector: 1.0 2.0 3.0 Input 3 components of the second vector: 2.0 3.0 4.0 a.b = 20.000 a^b = (-1.000, 2.000,-1.000) 注)成分の型が同じであるこのような例は,配列で書くことが可能である。 例題 9_2 「名前と5科目の成績を入力して例4の成績表を作成する。」[ex9_2.f90] ! --- 5科目成績表 --- TYPE kamoku_seiseki INTEGER :: number CHARACTER(LEN=12) :: name INTEGER :: mark(1:5) END TYPE kamoku_seiseki INTEGER :: n, i = 0 CHARACTER(LEN=1) :: answer TYPE(kamoku_seiseki) :: s5 ! PRINT*, "学生数は?"; READ*, n OPEN(8, FILE = 'h3.txt') DO WHILE( i < n ) PRINT*, "学生の名前:" ; READ "(A)", s5%name PRINT*, "5科目の点数:"; READ *, s5%mark(:) ! 配列 PRINT "(A12, 5I4)", s5%name, s5%mark(:) PRINT*, "確認(Empty or N)"; READ "(A)", answer IF( answer == ' ') THEN i = i + 1; s5%number = i WRITE(8, '(I5, 1X, A12, 5I4)' ) s5 END IF END DO CLOSE(8) END
9.2. ポインタ [90] 変数や配列などの記憶領域を「指す」変数としてポインタが用意されている。単独の 変数を指すだけなら大して有難みがないが,動的に大きさが変化する部分配列やリスト 構造と呼ばれるデータなど,柔軟なデータ構造を扱う場合に役立つ。ポインタの意味を 正確に理解するために,冗長になるかもしれないがトリビアルな例をいくつか繰返す。 9.2.1. ポインタ変数 ポインタ変数は,POINTER属性を指定した宣言文で宣言する: 型,POINTER :: ポインタ変数名または配列名,... ポインタが指すことができるのは 1) ポインタと同じ型で,次の形で TARGET属性 を指定された変数または配列 型,TARGET :: 変数名または配列名,... 2) ALLOCATE文で割り当てられる無名の記憶領域(→9.2.3.) 9.2.2. ポインタ演算 ポインタ代入文 同じ型のポインタとターゲットは,次の式で関係づけられて始めて結合する: ポインタ名 => ターゲット名(またはポインタ名) 例 REAL, POINTER :: p REAL, TARGET :: x p => x ポインタ変数 p に x の番地 (x の値が記録されている記憶領域のアドレス値)が 代入される。これを以後では「 p は x を指す」と言う。右辺が別のポインタの場合 は,右辺のポインタに代入されている番地(ポインタ変数自身の番地ではない)が, 左辺のポインタ変数に代入される。 p => x ---------- ---------- | @x | 番地 @x | x の値 | ---------- ---------- ポインタ p 変数 x ポインタを含む式と代入文 ポインタはターゲット変数と結合しておれば,その変数を書くのと全く同じ意味,す なわち「別名」として,式や普通の代入文などに書くことができる。この意味で用いら れる普通の代入文とポインタ代入文を明確に区別して理解しておく必要がある。 例 REAL, POINTER :: p, q REAL, TARGET :: x, y p => x ; q => y x = 1.0; y = 2.0 のとき PRINT *, p ...「p の指している変数の値を出力せよ。」PRINT*, x と 同じで,1.0 が出力される。 y = p + q ... y = x + y と同じで, y は 3.0 となる。 PRINT*, q ... もちろん「q の指す変数」の値も 3.0 となっている。 次に p = q は「q の指している変数の値を,p の指している変数に代入せよ。」という 普通の意味の代入文であり,x = y と同じで,x の値が 3.0 となる。また, p = 4.0 も普通の意味の代入文であって「4.0 を,p の指している変数に代入せよ。」 であり,「ポインタ p の値が 4.0」 になるのではない。これにより PRINT*, p ... 4.0 が出力される。(p の指す変数 x の値 4.0 の出力) PRINT*, x ... 4.0 が出力される。 次にポインタ代入文 p => q とすると,「ポインタ p を,q が指している変数を指すことにせよ。」である から, PRINT*, p ... 3.0 が出力される。 PRINT*, x ... 4.0 のままである。( p には見離されてしまったから。) このようにポインタは,=> でターゲットに結合している間は,ターゲットの 「別名」と考えればよい。別名は,次に別のポインタ代入式 => の左辺に現れ たときに無効となり,新しいターゲットの別名に変わる。最後に p => 5.0 には,「p を,定数 5.0 を指すことにせよ。」という意味を持たすことは できず,コンパイルエラーとなる。 9.2.3. ポインタ配列 ポインタ配列は,同じ型で同じ次元数(ランク)のターゲット配列と結合させること ができる。この場合,ポインタ宣言では配列の型と次元数だけ指定しておけばよく,各 次元の大きさ(寸法)を指定しておく必要はないので,次の例のように様々に大きさが 変化する配列を一つの名前で指すことができて便利である: 例 REAL, POINTER :: p1(:), p2(:,:) 寸法指定は不要,次元は必要 REAL, TARGET :: x(1:8,1:8) INTEGER :: i x = RESHAPE( (/ (i, i = 1, 64) /), SHAPE(x) ) p1 => x(1,:) p2 => x(1:4,1:4) 注)この場合,1行1列を取り出して,例えば x(3:3, 2:2) であっても,これは 配列要素 x(3,2) とは違い「1行1列の2次元配列」であることに注意せよ。 同じ意味で, x(1,:) は1次元配列であるが,x(1:1,:) は2次元配列である。 次の例題では,そのことが考慮されている。 例題 9_3 「与えられた2次元配列の部分配列を『窓』として定義する。」[ex9_3.f90] ! --- Window of 2d Array --- PROGRAM main INTEGER, POINTER :: window(:,:) ! 2次元配列 INTEGER, TARGET :: mat(1:8,1:8) INTEGER :: i, j, m, n, k mat = RESHAPE( (/ (i, i = 1, 64) /), SHAPE(mat) ) PRINT*,"第何行から何行まで?(1≦i≦j≦8)"; READ*, i, j PRINT*,"第何列から何列まで?(1≦m≦n≦8)"; READ*, m, n ! window => mat(i:j, m:n) DO k = 1, j-i+1 PRINT '(8I5)', window(k, :) END DO END
(実行例) 第何行から何行まで?(1≦i≦j≦8) 1 3 第何列から何列まで?(1≦m≦n≦8) 1 5 1 9 17 25 33 2 10 18 26 34 3 11 19 27 35 注)部分配列を出力するだけの目的なら,わざわざこんなことをする必要はない。 配列の動的割付け(→ 6.3.)を用いると,このプログラムは[ex9_3_2.f90] INTEGER, ALLOCATABLE :: partial(:,:) INTEGER :: mat(1:8,1:8) ............. ALLOCATE( partial(j-i+1, m-n+1) ) partial = mat(i:j, m:n) .............
と書くことができる。概念的には,後者が配列の一部分(窓)をコピーして新たな実体で ある配列 partial を作成しているのに対して,前者は単に窓部分の記憶領域を指し示 すだけで作業を進めているという違いがある。「指し示すだけ」とは言っても,指示先 の番地を実体として記録しておかねばならないから,結局,どちらが効率的あるいは 便利であるかは使用目的によるであろう。 9.2.4. ALLOCATE文 ポインタはALLOCATE文によって,無名の領域と結合させることができる。ただし, この場合は,ポインタ宣言文で ALLOCATABLE属性を指定する必要はない。 ALLOCATE(ポインタ変数名) 例 REAL, POINTER :: p ALLOCATE(p) INTEGER, POINTER :: p(:) ALLOCATABLE指定は不要 INTEGER :: n READ*, n ALLOCATE( p(n) ) 無名の領域と結合する 9.2.5. 結合状態の調査と解除 ポインタは宣言しただけでは未定義状態であり, (1) ポインタ代入文によりターゲット変数と (2) ALLOCATE文により,無名の領域と 結合する。結合状態の解除は,NULLIFY文 NULLIFY(ポインタ変数名,... ) による。また,上の例のように,新たに別のターゲット(またはポインタ)と結合した 場合には元のターゲットとの結合は解除される。ALLOCATEによる結合は,DEALLOCATE文 DEALLOCATE(ポインタ変数名,... ) で解除される。 ポインタが現在,結合しているかどうかは,ASSOCIATED関数で調べることができる。 ASSOCIATED(ポインタ [,ターゲットまたはポインタ]) ポインタがいずれかのターゲットと結合している場合に「真」,それ以外(DEALLOCATE 文またはNULLIFY文で結合が解除または空にされた状態)は「偽」を返す。第二引数が 指定されている場合,ターゲットまたは空でないポインタと結合しているときに「真」を返す。 例 INTEGER, POINTER :: p, q INTEGER, TARGET :: x, y p => x; NULLIFY(q) PRINT*, ASSOCIATED(p) → T x は未定義でもよい PRINT*, ASSOCIATED(q) → F q は空だから p => q PRINT*, ASSOCIATED(p) → F 結合している q が空だから PRINT*, ASSOCIATED(p,q) → F 同 上 q => y PRINT*, ASSOCIATED(p,y) → F 上で p は一たん空になっているから p => q PRINT*, ASSOCIATED(p,q) → T qが空でなくなったから PRINT*, ASSOCIARED(p,y) → T これで p は y を指したことに なる 例題 9_4 「ある科目の生徒の成績を次々に入力し,得点順のリスト構造データを 生成する。」 これはポインタ例題の定番である。リスト構造とは,1次元チェイン状につながった データ列であるが,似たような構造の1次元配列の場合,隣り合う要素が順番に並んで 記録されていくのに対して,リストでは,バラバラに記録されている個々のデータを, ただポインタで続きを指すことで繋いでいくだけである。また,配列では動的割付けを 使うにしても,配列を使用するまでに大きさが決まっていなければならないのに対して, リストでは動的にどんどん長さが増えていくような柔軟な構造を扱うことができる。 entrance ---> first --> second ..--> last -------- | -------- | -------- -------- | | | | name | | | name | | name | -------- | -------- | -------- -------- | | | | mark | | | mark | | mark | -------- | -------- | -------- -------- | next @ --> | next @ --> | next @ --> .. | NULL | -------- -------- -------- -------- ↑これが基本単位(@ はポインタ) 図のように,名前 name(文字型)と得点 mark(整数型)と次のデータを指すポイン タ next から成る構造型の基本ユニットを用いる。ポインタは次のユニットを指すのだ から,ユニットと同じ構造型を持っていなければならない。すなわち,「自分自身を成 分の型として含む構造型」になる。 簡単のため最初は,先頭のデータの前に新しいデータを付け加えていく場合を考え, この手続きを内部サブルーチンにしておく。あとでサブルーチンだけ書き換えればよい。[ex9_4_1.f90] ! --- 成績リスト --- PROGRAM usage_of_pointer TYPE unit ! 自分自身を成分にもつ構造型 CHARACTER(LEN=12) :: name INTEGER :: mark TYPE(unit), POINTER :: next END TYPE ! CHARACTER(LEN=12) :: namae INTEGER :: tensu TYPE(unit), POINTER :: ent, new, p ALLOCATE(ent); NULLIFY(ent%next) ! 最初は空にしておく DO PRINT*, "Input name (or [ENTER]-key to stop):" READ(*,'(A)') namae; IF( namae ==' ') EXIT PRINT*, "Input her or his mark:" READ*, tensu CALL rearrange END DO ! ! --- 出力 p => ent%next DO WHILE( ASSOCIATED(p) ) ! NULL となるまで続ける PRINT "(A12, I4)", p%name, p%mark p => p%next END DO ! CONTAINS SUBROUTINE rearrange ALLOCATE(new) ! (1) new = unit(namae, tensu, ent%next) ! (2) ent%next => new ! (3) END SUBROUTINE END
(1) ポインタ new が新しい無名領域を指すように準備しておき, (2) そこへ新しいデータを代入しておく。この unit構造をもつ新データの next 成分 (ポインタ)は,ent%next が指していた今までの先頭データを指すようにし, (3) 入り口 ent の next成分 ent%next が 新たに new(が指す領域)を指すように 変更すればよい。[これは,普通の代入文で「 ent%next = new 」と書くのと どう違うか考えてみよ。] さて,得点順のリストにするために,新しく読み込んだデータの入るべき位置を探し てチェインをつなぎ替える内部サブルーチンは以下のようになる。[ex9_4.f90] .......... .......... SUBROUTINE rearrange ALLOCATE(new) p => ent DO WHILE( ASSOCIATED(p%next)) ! (1)(3) IF( tensu > p%next%mark ) EXIT ! (2) p => p%next END DO new = unit(namae, tensu, p%next) ! (4) p%next => new ! (5) END SUBROUTINE
(1) まだ一つもデータがない最初の状態では p%next つまり ent%next は空(NULL) であり DOループは実行されない。 (2) 新しく入力された得点が既に高得点順に並んでいる既存の得点より大きくなる 最初の位置を順番に探していき,見つかればループを抜ける。 (3) 新しい得点が一番低くて終端まで達した場合でも,next 成分が空(ASSOCIATED が 偽)となり,探索ループは終了する。 (4) いずれの場合でもループを抜けた時点で,新しく入力された name および mark 成分データに,直前のユニットのポインタ成分 next を付け加えたもので新しい ユニットを構成し,これをポインタ new(が指す無名の領域)に代入する。 (5) 直前のユニットのポインタ next は,新たに new(の指す領域)を指すことに 変更する。 配列を使ってこの作業を行う場合には,新しいデータを配置すべき位置を探し,そこ から後ろの全要素をメモリ上で一つずつシフトして空席を空けてから挿入するという大 がかりなことをやらなければならない。リスト構造では,上の例のように該当個所の 前後のポインタだけ書き換えてチェインをつなぎ変えるだけである。 9.2.6. ポインタ引数 ポインタ変数やポインタ配列を,手続きの引数に書くことができる。「引数による 結合」(→8.1.3.)の意味は,引用される副プログラムの 仮引数がポインタであるかないかによって異なる: (1) 仮引数がポインタではない場合 ポインタを用いた代入文や演算の場合と同様に,ポインタが指しているターゲット (またはALLOCATEされた無名領域)の値がわたされる。したがって,実引数のポインタ は引用前に必ず結合状態になっていなければならない。 例 PROGRAM main INTEGER, POINTER :: p INTEGER, TARGET :: x = 2 p => x CALL bai(p) →「p の指している変数の値を持っていきなさい。」 PRINT*, x → 4 が出力される END ! SUBROUTINE bai(n) INTEGER, INTENT(INOUT) :: n n = 2*n END (2) 仮引数がポインタの場合 引用により,引用した時点での結合状態が関係づけられる。この使い方の場合,仮引 数のポインタにはINTENT属性を指定できない。 例題 9_5 「1次元配列をサイクリックに置換する。」[ex9_5.f90] ! --- Cyclic Permutation --- PROGRAM main INTERFACE ! この場合,引数の属性を教えておく必要がある SUBROUTINE cyclic(q) INTEGER, POINTER :: q(:) END END INTERFACE ! INTEGER, POINTER :: p(:) INTEGER, TARGET :: x(1:5) = (/ 1, 2, 3, 4, 5 /) p => x; CALL cyclic(p) PRINT*, x ! 2 3 4 5 1 が出力される END ! SUBROUTINE cyclic(q) INTEGER, pointer :: q(:) q = CSHIFT( q, 1) ! 要素を循環させる組込み関数 END
ポインタ q,したがって p の指している配列の要素を循環させることになる。この サブルーチンを SUBROUTINE cyclic(q) INTEGER, POINTER :: q(:) INTEGER, TARGET :: y = (/ 2, 3, 4, 5, 1 /) q => y END と書いても,「ポインタ q,したがって p を,配列 y を指すようにしなさい。」であ るから,p => x の結合は解除されてしまい,代入文 p = q のような効果は得られない: PRINT*, x → 1 2 3 4 5 のままである PRINT*, p → 2 3 4 5 1 (p は y を指すようになったから) である。つまり「仮引数 q が配列 y を指しているという結合状態」が p に返される。 もちろん,普通の代入文 q = y を書いてあるなら,「ポインタ q の指す配列に y の 値を代入しなさい。」が p に返されるから,p => x は保持されており,p の指す配列 x に (/ 2, 3, 4, 5, 1 /) が代入されることになる。 このようにポインタ間の「引数による結合」は,代入文でもなければポインタ代入文 でもなく,まさに引用の瞬間に一体のものとなるとしか言いようがない。 9.3. モジュール [90] モジュールはサブルーチンや関数と同じくプログラム単位であり,[90]で初めて導入 されたもので,例として次のような目的で用いられる: (1) いくつかのプログラム単位で共通に使う変数や定数,配列の型宣言を1カ所に まとめておく。 (2) これとセットで,サブルーチンや関数もモジュール内で定義しておく。 (3) 構造型データの間の2項演算など,ユーザ定義の演算子を表現する。 (4) 構造型を含め,異なる型の間のユーザ定義の代入文を表現する。 9.3.1. 型宣言の共通化 これは[77]までは COMMON文 と BLOCK DATA文 として用意されていた機能に近いが, 共通に使う変数や定数名を,プログラム全体の中で1箇所だけにまとめて書いておき, それを使うプログラム単位では,USE文 で宣言するだけでよい。INTERFACE文による 仕様宣言は不要である。 (モジュール副プログラム) MODULE モジュール名 共有変数などの型宣言,初期値設定 ................... END MODULE [モジュール名] (使用するプログラム単位での宣言) USE モジュール名 例 MODULE common_variables REAL :: a, x(256) REAL, PARAMETER :: pi = 3.141593 INTEGER, PARAMETER :: mask = 2147483647 END MODULE (使用宣言) USE common_variables ..... MODULE で用意された変数等は,USE 宣言をしたプログラム単位の間で共通の値を持 つが,USE宣言をしていないプログラム単位中では,同じ変数名等が使われていても, 値は共有されない。 9.3.2. 手続きモジュール モジュール中に,変数などの型宣言とセットで,サブルーチン,関数などの内部手続 きを定義しておくことができる。 MODULE モジュール名 共有変数などの型宣言,初期値設定 ................... CONTAINS 内部手続き ................... END ... END MODULE [モジュール名] 例題 9_6 「英文データを,文字列自身とその実際の長さを表す整数を成分とする 構造型データとして定義し,これと文の実際の長さを求める関数をセッ トにしたモジュール」(文の長さは80字以内で,途中には連続した2つ 以上の空白はないものとする。)[ex9_6.f90] ! --- String having Variable Length --- MODULE variable_string_data TYPE v_string CHARACTER(LEN=80) :: str INTEGER :: len END TYPE CONTAINS FUNCTION length(s) CHARACTER(LEN=*), INTENT(IN) :: s ! (1) INTEGER :: length, i, ls ; ls = LEN(s) ! (1) IF( s(ls:ls) /= ' ') THEN length = ls ELSE IF( s(ls-1:ls-1) /= ' ') THEN length = ls-1 ELSE length = INDEX(s, ' ') - 1 ! (2) END IF END FUNCTION END MODULE ! PROGRAM main USE variable_string_data TYPE(v_string) :: vst PRINT*, "Input a sentence. : " READ '(A)', vst%str vst%len = length(vst%str) PRINT "(A, '(', I2, ')' )", vst%str(1:vst%len), vst%len END
(1) 仮引数にある文字変数の長さは, CHARACTER(LEN=*) または CHARACTER*(*) と しておけば,引用された時点でその長さが決定される。送られてきた文字列の (宣言されている)長さは組込み関数 LEN で知ることができる。なお,この例題 の関数で求めている文字列の実際の長さ(右端の空白を除いた長さ)なら, 組込み関数 LEN_TRIM で求めることができる。 (2) INDEX(a, b) は文字列 a の中に文字列 b が初めて現れた位置(先頭から何文字 目),もし見つからなければ0を返す組込み関数。 9.3.3. ユーザ定義演算子 一般には成分の型が異なる構造型のデータでは,構造型変数名のままでの演算は許さ れていないが,ベクトル演算のような場合に,成分の間での演算ではなくベクトル名そ のものでの演算子が欲しくなる。INTERFACE文との組み合わせにより,モジュールでこ れを実現できる。 MODULE モジュール名 共有変数などの型宣言,初期値設定 ................... INTERFACE OPERATOR(演算子記号) MODULE PROCEDURE 演算子を定義する関数名 END INTERFACE .................. CONTAINS FUNCTION 関数名(仮引数1 [,仮引数2] ) RESULT(変数名) .................. END FUNCTION END MODULE [モジュール名] ・演算子記号は,演算子 + - * / と,両側をピリオドで挟んだ英字名 ・2項演算子の場合の関数の仮引数は,定義する演算子を例えば x .OP. y と書 くとすると,(x, y) のように,オペランドの順に書いておかなければならない。 ・以下の例題中の * のように,同じ演算記号を別の意味で2重に定義してあっても, →ユーザ定義演算子のプログラム例「πの任意長計算」 例題 9_7 「ベクトル型の定義,ベクトルの引き算とスカラ倍,およびスカラ積 を求めるユーザ定義演算をモジュールで用意しておき,ベクトルを 与えられた平面内の成分と直交成分とに分解する。」[ex9_7.f90] ! --- Projection of a Vector --- MODULE vector_arithmetic TYPE vector REAL :: x, y, z END TYPE ! INTERFACE OPERATOR(-) MODULE PROCEDURE subtraction END INTERFACE INTERFACE OPERATOR(*) MODULE PROCEDURE scalar_multiplication END INTERFACE INTERFACE OPERATOR(*) MODULE PROCEDURE inner_product END INTERFACE ! CONTAINS FUNCTION subtraction(u, v) RESULT(w) TYPE(vector), INTENT(IN) :: u, v TYPE(vector) :: w w%x = u%x - v%x; w%y = u%y - v%y; w%z = u%z - v%z END FUNCTION ! FUNCTION scalar_multiplication(s, v) RESULT(w) TYPE(vector), INTENT(IN) :: v TYPE(vector) :: w REAL, INTENT(IN) :: s w%x = s * v%x; w%y = s * v%y; w%z = s * v%z END FUNCTION ! FUNCTION inner_product(u, v) RESULT(s) TYPE(vector), INTENT(IN) :: u, v REAL :: s s = u%x * v%x + u%y * v%y + u%z * v%z END FUNCTION END MODULE vector_arithmetic ! PROGRAM main USE vector_arithmetic TYPE(vector) :: a, n, perp, planar PRINT *, "Input the normal vector of projection-plane:" READ *, n PRINT *, "Input a 3D vector:" READ *, a perp = ( (a*n)/(n*n) ) * n planar = a - perp PRINT "(A, 3F7.2, A)", 'Perp. Component = (', perp, ' )' PRINT "(A, 3F7.2, A)", 'Planar Component = (', planar, ' )' END
(実行例) Input the normal vector of projection-plane: 1.0 1.0 1.0 Input a 3D vector: 3.0 4.0 5.0 Perp. Component = ( 4.00 4.00 4.00 ) Planar Component = ( -1.00 0.00 1.00 ) 9.3.4. ユーザ定義代入文 構造型のデータの場合,両辺が同じ型なら構造型変数名のままでの代入文が許されるが, 同じような構造を持っていても異なる型の間の代入文は許されていない。このような 場合にもモジュールで代入文を定義できる。 MODULE モジュール名 共有変数などの型宣言,初期値設定 ................... INTERFACE ASSIGNMENT(=) MODULE PROCEDURE 代入を定義する手続き名 END INTERFACE .................. CONTAINS SUBROUTINE 手続き名(仮引数1,仮引数2) ※ 型,INTENT(OUT) :: 引数名1 型,INTENT(IN) :: 引数名2 .................. END SUBROUTINE END MODULE [モジュール名] ※ 仮引数の順序を,そのINTENT が OUT, IN である順に書いておかないとエラーになる。 例題 9_8 「3次元ベクトルと寸法3の1次元配列の間の相互の代入文を定義する。」[ex9_8.f90] ! --- Vector and Array --- MODULE vector_and_array TYPE vector REAL :: x, y, z END TYPE vector ! INTERFACE ASSIGNMENT(=) MODULE PROCEDURE vector_to_array END INTERFACE ! INTERFACE ASSIGNMENT(=) MODULE PROCEDURE array_to_vector END INTERFACE ! CONTAINS SUBROUTINE vector_to_array(a, v) ! ※ TYPE(vector), INTENT(IN) :: v REAL, INTENT(OUT) :: a(1:3) a = (/ v%x, v%y, v%z /) END SUBROUTINE ! SUBROUTINE array_to_vector(v, a) ! ※ REAL, INTENT(IN) :: a(1:3) TYPE(vector), INTENT(OUT) :: v v = vector( a(1), a(2), a(3) ) END SUBROUTINE END MODULE ! PROGRAM main USE vector_and_array TYPE(vector) :: u REAL :: w(1:3) PRINT*, "Input 3D-vector components:" READ*, u w = u u = 2.0 * w PRINT '(3F8.3)', u END
⇒9章先頭へ ⇒付録へ