为什么我的程序会循环两次?

Why does my program loop twice?

我尝试在 Fortran 中创建一个 toUpper 和 toLower 函数,它们在我的实现中似乎运行良好;该程序提示用户输入字符串,然后以大写字母和小写字母打印出来。

但是,在我添加了一个大写函数之后,程序提示用户输入字符串两次,我不明白为什么。好像还行,但是做了两次。

谁能告诉我为什么我的程序提示输入两次?下面的代码和输出。

program stringutils
    character :: words*1000

    print*, 'Enter a string.'
    read(*,'(A)') words

    call toUpper(words)
    print*, 'toUpper: ', trim(words)

    call toLower(words)
    print*, 'toLower: ', trim(words)

    call capitalize(words)
    print*, 'capitalize: ', trim(words)

end program stringutils

subroutine toUpper(string)
    character :: string*1000
    integer :: i, charNum

    do i = 1, len(trim(string))
        charNum = iachar(string(i:i))
        if (charNum >= 97 .and. charNum <= 122) then
            string(i:i) = achar(charNum - 32)
        end if
    end do
end subroutine toUpper

subroutine toLower(string)
    character :: string*1000
    integer :: i, charNum

    do i = 1, len(trim(string))
        charNum = iachar(string(i:i))
        if (charNum >= 65 .and. charNum <= 90) then
            string(i:i) = achar(charNum + 32)
        end if
    end do
end subroutine toLower

subroutine capitalize(string)
    character :: string*1000
    integer :: i

    call toUpper(string(1:1))
    do i = 2, len(trim(string))
        if (iachar(string(i-1:i-1))==32) then
            call toUpper(string(i:i))
        else
            call toLower(string(i:i))
        end if
    end do
end subroutine capitalize

示例输出:

 Enter a string.
this IS a tEsT!
 toUpper: THIS IS A TEST!
 toLower: this is a test!
 capitalize: This Is A Test!
 Enter a string.
why is this running a second time?
 toUpper: WHY IS THIS RUNNING A SECOND TIME?
 toLower: why is this running a second time?
 capitalize: Why Is This Running A Second Time?

我在 Windows 中通过 MinGW 使用 gfortran。

虽然我不确定是什么导致它循环两次,但我知道它与 capitalize 函数有关,也许我如何将单个字符发送到期望 1000 个字符的函数。所以我添加了 charToUpper 和 charToLower 函数,它现在按预期工作。这是新代码。我觉得capitalize函数可以更好...欢迎评论。

program stringutils
    character words*1000

    print*, 'Enter a string.'
    read(*,'(A)') words

    call toUpper(words)
    print*, 'toUpper: ', trim(words)

    call toLower(words)
    print*, 'toLower: ', trim(words)

    call capitalize(words)
    print*, 'capitalize: ', trim(words)

end program stringutils

subroutine toUpper(string)
    character string*1000
    integer i

    do i = 1, len(trim(string))
        call charToUpper(string(i:i))
    end do
end subroutine toUpper

subroutine toLower(string)
    character string*1000
    integer i

    do i = 1, len(trim(string))
        call charToLower(string(i:i))
    end do
end subroutine toLower

subroutine charToUpper(c)
    character c
    integer charNum

    charNum = iachar(c)
    if (charNum >= 97 .and. charNum <= 122) then
        c = achar(charNum - 32)
    end if
end subroutine charToUpper

subroutine charToLower(c)
    character c
    integer charNum

    charNum = iachar(c)
    if (charNum >= 65 .and. charNum <= 90) then
        c = achar(charNum + 32)
    end if
end subroutine charToLower

subroutine capitalize(string)
    character :: string*1000
    integer :: i

    call charToUpper(string(1:1))
    do i = 2, len(trim(string))
        if (iachar(string(i-1:i-1))==32) then
            call charToUpper(string(i:i))
        else
            call charToLower(string(i:i))
        end if
    end do
end subroutine capitalize

这是扩展评论而不是答案...

我不相信你对你最初报告的问题的原因的解释。我不明白为什么程序会因为一两个错误指定的例程而执行两次。尽管如此,如果更多地关注现代 Fortran 实践,您的例程将会得到很大改进。

您已经编写了一堆例程来处理包含 1000 个字符的字符串。更好的办法当然是编写它们来操作作为参数传递的字符串的任意长度的字符串。现代 Fortran(因为我不确定,至少 90,可能在那之前)允许传递 假定长度的字符 参数,就像这样:

subroutine toUpper(string)
    character(*) :: string
    integer :: i, charNum

    do i = 1, len(trim(string))
        charNum = iachar(string(i:i))
        if (charNum >= 97 .and. charNum <= 122) then
            string(i:i) = achar(charNum - 32)
        end if
    end do
end subroutine toUpper

现在,当例程被调用时,它将对 string 中的所有字符进行操作,不多也不少。

我还建议进行许多其他修改。我没有时间详细介绍所有这些内容,但如果您在 SO 上四处逛逛,您会发现有关这些主题的几个 Qs 和 As。这是入门套装:

  • 始终使用程序实体的显式声明,在您的程序(和模块)中包含 implicit none。这是编写安全程序的第一条基本规则。
  • 将您的例程放入 moduleuse 中。这可确保编译器能够并且确实会检查您对它们所做的调用在语法上是否有效。
  • 我个人会将你所有的 subroutines 变成 functions 和 return 一个版本的输入字符串变成大写(或小写)。让子例程就地修改输入字符串没有错,但是组合函数更容易,例如,有一天你可能想写一些像 HumpyString = capitalise(toLower(string))
  • 这样的东西

我在 Linux 中用 Gfortran 编译了你的代码。 我经历过以下情况: 您的 "loop twice" 问题确实发生了,但对我来说不是每次都发生。我随机得到了一个分段错误。 我尝试回答 :

你猜对了,问题出在capitalize子程序。我未能使用 gdb 调试您的代码,但段错误似乎表明您试图访问未声明的字符串的组件或类似的东西,以及您正在调用 toUppertoLower 循环。那是我找到东西的地方:

在循环中,您通过发送 string(i:i) 来调用 toUppertoLower,这是一个唯一字符(长度为 1)。但是在 toUpper 中,您声明了一个长度为 1000 的字符,而在此例程中,您只处理 string(i:i),即长度为 1 的字符,这是不合逻辑的。通过将 character :: string*1000 替换为 character :: string*1,它似乎可以工作并且更有意义,因为该子例程每次都需要转换一个字符。

不过,我无法解释为什么您的代码会崩溃或循环两次,但这可能是由于字符长度溢出导致的某种内存泄漏...

无论如何,@High Performance Mark 给了你一个关于假定长度字符参数的好建议。如果您以一种通过模块和接口执行参数检查的方式进行编码,编译器可能已经能够警告您。也许 INTENT 子句是明智的。这些都是 Fortran 90 的强大功能。