Reputation: 25
I tried to create a toUpper and toLower function in Fortran, and they seemed to work fine in my implementation; the program prompts the user for an input string and then prints it out in caps and then in lowercase.
However, after I added a capitalize function, the program prompts the user for an input string twice, and I can't figure out why. It seems to work, but it does it twice.
Could someone be so kind as to let me know why my program prompts for input twice? Code and output below.
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
Example output:
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?
I am using gfortran via MinGW in Windows.
Upvotes: 2
Views: 255
Reputation: 396
I have compiled your code with Gfortran
in Linux.
I have experienced the following :
Your "loop twice" problem indeed happened, but not everytime for me. I got a segmentation fault rather randomly.
I try an answer :
You assumed right, the problem comes from the capitalize
subroutine. I failed to debug your code with gdb
but the seg fault seems to indicate you tried to access a component of a string that is not declared or something like that and the fact that you are calling toUpper
and toLower
in a loop. That's where I found something:
In the loop, you call toUpper
or toLower
by sending them string(i:i)
which is an unique character (length 1). But in toUpper
, you declared a character of length 1000 whereas in this routine you are working on string(i:i)
only, that is character of length 1, that's not logical. By replacing character :: string*1000
to character :: string*1
, it seems to be working and it makes more sense because this subroutine needs to convert ONE character each time.
I can't explain however why your code crashes or loops twice but it might be some kind of memory leak as the result of your character length overflow...
Anyway, @High Performance Mark gives you a good advice about assumed-length character arguments. The compiler may have been able to warn you if you have coded in a way to perform an argument check through modules and interfaces. Perhaps an INTENT
clause would have been wise. These are powerful features of Fortran 90.
Upvotes: 1
Reputation: 78306
This is something of an extended comment rather than an answer ...
I'm not convinced by your explanation of the cause of the problem you initially reported. I can't see why the program would execute twice because of a mis-specified routine or two. Notwithstanding that, your routines would be much improved by a little more attention to modern Fortran practice.
You've written a bunch of routines to manipulate strings with 1000 characters. Better surely would be to write them to manipulate strings of whatever length the string passed as argument happens to be. Modern Fortran (since when I'm not sure, at least 90, possibly before that) allows passing assumed-length character arguments, rather like this:
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
Now, when the routine is called it will operate on all the characters in string
, no more, no less.
There's a host of other modifications I'd suggest too. I don't have time to cover all of them in detail but if you poke around here on SO you'll find several Qs and As on the topics. Here's a starter set:
implicit none
in your programs (and modules). This is the fundamental number one rule for writing safe programs.module
s and use
them. This ensures that the compiler can, and does, check that the calls you make to them are valid syntactically.subroutines
into functions
and return a version of the input string made into upper (or lower) case. There's nothing wrong with having subroutines modify the input string in place but it's easier to compose functions, for example you might one day want to write something like HumpyString = capitalise(toLower(string))
Upvotes: 1
Reputation: 25
Although I'm not sure what caused it to loop twice, I know it is something with the capitalize function and maybe how I'm sending a single char to a function expecting a 1000 chars. So I added charToUpper and charToLower functions and it works as expected now. Here is the new code. I feel like the capitalize function could be better... comments welcome.
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
Upvotes: 0