使用延迟长度的字符字符串读取用户输入

9
我希望能够简单地使用延迟长度字符串读取用户输入。我之所以这样做,是因为在不知道用户输入的大小之前,我不想声明字符串的大小。我知道有一些“复杂”的方法可以实现这一点。例如,可以使用iso_varying_string模块:https://www.fortran.com/iso_varying_string.f95。此外,在这里也有一个解决方案:Fortran Character Input at Undefined Length。然而,我希望找到像以下代码一样简单的解决方案:
program main

  character(len = :), allocatable :: my_string
  read(*, '(a)') my_string
  write(*,'(a)') my_string
  print *, allocated(my_string), len(my_string)

end program

当我运行这个程序时,输出结果为:
./a.out
here is the user input

F       32765

请注意,write(*,'(a)') my_string 没有输出任何内容。为什么呢?

此外,my_string 也没有被分配空间。为什么呢?

为什么这不是 Fortran 的一个简单特性呢?其他语言是否有这个简单特性?我是否对这个问题的基本理解存在缺陷?

4个回答

17

vincentjs的回答并不完全正确。

现代(2003+)Fortran在赋值过程中确实允许对字符串进行自动分配和重新分配,因此类似于以下语句序列:

character(len=:), allocatable :: string
...
string = 'Hello'
write(*,*)
string = 'my friend'
write(*,*)
string = 'Hello '//string
write(*,*)

代码是正确的,将按预期工作并编写出三个不同长度的字符串。但是,目前有一种广泛使用的编译器——Intel Fortran编译器默认情况下不使用2003语义,因此在尝试编译时可能会引发错误。请参阅文档以了解使用Fortran 2003的设置。

然而,在读取字符串时,这个特性不可用,因此您必须采用经过尝试和测试的(也就是老式的,如果您喜欢的话)方法,即声明足够输入的缓冲区大小,然后分配可分配变量。就像这样:

character(len=long) :: buffer
character(len=:), allocatable :: string
...
read(*,*) buffer
string = trim(buffer)

不,我不知道为什么语言标准禁止在read上进行自动分配,只是它确实这样做。


9

延迟长度字符是Fortran 2003的一个特性。请注意,许多与之相关的复杂方法都是针对早期语言版本编写的。

有了Fortran 2003支持,将完整记录读入字符变量相对简单。下面是一个非常简单的示例,仅具有最少的错误处理。这样的过程只需要编写一次,并且可以根据用户的特定要求进行自定义。

PROGRAM main
  USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY: INPUT_UNIT
  IMPLICIT NONE
  CHARACTER(:), ALLOCATABLE :: my_string

  CALL read_line(input_unit, my_string)
  WRITE (*, "(A)") my_string
  PRINT *, ALLOCATED(my_string), LEN(my_string)

CONTAINS
  SUBROUTINE read_line(unit, line)      
    ! The unit, connected for formatted input, to read the record from.
    INTEGER, INTENT(IN) :: unit
    ! The contents of the record.
    CHARACTER(:), INTENT(OUT), ALLOCATABLE :: line

    INTEGER :: stat           ! IO statement IOSTAT result.
    CHARACTER(256) :: buffer  ! Buffer to read a piece of the record.
    INTEGER :: size           ! Number of characters read from the file.
    !***
    line = ''
    DO
      READ (unit, "(A)", ADVANCE='NO', IOSTAT=stat, SIZE=size) buffer
      IF (stat > 0) STOP 'Error reading file.'
      line = line // buffer(:size)
      ! An end of record condition or end of file condition stops the loop.
      IF (stat < 0) RETURN
    END DO
  END SUBROUTINE read_line
END PROGRAM main

我在我的问题中提到,我正在寻找比我在Fortran Character Input at Undefined Length发布的答案更简单的东西。你的子程序不就是我在Fortran Character Input at Undefined Length发布的相同答案吗? - David Hansen
这是一种类似的技术。请注意,该问题被标记为Fortran 95 - 延迟长度字符是在Fortran 2003中引入的。调用一个带有两个参数的子程序是一个相当简单的练习。 - IanH

0

你知道有一些“复杂”的方法可以实现你想要的功能。但我不会讨论那些方法,而是回答你前两个“为什么?”。

与内在赋值不同,read语句没有将目标变量首先分配到正确的大小和类型参数以适应输入内容(如果它还没有这样)。事实上,输入列表中的项目必须被分配。Fortran 2008年9.6.3条款明确规定:

如果输入项或输出项是可分配的,则必须进行分配。

无论可分配变量是具有延迟长度的字符、具有其他延迟长度类型参数的变量还是数组,都是如此。

还有另一种声明具有延迟长度字符的方法:给它赋予pointer属性。然而,这对你没有帮助,因为我们也看到:

如果输入项是指针,则必须将其与可定义的目标相关联...

为什么你的write语句没有输出与你看到字符变量未分配有关:你没有遵循Fortran的要求,因此不能期望未指定的行为。


我会猜测为什么有这个限制。我看到两种明显的方法可以放宽限制:

  • 允许自动分配;
  • 允许分配延迟长度字符。

第二种情况很容易:

如果输入项或输出项是可分配的,则应分配它,除非它是具有延迟长度的标量字符变量

然而,这样做很笨拙,这种特殊情况似乎违背了整个标准的精神。我们还需要一个经过深思熟虑的规则来处理这种特殊情况的分配。

如果我们采用一般情况的分配,我们可能需要确保未分配的有效项是列表中的最后一个有效项:

integer, allocatable :: a(:), b(:)
character(7) :: ifile = '1 2 3 4'
read(ifile,*) a, b

然后我们就要担心

type aaargh(len)
  integer, len :: len
  integer, dimension(len) :: a, b
end type
type(aaargh), allocatable :: a(:)
character(9) :: ifile = '1 2 3 4 5'

read(ifile,*) a

很快就会变得非常混乱。似乎有很多问题要解决,而且有各种难度不同的方法可以解决阅读问题。

最后,我还要注意,在数据传输语句期间分配是可能的。尽管在现有规则下,当出现在派生类型的已分配变量的输入列表组件中时,必须对变量进行分配 不需要,如果该有效项由定义的输入处理,则不需要分配。


0
延迟长度数组就是这样:长度被延迟了。你仍然需要使用 allocate 语句来分配数组的大小,然后才能给它赋值。一旦你分配了它,就不能改变数组的大小,除非你使用 deallocate,然后再用新的大小重新分配。这就是为什么你会得到一个调试错误。
Fortran 不像 C++ 中的 std::string 类那样提供动态调整字符数组大小的方法。在 C++ 中,你可以初始化 std::string var = "temp",然后重新定义它为 var = "temporary",而不需要额外的工作,这是有效的。这只有在 std::string 类的函数中完成了幕后的重定位(如果超过缓冲区限制,则将其大小加倍,这在功能上等同于使用 2 倍大的数组进行重新分配)时才可能做到这一点。
实际上,在处理Fortran字符串时,我发现最简单的方法是分配一个足够大的字符数组,以适应大多数预期输入。如果输入的大小超过了缓冲区,则只需通过使用更大的大小重新allocate数组来增加数组的大小。使用trim可以去除尾随空格。

2
真的,但值得注意的是,在分配时重新分配可以简化很多事情。特别是,您的C++示例在Fortran中同样简单。编译器甚至可以优化它以在幕后调用realloc(),但我不知道是否有任何编译器这样做。 - Vladimir F Героям слава

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接