跳转到内容

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 变量 word1word2 分别被声明为具有 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',但结果本质上是供应商特定的。因此,ichariachar 等函数之间存在以下描述的差异。

字符种类

[编辑 | 编辑源代码]

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 种类表示的许多语言或字符集中。sizelboundubound 等适用于任何数据类型(包括字符类型)的数组的函数,此处不作描述。

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(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(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(string, set, back, kind) 返回一个默认整数(或可选类型的整数),表示 set 中任何字符在 string 中出现的第一个位置。要从右到左搜索,可选的逻辑 back 必须设置为 true。string 可以是数组,在这种情况下,结果是整数数组。如果 string 是数组,则 set 可以是与 string 大小和形状相同的数组,并且 set 的每个元素都将在 string 的对应元素中进行扫描。上面描述的 indexscan 的特例,因为必须找到 set 的所有字符,并且必须按照 set 中字符的顺序找到。

selected_char_kind

[edit | edit source]

selected_char_kind(name) 是一个整数函数,它返回命名字符集的 kind 值。语言标准必须支持的唯一集合是 name='DEFAULT'。如果 name 不受支持,则结果为 -1。

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" 形式将写入整个字符变量,包括所有尾随空格,因此通常使用 trimadjustl 或两者。

示例

character (len=512) :: line
!...
write (10,'(a)') trim(adjustl(line))

内部读取和写入

[编辑 | 编辑源代码]

Fortran 有许多隐藏的秘密,其中最有用的是 readwrite 语句可以像操作文件一样在字符变量上使用。因此,就解释了为什么没有将数字转换为字符串或反之的函数。字符变量被视为一个“内部文件”。

示例

character (120) :: text_in, text_out
integer :: i
real :: x
!...
write (text_in,'(A,I0)') 'i = ', i  ! Formatted
!...
read (text_out,*) x  ! List-directed

除了类型转换之外,这种内部读写还可以用作非常灵活且防弹的方法来读取内容格式可能不确定的文件。外部文件逐行读取到一个字符变量中,scanverify 可以用来确定行中存在什么,然后在字符变量上进行内部文件读取,将数据转换为 realintegercomplex 等适当的类型。

最近扩展

[编辑 | 编辑源代码]

character(:), allocatable

[编辑 | 编辑源代码]

字符标量数据的尺寸可以推迟(或“可分配”),因此不需要声明为特定长度。然后,可以正式分配结果标量,或者如以下示例所示,可以自动分配。

示例

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 字符集函数 iacharachar 用于连续检查字符串中的每个字符。

示例

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

哪个例程更快将取决于 indexiachar 内在函数的相对速度。在一个不那么科学的测试中,上面的第一种方法似乎比第二种方法快两倍多,但这将因供应商而异。

华夏公益教科书