Fortran/字串
現代 Fortran 擁有廣泛的功能來處理字串或文字資料,但其中一些語言定義的功能尚未被編譯器開發人員廣泛實施。應該記住,Fortran 是為科學計算而設計的,可能不適合編寫新的文字處理器。
Fortran 中支援字串的主要功能是內在資料型別 character。字元文字常量可以使用單引號或雙引號分隔,必要時可以使用兩個連續的單引號或雙引號轉義。連線運算子是 //(但這不能用於連線不同 KIND 的字元實體)。允許使用字元標量變數和陣列。字元變數具有子字串表示法來引用和提取子字串。
示例
program string_1
implicit none
! Declarations
character (len=6) :: word1
character (len=2) :: word2
word1 = "abcdef" ! Assignment
word2 = word1(5:6) ! Substring
word1 = 'Don''t ' ! Escape with a double quote
write (*,*) word2//word1 ! Concatenation
end program string_1
在上面的示例中,兩個 character 變數 word1 和 word2 分別被宣告為具有 6 個字元和 2 個字元的長度。
在 character 賦值操作中,如果賦值的右側比左側短,則左側的剩餘字元將用空格填充。如果右側比左側長,則右側將被截斷。在這兩種情況下,編譯器或執行時都不會引發錯誤。
允許使用 character 陣列和協陣列,並且可以像其他任何 Fortran 陣列一樣宣告和訪問它們。當陣列索引和子字串表示法需要組合時,陣列索引首先出現,子字串表示式其次出現,如下面的示例的最後一行所示
character (len=120), dimension (10) :: text
text(1) = 'This is the first element of the array "text"'
text(2:3) = ' ' ! Elements 2 and 3 are blank.
text(4)(20:20) = '!' ! Character 20 of element 4.
與某些程式語言不同,Fortran character 資料和變數不需要顯式字元來終止字串。此外,與 C 型別語言不同,Fortran character 資料不包含嵌入式和轉義的控制字元(例如 /n),所有輸出控制的處理都是透過廣泛的 format 子系統完成的。
在內部,Fortran 為所有允許的字元維護一個排序順序。非列印字元可以包含在排序順序中。排序順序未由語言標準指定,但大多數供應商支援 ASCII 或 EBCDIC。此排序順序意味著可以執行詞法比較以確定例如 'a'<'b',但結果本質上是供應商特定的。因此,ichar 和 iachar 等函式之間存在以下描述的差異。
character 也可以具有 kind,但這與供應商相關。它可以允許編譯器支援 unicode、俄語字母或日語字元等。不需要指定 character 變數的長度或種類。如果未宣告 character 變數的長度或種類,則結果為預設種類且長度為一個字元的變數。一個數字表示長度,兩個數字按順序表示長度和種類。通常更清晰,但稍微冗長一些,就像以下示例中的第 6-8 行所示。編譯器供應商控制支援哪些字元種類以及分配給訪問相應字元集的整數值。
program string_2
implicit none
character :: one
character (5) :: english_name
character (5,2) :: japanese_name
character (len=80) :: line
character (len=120, kind=3) :: unicode_line
character (kind=4, len=256) :: ebcdic_string
!...
end program string_2
內在函式 selected_char_kind(name) 返回具有相應名稱的字元集的正整數種類值(例如 default、ascii、kanji、iso_10646 等),但唯一必須支援的字元集是 default,如果名稱不受支援,則將返回 -1。令人失望的是,供應商通常在實施除預設種類以外的其他種類方面進展緩慢,但例如 gfortran 算是一個明顯的例外。
Fortran 擁有一套相當有限的內在函式來支援字元操作、搜尋和轉換。但基本集足以根據需要構建一些強大的功能。存在一些奇怪的缺失,例如將小寫字母轉換為大寫字母的能力,但這可以理解和原諒,因為這些概念可能不存在於可能由不同 character 種類表示的許多語言或字元集中。size、lbound 和 ubound 等適用於任何資料型別(包括字元型別)的陣列的函式,此處不作描述。
achar(i, kind) 返回指定種類字元的 ASCII 排序順序中的第 i 個字元。整數 i 必須在 0 < i < 127 範圍內。Kind 是一個可選的整數。如果未指定 kind,則假定預設 kind。achar(72) 的值為 'H'。achar 的一個非常有用的功能是它允許訪問非列印 ASCII 字元,例如回車 (achar(13))。achar 將始終返回 ASCII 字元,即使處理器的排序順序不是 ASCII。如果存在 kind,則結果的 kind 引數由 kind 指定;否則,結果的 kind 引數是預設字元的 kind 引數。如果處理器無法在結果的 kind 中表示結果值,則結果未定義。強烈建議使用 achar 而不是下面描述的 char,因為它可以在處理器之間移植。
adjustl(string) 左對齊,從字串中刪除前導(左側)空格,並在字串右側填充空格,以使結果的長度與輸入字串相同。
adjustr
[edit | edit source]adjustr(string) 右對齊,從字串中刪除尾隨(右側)空格,並在字串左側填充空格,以使結果的長度與輸入字串相同。
char
[edit | edit source]char(i, kind) 返回指定型別的字元的處理器排序序列中的第 i 個字元。整數 i 不必在 0 < i < 127 的範圍內。Kind 是一個可選的整數。如果未指定 kind,則假定預設 kind。如果處理器無法以結果型別的 kind 表示結果值,則結果未定義。
iachar
[edit | edit source]iachar(c, kind) 是上面描述的 achar 的反函式。c 是一個單個輸入字元,iachar(c) 返回 c 在 ASCII 字元集中的位置,作為預設整數。Kind 是一個可選的輸入整數,如果指定了 kind,它將指定 iachar 返回的整數的型別。
ichar
[edit | edit source]ichar(c, kind) 是上面描述的 CHAR 的反函式。c 是一個單個輸入字元,ichar(c) 返回 c 在所選字元集中的位置,作為預設整數。Kind 是一個可選的輸入整數,如果指定了 kind,它將指定 ichar 返回的整數的型別。
index
[edit | edit source]index(string, substring) 返回一個預設整數,表示從左到右搜尋 substring 在 string 中的第一個例項的位置。有兩個可選引數:back 和 kind。如果邏輯 back 設定為 true,則從右到左進行搜尋;如果指定了整數 kind,則 index 返回的整數將是該型別。如果 substring 不出現在 string 中,則結果為 0。
len
[edit | edit source]len(c, kind) 返回一個整數,表示字元 c 的宣告長度。這在接收字元虛擬引數的子程式中非常有用。c 可以是字元陣列。Kind 是一個可選的整數,它控制 len 返回的整數的型別。
len_trim
[edit | edit source]len_trimc, kind) 返回 c 的長度,不包括任何尾隨空格(但包括前導空格)。如果 c 只有空格,則結果為 0。因此,像 len_trim(adjustl(c)) 這樣的表示式可用於計算 c 中第一個和最後一個非空格字元之間的字元數。Kind 是一個可選的整數,它控制 len_trim 返回的整數的型別。
new_line
[edit | edit source]new_line(c) 是一個字元函式,它返回當前處理器的換行符。返回的字元的型別將與 c 的型別相同。如果 c 所屬的字元型別不包含相關的換行符,則可能會返回空格字元。此函式不太可能使用,除非在某些非常特殊的情況下。
repeat
[edit | edit source]repeat(string, ncopies) 連線字串的整數 ncopies。因此 repeat('=',72) 是一個包含 72 個等號的字串。String 必須是標量,但可以是任何長度。String 中的尾隨空格包含在結果中。
scan
[edit | edit source]scan(string, set, back, kind) 返回一個預設整數(或可選型別的整數),表示 set 中任何字元在 string 中出現的第一個位置。要從右到左搜尋,可選的邏輯 back 必須設定為 true。string 可以是陣列,在這種情況下,結果是整數陣列。如果 string 是陣列,則 set 可以是與 string 大小和形狀相同的陣列,並且 set 的每個元素都將在 string 的對應元素中進行掃描。上面描述的 index 是 scan 的特例,因為必須找到 set 的所有字元,並且必須按照 set 中字元的順序找到。
selected_char_kind
[edit | edit source]selected_char_kind(name) 是一個整數函式,它返回命名字元集的 kind 值。語言標準必須支援的唯一集合是 name='DEFAULT'。如果 name 不受支援,則結果為 -1。
trim
[edit | edit source]trim(string) 是一個字元值函式,它返回一個刪除了尾隨空格的字串。如果 string 全部為空格,則結果的長度為零。
verify
[edit | edit source]verify(string, set, back, kind) 是一個整數函式,它返回 string 中第一個不在 set 中的字元的位置。因此,verify 基本上是 scan 的反函式。在 verify 中,back 和 kind 都是可選的,並且具有與上面 scan 中描述的相同作用。如果 string 中的每個字元也在 set 中(或者 string 的長度為零),則函式返回 0。
正則表示式
[edit | edit source]Fortran 沒有為字元資料定義任何語言級別的正則表示式或排序功能。Fortran 沒有定義語言級別的文字標記器,但是,用一點技巧,列表定向輸入可以提供部分解決方案。但是,有一些 Fortran 庫封裝了 C 正則表示式庫。
字元資料的 I/O
[edit | edit source]讀取格式化
[edit | edit source]read 用於字元資料可以是列表定向的,也可以使用 "a" 或 "an" 形式的編輯描述符進行格式化。在 "a" 形式中,寬度取自列表中對應項的寬度。在 "an" 形式中,整數 n 指定要傳輸的字元數。通用編輯描述 "gn" 也可以使用。
示例
character (120) :: line
open (10,"test.dat")
read (10,'(a)') line ! Read up to 120 characters into line
read (10,'(a5)') line(115:) ! Read 5 character and put them at the end of line
寫入格式化
[edit | edit source]a 和 g 編輯描述符存在於 write 中,如上所述。"a" 形式將寫入整個字元變數,包括所有尾隨空格,因此通常使用 trim 或 adjustl 或兩者。
示例
character (len=512) :: line
!...
write (10,'(a)') trim(adjustl(line))
Fortran 有許多隱藏的秘密,其中最有用的是 read 和 write 語句可以像操作檔案一樣在字元變數上使用。因此,就解釋了為什麼沒有將數字轉換為字串或反之的函式。字元變數被視為一個“內部檔案”。
示例
character (120) :: text_in, text_out
integer :: i
real :: x
!...
write (text_in,'(A,I0)') 'i = ', i ! Formatted
!...
read (text_out,*) x ! List-directed
除了型別轉換之外,這種內部讀寫還可以用作非常靈活且防彈的方法來讀取內容格式可能不確定的檔案。外部檔案逐行讀取到一個字元變數中,scan 和 verify 可以用來確定行中存在什麼,然後在字元變數上進行內部檔案讀取,將資料轉換為 real、integer、complex 等適當的型別。
字元標量資料的尺寸可以推遲(或“可分配”),因此不需要宣告為特定長度。然後,可以正式分配結果標量,或者如以下示例所示,可以自動分配。
示例
character (:), allocatable :: string
!...
string = 'abcdef'
!...
string = '1234567890'
!...
string = trim(line)
!...
甚至可以宣告一個假設長度元素的陣列,如下所示。
示例
character (:), dimension (:), allocatable :: strings
但是,應謹慎使用此功能,並且有一些限制適用
通常,過程可以使用字元虛引數編寫,其中該引數的長度事先未知。現代 Fortran 允許使用 len=* 宣告假設長度的虛引數。型別字元的函式可以編寫成使結果假設與虛引數長度相關的長度。
示例
call this('Hello')
call this('Goodbye')
!...
subroutine this(string)
implicit none
character (len=*), intent (in) :: string
character (len=len(string)+5) :: temp
!...
end subroutine
在上面的示例中,character 變數 temp 被宣告為比字串多 5 個字元,無論實際引數的長度是多少。在下一個示例中,函式返回一個字串,其長度與一個或多個引數的長度相關。
示例
string = that('thing', 7)
!...
function that(in_string, n) result (out_string)
implicit none
character (len=*), intent (in) :: in_string
integer, intent(in) :: n
character (len=len(in_string)*n) :: out_string
!...
end function
在字元函式必須返回一個字串並且該字串的長度與輸入不簡單相關的情況下,可以使用上述假設長度、可分配形式,並在下面的案例轉換示例中進行了說明。
character 引數可以宣告而不顯式宣告長度,例如;
character (*), parameter :: place = 'COEFF_LIST_initialise'
以下是一些上述想法的進一步示例,但針對存在案例轉換概念的語言進行定向。在第一個示例中,ASCII 字元集函式 iachar 和 achar 用於連續檢查字串中的每個字元。
示例
function up_case(in) result (out)
implicit none
character (*), intent (in) :: in
character (:), allocatable :: out
integer :: i, j
out = in ! Transfer whole array
do i = 1, LEN_TRIM(out) ! Each character
j = iachar(out(i:i)) ! Get the ASCII position
select case (j)
case (97:122) ! The lower case characters
out(i:i) = ACHAR(j-32) ! Offset to the upper case
end select
end do
end function up_case
一種不依賴於 ASCII 表示函式的替代方法如下所示
示例
function to_upper(in) result (out)
implicit none
character (*), intent (in) :: in
character (:), allocatable :: out
integer :: i, j
character (*), parameter :: upp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
character (*), parameter :: low = 'abcdefghijklmnopqrstuvwxyz'
out = in ! Transfer all characters
do i = 1, len_trim(out) ! All non-blanks
j = index(low, out(i:i)) ! Is ith character in low
if (j>0) out(i:i) = upp(j:j) ! Yes, then subst with upp
end do
end function to_upper
哪個例程更快將取決於 index 和 iachar 內在函式的相對速度。在一個不那麼科學的測試中,上面的第一種方法似乎比第二種方法快兩倍多,但這將因供應商而異。