Fortran2003から導入されたdeferred length character、つまり可変長文字列ですが、gccのバージョンでちょっと挙動が変わっていたので、まとめてみます。
比較したバージョン
- GCC 4.8.4
- AtCoderのコードテスト
- GCC 4.8.5
- CentOS 7
- GCC 5.4.0
- Ubuntu 16.04
基本
program main
implicit none
integer :: W
character(len=:), allocatable :: S
W = 10
allocate(character(len=W) :: S)
write(*, *) S
write(*, *) "string len: ", len(S)
S = "hoge"
write(*, *) S
write(*, *) "string len: ", len(S)
end program main
これを実行すると以下の出力を得ます。
X�؉�X�
string len: 10
hoge
string len: 4
可変長文字列なのでメモリの割当が必要となります。allocateを呼び出す場合は
allocate(character(len=W) :: S)
とします。ですがわざわざallocate
しなくてもS = "hoge"
とすると必要な分だけallocateされます。
可変長文字列の可変長配列
入力
AtCoderでよくある、1行目に$H$と$W$、続く$H$行で長さ$W$の文字列が入力される状況を考えます。
3 4
abcd
efgh
ijkl
GCC5.4.0
5.4.0では以下のようにして文字列を取得できます。
program main
implicit none
integer :: W, H, y
character(len=:), allocatable :: S(:)
read(*, *) H, W
allocate(character(len=W) :: S(H))
read(*, *) S
do y = 1, H
write(*, *) S(y)
enddo
end program main
長さW
の文字列のH
個の配列を allocate(character(len=W) :: S(H))
としておけば、
read(*, *) S
の一回でH
行の文字列をすべて読み込めます。
ちなみに2行目3文字目のみを取り出すには
write(*, *) S(2)(3:3)
とします。
GCC 4.8では・・・
次のように、ちゃんと配列を割り当てられたかどうかをチェックするコードを挟んで見ます。
program main
implicit none
integer :: W, H, y
character(len=:), allocatable :: S(:)
write(*, *) "Input height and width"
read(*, *) H, W
allocate(character(len=W) :: S(H))
write(*, *) "Height (array size): ", size(S)
write(*, *) "Width (string len): ", len(S(1))
read(*, *) S
do y = 1, H
write(*, *) S(y)
enddo
end program main
これを実行して、上で紹介した入力を与えてやると、
- 4.8.4
Input height and width
Height (array size): 3
Width (string len): 0
- 4.8.5
Input height and width
Height (array size): 3
Width (string len): 4196752
abcd (めちゃ長い空白)
以下略
全然ダメです。特に4.8.4はデータをもたせることすらできません。
回避策
Type Array(失敗)
インテルコンパイラーに妙に詳しい人が、Typeの中にDeferred length characterをメンバにもたせ、そのTypeの動的Arrayを使うと良いと提案していました。
type str
character(len=:), allocatable :: t
end type str
type(str), allocatable :: S(:)
これをGCC4.8.5でコンパイルすると、
type_array.f08:6.40:
character(len=:), allocatable :: t
1
エラー: Deferred-length character component 't' at (1) is not yet supported
とのことです。使えなかった。それにこの方法は
S = "hogehoge"
といった代入もできないので、できたらやりたくないところ。
固定長文字列配列
もうdeferred length characterは諦め、従来通りの長めに取った固定長文字列を使います。
program main
implicit none
integer :: W, H, y
character(len=100), allocatable :: S(:)
write(*, *) "Input height and width"
read(*, *) H, W
allocate(S(H))
write(*, *) "Height (array size): ", size(S)
write(*, *) "Width (string len): ", len(S(1))
read(*, *) S
do y = 1, H
write(*, *) trim(S(y))
enddo
end program main
これを上の入力で実行すると、出力は
Input height and width
Height (array size): 3
Width (string len): 100
abcd
efgh
ijkl
と、固定長の長さが確認できます。
また、read(*, *) S
の1回で、入力の各行がS
の各成分に代入されました。
S(1)
にすべて押し込まれるといったことがなくて安心。
ちなみに
可変長文字列の可変長配列ですが、代入してしまう場合の注意です。
character(len=:), allocatable :: S(:)
S = ["hoge", "fugafuga"]
これはエラーです。
S = ["hoge", "fugafuga"]
1
Error: Different CHARACTER lengths (4/8) in array constructor at (1)
以下のようにスペースを入れて水増しします。
S = ["hoge ", "fugafuga"]
write(*, *) S
write(*, *) "Height (array size): ", size(S)
write(*, *) "Width (string len): ", len(S(1))
これの出力は以下のようになります。
hoge fugafuga
Height (array size): 2
Width (string len): 8
おわりに
Fortranの文字列操作は、Cに比べるとかなりやりやすい方ですが、現代的な言語に比べるとやはり見劣りしますね。