기본 & 프로그램 구조
프로그램 구조 & Hello World
모든 Fortran 프로그램은 'program NAME'으로 시작하여 'end program NAME'으로 끝납니다. 'implicit none'은 현대 Fortran에서 필수 — 모든 변수의 명시적 선언을 강제(없으면 Fortran은 i-n으로 시작하는 변수는 정수, 다른 것은 실수로 암묵적 타이핑을 사용, 이는 버그의 주요 원인). 'contains' 블록은 실행 코드를 내부 프로시저(프로그램 내에 정의된 서브루틴/함수)와 분리. 주석은 '!'로 시작. 자유 소스 형식(Fortran 90+)은 .f90 확장자 사용; 열은 중요하지 않음. gfortran/ifort로 컴파일.
program hello
! A complete Fortran program structure
implicit none
! declarations go here
integer :: status = 0
print *, "Hello, World!" ! list-directed output to stdout
! executable statements
call do_work(status)
print *, "Exit status: ", status
contains
subroutine do_work(st)
integer, intent(out) :: st
st = 0
print *, "Working..."
end subroutine do_work
end program hello
! Compile: gfortran hello.f90 -o hello
! Run: ./hello변수 & 내장 타입
Fortran에는 5개의 내장 타입: integer, real, complex, character, logical. 'kind'는 정밀도/크기 선택 — 64비트에는 kind=8 사용(또는 이식성을 위해 selected_real_kind/iso_fortran_env 사용). 실수 리터럴은 kind 접미사 필요: 3.14_8(3.14만이 아님). double precision은 real(kind=8)의 레거시 구문. 복소수 리터럴은 (real, imag) 형식 사용. 논리값은 .true. / .false.(점 포함). 문자열은 len=:와 allocatable(지연 길이, Fortran 2003+)으로 선언하지 않는 한 고정 'len'을 가짐. 조용한 정밀도 손실을 피하기 위해 항상 일치하는 kind 접미사로 초기화.
program variables
implicit none
! Integer types
integer :: count = 0
integer(kind=8) :: big = 9223372036854775807_8 ! 64-bit
! Real types
real :: x = 3.14 ! default (often 32-bit)
real(kind=8) :: y = 2.718281828459045_8 ! double precision
double precision :: z = 1.0d0
! Complex
complex :: c = (1.0, 2.0) ! 1 + 2i
complex(kind=8) :: cw = (1.0_8, 2.0_8)
! Character
character(len=20) :: name = "Alice"
character(len=:), allocatable :: flexible ! deferred length
! Logical
logical :: flag = .true.
! Print all
print *, count, big
print *, x, y, z
print *, c, cw
print *, name, flag
end program variables상수 & 매개변수
상수는 'parameter' 속성을 사용하고 선언 시 초기화되어야 함. 수정 불가 — 컴파일러가 최적화하고 인라인 가능. 관례상 SCREAMING_SNAKE_CASE 사용. character(*)은 '초기화에서 길이 가져오기' 의미(문자열 상수에 편리). 매개변수는 배열 크기, 물리 상수, 열거형 같은 정수 코드에 일반적으로 사용. Fortran 2003+은 적절한 ENUM 타입도 있지만 매개변수 정수가 관용적 선택. 매개변수는 배열 차원 선언과 다른 상수 식 컨텍스트에서 사용 가능.
program constants
implicit none
! Named constants via 'parameter' attribute
integer, parameter :: MAX_SIZE = 100
real, parameter :: PI = 3.14159265
real, parameter :: E = 2.718281828
character(*), parameter :: APP_NAME = "MyApp"
! Using parameters
real :: arr(MAX_SIZE)
arr = 0.0
print *, APP_NAME, " size=", MAX_SIZE
print *, "Circumference: ", 2.0 * PI * 5.0
! Enum-like via parameter
integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
integer :: day = MONDAY
print *, "Day code: ", day
end program constants연산자 & 표현식
Fortran 연산자: 산술(+ - * / **), **는 거듭제곱(Fortran 고유). 정수 나눗셈은 0으로 향해 잘림 — 진정한 나눗셈에는 real(a)/b 사용. 두 가지 관계 구문: 현대(< > == /= <= >=)와 레거시(.lt. .gt. .eq. .ne. .le. .ge.). 논리: .and. .or. .not. .eqv.(동등) .neqv.(배타적 또). 문자열 연결은 // 사용; trim()은 후행 공백 제거(Fortran은 고정 길이 문자열을 공백으로 채움). mod vs modulo: mod는 잘린 나눗셈 부호 따르고, modulo는 내림 나눗셈 따름 — 음수 피연산자에 대해 다름.
program operators
implicit none
integer :: a = 17, b = 5
real :: x = 2.0
! Arithmetic
print *, a + b, a - b, a * b ! 22 12 85
print *, a / b ! 3 (integer division!)
print *, real(a) / b ! 3.4 (cast to real)
print *, a ** 2 ! 289 (exponentiation)
print *, mod(a, b) ! 2 (modulo)
print *, modulo(a, b) ! 2 (differs for negatives)
! Relational (both forms work)
print *, a > b, a < b ! T F
print *, a .gt. b, a .lt. b ! T F (old form)
print *, a == b, a /= b ! F T
! Logical
print *, (a > 0) .and. (b > 0) ! T
print *, (a > 0) .or. (b < 0) ! T
print *, .not. (a > 0) ! F
print *, (a > 0) .eqv. (b > 0) ! T (equivalence)
! String concatenation
character(10) :: s1 = "Hello", s2 = "World"
print *, trim(s1) // " " // trim(s2) ! Hello World
end program operators내장 함수 & 수학
Fortran은 풍부한 내장(intrinsic) 함수 세트 보유. 수학: abs, sqrt, exp, log(자연), log10, sin/cos/tan/asin/acos/atan/atan2, sinh/cosh/tanh. 반올림: int(잘림), nint(가장 가까운), floor, ceiling. 변환: real(), int(), cmplx(). 조회: size, shape, huge(최대값), tiny(최소 양수), kind. 모든 삼각 함수는 라디안 사용. atan2(y, x)는 올바른 사분면의 각도 반환(atan과 달리). 범위 제한 확인에는 huge/tiny 사용. 내장 함수는 기본적 — 배열에서 자동으로 요소별 작동.
program intrinsics
implicit none
real :: x = -3.7, y = 2.5
! Math functions
print *, abs(x) ! 3.7
print *, sqrt(2.0) ! 1.414...
print *, exp(1.0) ! 2.718... (e^x)
print *, log(2.0) ! 0.693... (natural log)
print *, log10(1000.0) ! 3.0
print *, sin(3.14159/2) ! 1.0
print *, cos(0.0) ! 1.0
print *, atan2(1.0,1.0) ! 0.785... (pi/4)
! Rounding
print *, int(x) ! -3 (truncate toward zero)
print *, nint(x) ! -4 (nearest integer)
print *, floor(x) ! -4 (toward -inf)
print *, ceiling(x) ! -3 (toward +inf)
print *, abs(x), max(x, y), min(x, y) ! 3.7 2.5 -3.7
! Type conversion
print *, real(5) ! 5.0
print *, int(3.9) ! 3
! Inquiry
real :: arr(10)
print *, size(arr) ! 10
print *, huge(1) ! 2147483647
print *, tiny(1.0) ! smallest positive real
end program intrinsics제어 흐름
If...Then...Else
블록 IF: 'if (cond) then ... else if (cond) then ... else ... end if'. 각 분기는 'then' 필요(마지막 else 제외). 논리 IF는 한 줄: 'if (cond) statement'('then'/'end if' 없음). 조건은 관계 연산자(< > == /= <= >= 또는 .lt. .gt. .eq. .ne. .le. .ge.)를 .and. .or. .not.과 결합. 'stop'은 프로그램 종료(선택적으로 메시지/코드와 함께). 산술 IF(if (x) label1, label2, label3)는 Fortran 2018에서 삭제됨 — 사용 금지. 선언되지 않은 변수를 잡기 위해 항상 'implicit none' 사용.
program if_demo
implicit none
integer :: score = 85
character(1) :: grade
! Multi-branch if/else if/else
if (score >= 90) then
grade = 'A'
else if (score >= 80) then
grade = 'B'
else if (score >= 70) then
grade = 'C'
else if (score >= 60) then
grade = 'D'
else
grade = 'F'
end if
print *, "Score ", score, " -> Grade ", grade
! Logical if (single statement, no 'then')
if (score < 0 .or. score > 100) stop "Invalid score"
! Arithmetic if (OBSOLETE - avoid)
! if (x) 10, 20, 30 ! jump to label based on sign
end program if_demoSelect Case (Switch)
select case는 Fortran의 switch 문. 케이스는 단일 값(case (3)), 목록(case (1, 3, 5)), 또는 범위(case (4:5)는 4에서 5 포함)일 수 있음. case default는 폴백. C와 달리 fall-through 없음 — 각 분기는 독립적이고 하나만 실행. integer, character, logical 타입에서 작동(real은 안 됨). 문자 범위는 ASCII 순서 사용('A':'Z'). 부동소수점 비교에는 if/else 사용. integer/char 디스패치에 긴 if/else if 체인보다 select case가 더 효율적(컴파일러가 점프 테이블 사용 가능).
program case_demo
implicit none
integer :: day = 3
character(1) :: ch = 'A'
character(10) :: day_name
! Integer select case
select case (day)
case (1)
day_name = "Monday"
case (2)
day_name = "Tuesday"
case (3)
day_name = "Wednesday"
case (4:5)
day_name = "Thu/Fri"
case (6:7)
day_name = "Weekend"
case default
day_name = "Invalid"
end select
print *, day_name
! Character select case (case-insensitive via pre-upper)
select case (ch)
case ('A':'Z')
print *, "Uppercase letter"
case ('a':'z')
print *, "Lowercase letter"
case ('0':'9')
print *, "Digit"
case default
print *, "Other"
end select
! Logical select case
select case (day > 5)
case (.true.)
print *, "Weekend!"
case (.false.)
print *, "Weekday"
end select
end program case_demoDo 루프 (카운트)
카운트 DO 루프: 'do var = start, end, step'(step 기본값 1). 루프는 var <= end(양수 step) 또는 var >= end(음수 step)인 동안 실행. var은 각 반복 후 증가. implied-do 구문 [(expr, var=start,end)]은 배열 초기화와 I/O 목록에 강력. 명명된 루프(outer: do ... end do outer)는 특정 중첩 수준으로 cycle/exit 타겟팅 가능. 루프 변수는 자동으로 정의; Fortran에서는 루프 후 최종 값을 유지. 루프 본체 내에서 루프 변수 수정 피하세요.
program do_loops
implicit none
integer :: i, j, total
! Basic counted loop: do var = start, end [, step]
do i = 1, 5
print *, i ! 1 2 3 4 5
end do
! With step
do i = 10, 1, -1 ! countdown
print *, i
end do
do i = 0, 100, 25 ! 0 25 50 75 100
print *, i
end do
! Implied-do (inline, for array init / I/O)
integer :: arr(5) = [(i**2, i=1,5)] ! 1 4 9 16 25
print *, arr
print *, (i, i=1,3) ! 1 2 3
! Nested loops with labels (for cycle/exit targeting)
total = 0
outer: do i = 1, 3
inner: do j = 1, 3
total = total + i*j
end do inner
end do outer
print *, "Total: ", total
end program do_loopsDo While & 무한 루프
do while (cond) ... end do는 사전 테스트 루프(각 반복 전 조건 확인; 0번 실행 가능). 사후 테스트 동작에는 do ... if (cond) exit ... end do 사용. 단독 'do ... end do'는 무한 루프 — exit 문이 있어야 함(그렇지 않으면 무한). 'exit'는 가장 안쪽 루프(또는 명명된 루프)를 떠남. 명명된 루프(factorial_loop:)는 exit가 외부 루프 타겟팅 가능. 반복 횟수를 알 수 없고 조건에 의존할 때 do while 사용; 횟수를 미리 알 때 카운트 do 사용.
program while_demo
implicit none
integer :: n, count
real :: x, sum
! do while: pre-test loop
n = 1024
count = 0
do while (n > 1)
n = n / 2
count = count + 1
end do
print *, "log2(1024) = ", count ! 10
! Infinite loop with exit
sum = 0.0
do
read(*, *) x
if (x < 0) exit ! leave loop
sum = sum + x
end do
print *, "Sum: ", sum
! do ... end do with conditional exit
n = 1
factorial_loop: do
if (n > 10) exit factorial_loop
print *, n, factorial(n)
n = n + 1
end do factorial_loop
contains
recursive function factorial(n) result(f)
integer, intent(in) :: n
integer :: f
if (n <= 1) then
f = 1
else
f = n * factorial(n-1)
end if
end function factorial
end program while_demoCycle, Exit & 루프 제어
cycle은 현재 반복의 나머지를 건너뛰고 다음으로 점프(C/Python의 'continue'처럼). exit는 루프를 완전히 빠져나감('break'처럼). 둘 다 기본적으로 가장 안쪽 루프 타겟팅, 하지만 명명된 루프(search: do ... end do search)로 외부 루프 타겟팅 가능: 'exit search' 또는 'cycle search'. 중첩 루프를 깔끔하게 빠져나오는 데 필수. 필터링(원치 않는 반복 건너뛰기)에는 cycle, 조기 종료(검색 찾음, 오류 감지)에는 exit 사용. 명명된 루프는 중첩 제어 흐름을 명시적이고 읽기 쉽게 만듦.
program loop_control
implicit none
integer :: i, j
! cycle: skip to next iteration (like 'continue' in C)
do i = 1, 10
if (mod(i, 2) == 0) cycle ! skip even numbers
print *, i ! 1 3 5 7 9
end do
! exit: break out of loop (like 'break' in C)
do i = 1, 100
if (i * i > 50) then
print *, "Stopped at i=", i
exit
end if
end do
! Named loops: cycle/exit can target outer loops
search: do i = 1, 5
do j = 1, 5
if (i + j == 7) then
print *, "Found: ", i, "+", j, "= 7"
exit search ! break out of OUTER loop
end if
end do
end do search
! Early exit from a search
integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
do i = 1, size(arr)
if (arr(i) == 9) then
print *, "Found 9 at index ", i
exit
end if
end do
end program loop_control배열 & 벡터 연산
배열 선언 & 초기화
Fortran 배열은 기본적으로 1-인덱스(하한 = 1), 하지만 사용자 정의 하한 지정 가능: a(0:4)는 인덱스 0..4. 다차원 배열은 (행, 열) 순서 — 열 우선 저장(첫 번째 인덱스가 메모리에서 가장 빨리 변화). 배열 생성자 [1,2,3]이나 implied-do [(expr, i=start,end)]로 초기화. reshape는 1D 목록에서 다차원 배열 채우기. size()는 총 요소 반환; lbound/ubound는 하한/상한 반환. shape()는 형상을 1D 배열로 반환. 배열은 '전체 배열' — 명시적 루프 없이 할당하고 연산 가능.
program array_decl
implicit none
! Declaration with dimension
integer :: a(5) ! 1D, indices 1..5
integer :: b(0:4) ! 1D, indices 0..4 (custom lower bound)
real :: c(3, 4) ! 2D, 3 rows x 4 cols
real, dimension(10) :: d ! using dimension attribute
! Initialization at declaration
integer :: x(5) = [1, 2, 3, 4, 5]
integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
integer :: z(5) = 0 ! all zeros
real :: m(2,2) = reshape([1,2,3,4], [2,2])
! Allocation later
print *, size(x), lbound(x), ubound(x) ! 5 1 5
print *, size(c, dim=1) ! 3 (rows)
print *, size(c, dim=2) ! 4 (cols)
! Array of characters
character(10) :: names(3) = ["Alice", "Bob", "Carol"]
print *, names(2) ! Bob
end program array_decl배열 섹션 & 벡터 첨자
배열 섹션(슬라이싱)은 a(start:end:stride) 구문 사용 — 모든 부분 선택 사항. stride는 음수 가능(역순). 벡터 첨자는 인덱스 배열로 수집/분산 허용: a(idx)는 [a(idx(1)), a(idx(2)), ...] 반환. 섹션은 할당 가능: a(2:4) = [99,98,97]. 'where' 구문은 배열 수준 조건부 할당(numpy where처럼). Fortran의 배열 연산은 벡터화됨 — 요소별 연산에 명시적 루프 불필요. 이것이 수치 코드를 위한 Fortran의 킬러 기능: 깔끔하고 수학 같은 구문을 컴파일러가 자동 벡터화.
program array_sections
implicit none
integer :: a(10) = [(i, i=1,10)]
integer :: b(5)
integer :: idx(3) = [2, 5, 7]
! Array sections (slicing): a(start:end[:stride])
print *, a(3:7) ! 3 4 5 6 7
print *, a(1:10:2) ! 1 3 5 7 9 (stride 2)
print *, a(:5) ! 1 2 3 4 5 (start defaults to 1)
print *, a(6:) ! 6 7 8 9 10 (end defaults to ubound)
print *, a(::3) ! 1 4 7 10
print *, a(10:1:-1) ! 10 9 8 ... 1 (reverse)
! Vector subscript (gather)
b = a(idx) ! b = [a(2), a(5), a(7)] = [2, 5, 7]
print *, b
! Assigning to a section
a(2:4) = [99, 98, 97]
print *, a(1:5) ! 1 99 98 97 5
! 2D sections
real :: m(4,4) = 0.0
m(2:3, 2:3) = 1.0 ! set 2x2 sub-block
print *, m(2,2), m(3,3) ! 1.0 1.0
! Where construct (masked array assignment)
where (a > 50) a = 0 ! set elements > 50 to 0
end program array_sections다차원 배열 & 행렬
Fortran은 배열을 열 우선으로 저장(첫 번째 인덱스가 메모리에서 가장 빨리 변화) — C와 반대. reshape는 열 우선 순서로 채우므로 reshape([1,2,3,4],[2,2])는 [[1,3],[2,4]] 제공. matmul(A,B)은 진정한 행렬 곱셈(선형 대수); A*B는 요소별(Hadamard) — 같지 않음! transpose(A)는 전치 반환. 축소: sum, product, maxval, minval, maxloc, minloc, count — 모두 dim=을 지원하여 한 축으로 축소. 고성능을 위해 열 우선 순서로 루프 작성(첫 번째 인덱스로 가장 안쪽 루프)하여 캐시 친화적으로.
program matrices
implicit none
real :: A(3,3), B(3,3), C(3,3)
integer :: i, j
! Initialize with implied-do
A = reshape([(real(i), i=1,9)], [3,3])
! A = 1 4 7
! 2 5 8
! 3 6 9 (column-major fill!)
! Identity matrix
B = 0.0
do i = 1, 3
B(i,i) = 1.0
end do
! Matrix multiplication (intrinsic)
C = matmul(A, B) ! A * I = A
print *, C(1,1), C(2,2) ! 1.0 5.0
! Transpose
print *, transpose(A)(1,:) ! 1 2 3 (first row of A^T)
! Element-wise operations
C = A + B ! element-wise add
C = A * 2.0 ! scalar multiply
C = A * B ! element-wise (NOT matmul!)
! Array reduction along a dimension
print *, sum(A, dim=1) ! column sums: 6 15 24
print *, sum(A, dim=2) ! row sums: 12 15 18
print *, maxval(A) ! 9.0
print *, maxloc(A) ! 3 3 (location of max)
! Reshape
integer :: flat(6) = [1,2,3,4,5,6]
integer :: mat(2,3)
mat = reshape(flat, [2,3])
end program matrices할당 가능 배열 (동적)
allocatable 배열은 Fortran에서 동적 메모리를 하는 현대적 방법 — 포인터보다 안전(메모리 누수 없음, 범위 종료 시 자동 할당 해제). allocatable 속성과 지연 형상((:, (:,:), 등)으로 선언. allocate()에 stat=로 오류 잡기(항상 확인!). deallocate()로 명시적 해제. Fortran 2003+은 할당 시 자동 재할당 지원: flex = [flex, 4]가 배열 성장. allocated()는 현재 할당되었는지 확인. 컴파일러가 추적하고 자동 해제하므로 동적 배열에는 포인터보다 allocatable이 선호 — 누수 없음, 매달린 포인터 없음.
program alloc_demo
implicit none
integer, allocatable :: arr(:), matrix(:,:)
integer :: n, m, i, stat
! Get size from user
print *, "Enter size:"
read(*, *) n
m = n * 2
! Allocate
allocate(arr(n), matrix(n, m), stat=stat)
if (stat /= 0) then
print *, "Allocation failed!"
stop 1
end if
! Use the arrays
arr = [(i, i=1, n)]
matrix = 0.0
do i = 1, n
matrix(i, :) = i
end do
print *, size(arr), size(matrix, dim=2)
print *, allocated(arr) ! T
! Deallocate (or let it auto-deallocate at scope exit)
deallocate(arr, matrix)
print *, allocated(arr) ! F
! Automatic reallocation on assignment (Fortran 2003)
integer, allocatable :: flex(:)
flex = [1, 2, 3] ! auto-allocates to size 3
flex = [flex, 4, 5] ! reallocates to size 5: 1 2 3 4 5
print *, flex
deallocate(flex)
end program alloc_demo배열 내장 함수
Fortran의 배열 내장 함수는 초능력. 축소: sum, product, maxval, minval, maxloc(최대값 인덱스), minloc, count(true 개수), any(존재), all(모두). 모두 mask=로 조건부 축소 지원. pack()은 mask가 true인 요소 수집(numpy compress처럼); unpack()은 분산. cshift/eoshift는 배열 회전(순환 vs 끝 오프). merge(a, b, mask)는 요소별 선택. 참고: Fortran에는 내장 정렬 없음 — 직접 작성(또는 라이브러리 사용). 이 내장 함수는 기본적이고 벡터화 가능하여 Fortran 코드를 깔끔하고 빠르게 만듦.
program array_funcs
implicit none
integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
! Wait, let's fix size
integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
integer :: c(5) = [10, 20, 30, 40, 50]
logical :: mask(5) = [.true., .false., .true., .false., .true.]
! Inquiry
print *, size(b) ! 8
print *, shape(b) ! 8
print *, lbound(b), ubound(b) ! 1 8
! Reductions
print *, sum(b) ! 31
print *, product(c) ! 120000000
print *, maxval(b), minval(b) ! 9 1
print *, maxloc(b) ! 6 (index of max)
print *, minloc(b) ! 2 (index of min, first occurrence)
print *, count(b > 3) ! 5 (number of true elements)
print *, any(b > 8) ! T (at least one)
print *, all(b > 0) ! T (all of them)
! With mask
print *, sum(b, mask=b > 3) ! sum of elements > 3
print *, pack(b, b > 3) ! compact array of elements > 3
print *, unpack([1,2], mask, 0) ! spread values per mask
! Manipulation
print *, cshift(b, 2) ! circular shift left by 2
print *, eoshift(b, 2) ! end-off shift left by 2 (fills 0)
print *, merge(b, c, mask) ! element-wise: b where mask true, else c
! Sorting (Fortran 2003+)
integer :: sorted(8)
sorted = b
call sort_array(sorted) ! custom sort (no built-in sort)
end program array_funcs문자열 & 문자 처리
문자 선언 & 길이
Fortran 문자열은 기본적으로 고정 길이 — 더 짧은 문자열은 선언된 길이를 채우기 위해 공백으로 패딩. character(N) 또는 character(len=N)은 길이 N 선언. character(*)은 컨텍스트에서 길이 가져오기(매개변수 초기화나 더미 인수). character(:), allocatable은 지연 길이 동적 문자열 활성(Fortran 2003+) — 문자열이 할당 시 재할당. len()은 선언된 길이 반환; len_trim()은 후행 공백 없는 길이 반환. trim()은 후행 공백 없는 문자열 반환(하지만 결과는 컨텍스트에서 여전히 고정 길이). 가변 길이 텍스트 처리에는 allocatable 지연 길이 문자열 사용.
program char_decl
implicit none
! Fixed-length strings
character(10) :: s1 = "Hello"
character(len=20) :: s2 = "World"
character(20) :: s3 ! len= keyword optional
! Deferred-length (allocatable) - Fortran 2003+
character(:), allocatable :: flex
flex = "Dynamic" ! len=7
flex = "Now longer string" ! reallocates to len=18
print *, len(flex) ! 18
! Array of strings
character(15) :: names(3) = ["Alice", "Bob", "Carol"]
! Single character
character(1) :: ch = 'A'
character :: ch2 = 'B' ! len=1 default
print *, s1 ! "Hello " (padded to 10)
print *, trim(s1) ! "Hello" (no padding)
print *, len(s1), len_trim(s1) ! 10 5
print *, names(2) ! "Bob" (padded)
end program char_decl문자열 연결 & 연산
문자열 연결은 // 연산자 사용. repeat(s, n)은 문자열을 n번 반복. 부분 문자열은 s(start:end) 사용 — 1-인덱스, 양 끝 포함(Python과 달리). s(8:)은 위치 8에서 끝까지; s(:5)는 시작에서 위치 5까지. index(s, sub)는 sub의 첫 번째 발생 위치 반환(없으면 0, 대소문자 구분). scan(s, set)은 set에 있는 첫 번째 문자 위치 반환; verify(s, set)은 set에 없는 첫 번째 문자 반환. adjustl/adjustr은 선행/후행 공백 이동. Fortran 문자열은 C처럼 null 종결이 아님 — 길이가 별도로 추적.
program string_ops
implicit none
character(20) :: first = "John", last = "Doe"
character(50) :: full
! Concatenation with //
full = first // " " // last ! "John Doe"
print *, trim(full)
! Repeat
print *, repeat("-", 30) ! 30 dashes
! Substring (1-indexed, inclusive)
character(20) :: s = "Hello, World!"
print *, s(1:5) ! "Hello"
print *, s(8:12) ! "World"
print *, s(8:) ! "World!" (to end)
print *, s(:5) ! "Hello" (from start)
! Index (find substring)
print *, index(s, "World") ! 8 (position, 0 if not found)
print *, index(s, "world") ! 0 (case-sensitive)
print *, scan(s, "aeiou") ! 2 (first vowel position)
print *, verify(s, "abcdefg") ! 1 (first char NOT in set)
! Length
print *, len_trim(s) ! 13
print *, adjustl(s) ! left-justify
print *, adjustr(s) ! right-justify
end program string_ops내장 문자열 함수
iachar(c)는 문자의 ASCII 코드 반환; achar(i)는 역. (ichar/char은 프로세서 종속 — 이식성을 위해 iachar/achar 선호.) Fortran에는 내장 대소문자 변환 없음 — iachar/achar로 수동(A-Z는 65-90, a-z는 97-122, 차이는 32). 사전식 비교: lge/lgt/lle/llt(사전식으로 더 큰/작은)은 다른 길이의 문자열을 우아하게 처리. 내부 I/O(파일 대신 문자열로 읽기/쓰기)는 문자열과 숫자 간 변환의 관용적 방법: read(str, *) num과 write(str, fmt) num. I0 형식은 최소 너비 정수 제공(패딩 없음).
program string_funcs
implicit none
character(20) :: s = "Hello World"
integer :: i
character(1) :: ch
! Character code conversion
print *, iachar('A') ! 65 (ASCII code)
print *, achar(66) ! 'B' (from ASCII code)
print *, ichar('A') ! processor-dependent (use iachar for ASCII)
! Case conversion (manual - no built-in)
do i = 1, len_trim(s)
ch = s(i:i)
if (ch >= 'A' .and. ch <= 'Z') then
s(i:i) = achar(iachar(ch) + 32) ! to lowercase
end if
end do
print *, s ! "hello world"
! Comparison
print *, lge("apple", "banana") ! F (lexicographic >=)
print *, lgt("zebra", "apple") ! T
print *, lle("abc", "abcd") ! T (<=)
print *, llt("abc", "abd") ! T (<)
! String to number conversion (internal read)
character(20) :: num_str = "3.14159"
real :: pi_val
read(num_str, *) pi_val
print *, pi_val * 2 ! 6.28318
! Number to string (internal write)
character(20) :: out_str
integer :: n = 42
write(out_str, '(I0)') n ! I0 = minimal-width integer
print *, "Number: " // trim(out_str)
end program string_funcs문자열 서식
형식 사양은 문자열에 존재: '(I5, F8.2, A)'. I=정수, F=고정소수점 실수, E=지수, ES=과학(가수 1-10), A=문자, X=공백, /=줄바꿈. 너비 먼저(I5 = 너비 5), 그다음 선택적 .m for 최소 자릿수(I5.3). I0은 최소 너비(패딩 없음) 의미. ES는 적절한 과학 표기법(1.23E+6) 제공 vs E(0.12E+7). 반복: 3I4 = 각각 너비 4의 정수 3개. 형식의 문자열 리터럴: '("text")'. 형식은 문자열 변수, *(목록 지향, 컴파일러 선택), 또는 레이블(문장 번호)일 수 있음. 깔끔한 출력을 위해 정수에는 I0, 실수에는 F 또는 ES 선호.
program format_demo
implicit none
integer :: n = 42
real :: pi = 3.14159265
real :: big = 1234567.89
character(20) :: name = "Alice"
! Format specifiers
! Iw - integer, width w
! Iw.m - integer, width w, at least m digits
! Fw.d - fixed-point real, width w, d decimals
! Ew.d - exponential, width w, d decimals
! Aw - character, width w
! A - character, default width
! nX - n spaces
! / - newline
write(*, '(I5)') n ! " 42"
write(*, '(I5.3)') n ! " 042"
write(*, '(I0)') n ! "42" (minimal)
write(*, '(F10.4)') pi ! " 3.1416"
write(*, '(E12.4)') big ! " 0.1235E+07"
write(*, '(ES12.4)') big ! " 1.2346E+06" (scientific)
write(*, '(A10)') name ! " Alice"
write(*, '(A, I3)') "n=", n ! "n= 42"
! Multiple items
write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi
! Repeated format: 3I4 = three integers width 4
write(*, '(3I4)') 1, 2, 3 ! " 1 2 3"
! Newline and spacing
write(*, '("Name: ", A, /, "Age: ", I3)') name, n
! List-directed (default format)
print *, name, n, pi
end program format_demo파싱 & 토큰화
Fortran에는 내장 split/tokenize 없음 — index()와 부분 문자열로 수동 작성 필요. 패턴: index로 구분자 찾기, 부분 문자열로 토큰 추출, 구분자 지나기, 반복. 키=값 쌍의 경우 index로 '=' 찾고 키(이전)와 값(이후)으로 분할. trim()은 후행 공백 제거; adjustl()은 선행 공백 제거. 견고한 파싱을 위해 빈 토큰과 공백도 처리. 또는 구조화된 데이터에 형식 지정자로 내부 읽기 사용, 또는 파일처럼 문자열에서 읽기. split() 같은 라이브러리가 일부 Fortran 프레임워크에 존재하지만 표준은 아님.
program parse_demo
implicit none
character(100) :: line = "name=Bob,age=30,city=NYC"
character(50) :: token
integer :: pos, start, end
! Split by comma
start = 1
do
end = index(line(start:), ",")
if (end == 0) then
! last token
token = line(start:)
call process(trim(token))
exit
end if
token = line(start:start+end-2)
call process(trim(token))
start = start + end
end do
contains
subroutine process(t)
character(*), intent(in) :: t
integer :: eq_pos
eq_pos = index(t, "=")
if (eq_pos > 0) then
print *, "Key: ", trim(t(:eq_pos-1)), &
" Value: ", trim(t(eq_pos+1:))
end if
end subroutine process
end program parse_demo
! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYC프로시저: 함수 & 서브루틴
함수
함수는 값을 반환하고 식에 사용(수학 함수처럼). 현대 구문: 'function name(args) result(var)' — result 변수가 반환되는 것. intent(in)은 읽기 전용 인수 표시(컴파일러가 강제). 함수는 스칼라 또는 배열 반환 가능(입력의 size()로 출력 크기 지정). 함수는 PURE여야 함(부작용 없음) — 함수에서 전역 상태 수정이나 I/O 하지 마세요. 내부 프로시저('contains' 블록)는 호스트의 변수에 접근(호스트 연관). 외부 프로시저의 경우 인터페이스 블록으로 시그니처 지정.
program func_demo
implicit none
! Function declared in interface or as external
print *, add(3, 4) ! 7
print *, square(5) ! 25
print *, distance(0.0, 0.0, 3.0, 4.0) ! 5.0
contains
! Basic function with result clause
function add(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function add
! Function with result named same as function (legacy)
function square(x) result(y)
integer, intent(in) :: x
integer :: y
y = x * x
end function square
! Real function
function distance(x1, y1, x2, y2) result(d)
real, intent(in) :: x1, y1, x2, y2
real :: d
d = sqrt((x2-x1)**2 + (y2-y1)**2)
end function distance
! Array-valued function
function reverse(arr) result(rev)
integer, intent(in) :: arr(:)
integer :: rev(size(arr))
integer :: i, n
n = size(arr)
do i = 1, n
rev(i) = arr(n-i+1)
end do
end function reverse
end program func_demo서브루틴 & Intent
서브루틴은 'call'로 호출되고 값을 반환하지 않음 — 인수를 제자리에서 수정. 서브루틴 사용 시기: (1) 여러 인수 수정 필요, (2) 작업이 '계산'이 아닌 '명령', (3) 배열 값 결과 반환이 어색. intent 속성은 인수 방향 문서화 및 강제: intent(in) = 읽기 전용(할당 시 컴파일러 오류), intent(out) = 쓰기 전용(진입 시 정의되지 않음, 반환 전 설정 필요), intent(inout) = 읽기-쓰기. 항상 intent 지정 — 버그 잡고 최적화 활성화. 서브루틴은 전달된 배열 수정 가능(연속적이면 복사 없음).
program sub_demo
implicit none
integer :: x = 10, y = 20
real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
! Subroutines are called with 'call'
call swap(x, y)
print *, x, y ! 20 10
call scale_array(arr, 2.0)
print *, arr ! 2 4 6 8 10
call fill_zero(arr)
print *, arr ! 0 0 0 0 0
contains
subroutine swap(a, b)
integer, intent(inout) :: a, b ! read AND write
integer :: tmp
tmp = a
a = b
b = tmp
end subroutine swap
subroutine scale_array(a, factor)
real, intent(inout) :: a(:)
real, intent(in) :: factor
a = a * factor ! whole-array operation
end subroutine scale_array
subroutine fill_zero(a)
real, intent(out) :: a(:) ! write-only (output)
a = 0.0
end subroutine fill_zero
end program sub_demo순수 & 기본적 함수
pure 함수는 부작용 없음: I/O 없음, 전역 변수 수정 없음, stop 없음, 다른 pure 프로시저만 호출 가능. 컴파일러 최적화(병렬화, 공통 부분식 제거) 활성화하고 일부 컨텍스트(DO CONCURRENT 등)에서 필요. elemental 함수는 스칼라용으로 작성되지만 자동으로 배열에서 요소별 작동 — 한 번 작성, 둘 다 사용. pure elemental은 둘 다 결합. 진정한 수학 함수(부작용 없음)에는 pure 사용. 연산이 자연스럽게 배열에 요소별로 적용되는 경우(수학 함수, 변환)에는 elemental 사용. 컴파일러가 배열에서 elemental 호출을 자동 벡터화 가능.
program pure_demo
implicit none
real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
real :: b(5)
integer :: i
! Pure function: no side effects, no I/O
b = square_arr(a)
print *, b ! 1 4 9 16 25
! Elemental function: works on scalars AND arrays automatically
b = cube(a) ! applies cube() element-wise
print *, b ! 1 8 27 64 125
print *, cube(2.0) ! also works on scalar: 8.0
contains
! Pure: no side effects, no I/O, no stop, only pure calls
pure function square_arr(x) result(y)
real, intent(in) :: x(:)
real :: y(size(x))
y = x * x
end function square_arr
! Elemental: scalar signature, but works on arrays too
elemental function cube(x) result(y)
real, intent(in) :: x
real :: y
y = x * x * x
end function cube
! Pure elemental: both
pure elemental double precision function sq(x) result(y)
double precision, intent(in) :: x
double precision :: y
y = x * x
end function sq
end program pure_demo선택적 & 키워드 인수
선택적 인수는 호출자가 생략 가능. 프로시저 내에서 present(arg)로 인수가 제공되었는지 확인 — 부재 선택적 접근은 정의되지 않은 동작. 키워드 인수(name="value")는 인수를 어떤 순서로든 전달 가능하고 호출을 자체 문서화. 키워드 사용 후 모든 후속 인수도 키워드 사용해야 함. 선택적 인수는 시그니처에서 모든 필수 인수 후에 와야 함. 기본값은 present() 검사로 구현(Fortran에는 내장 기본 구문 없음). 키워드 + 선택적으로 유연한 API 가능: 호출자는 필요한 것만 지정.
program optional_demo
implicit none
! All arguments after the first optional must also be optional
print *, greet("Alice") ! Hello, Alice!
print *, greet("Bob", "Hi") ! Hi, Bob!
print *, greet("Carol", greeting="Hey") ! keyword argument
print *, greet(greeting="Welcome", name="Dave") ! all keywords
! With present() check
call log_msg("Starting up")
call log_msg("Error!", level=2)
call log_msg("Debug info", level=0, file="debug.log")
contains
function greet(name, greeting) result(msg)
character(*), intent(in) :: name
character(*), intent(in), optional :: greeting
character(50) :: msg
character(20) :: g
if (present(greeting)) then
g = greeting
else
g = "Hello"
end if
msg = trim(g) // ", " // name // "!"
end function greet
subroutine log_msg(message, level, file)
character(*), intent(in) :: message
integer, intent(in), optional :: level
character(*), intent(in), optional :: file
integer :: lvl
lvl = 1
if (present(level)) lvl = level
print *, "[L", lvl, "] ", trim(message)
end subroutine log_msg
end program optional_demo내부 & 재귀 프로시저
내부 프로시저('contains' 내)는 호스트 연관 — 호스트 프로그램의 변수를 읽고 수정 가능(클로저처럼). 호스트 상태가 필요한 헬퍼에 사용. 재귀 프로시저는 'recursive' 접두어로 선언 필요(Fortran 90/2003); Fortran 2018은 재귀를 기본으로. 상호 재귀의 경우 인터페이스 블록으로 전방 참조 선언. 재귀는 우아하지만 느릴 수 있음(함수 호출 오버헤드)하고 위험(깊은 재귀의 스택 오버플로우). 팩토리얼/피보나치의 경우 반복 버전이 더 빠르고 안전. 자연스럽게 재귀적인 문제(트리 순회, 분할 정복)에는 깊이가 제한된 재귀 사용.
program nested_demo
implicit none
integer :: counter = 0 ! host variable
! Internal procedures (in contains) access host variables
call increment
call increment
print *, counter ! 2
! Recursive function
print *, factorial(5) ! 120
print *, fib(10) ! 55
! Mutual recursion (needs forward declaration)
print *, is_even(4) ! T
contains
subroutine increment
counter = counter + 1 ! modifies host's counter
end subroutine increment
recursive function factorial(n) result(f)
integer, intent(in) :: n
integer :: f
if (n <= 1) then
f = 1
else
f = n * factorial(n-1)
end if
end function factorial
recursive function fib(n) result(f)
integer, intent(in) :: n
integer :: f
if (n < 2) then
f = n
else
f = fib(n-1) + fib(n-2)
end if
end function fib
! Mutual recursion with interface
recursive function is_even(n) result(r)
integer, intent(in) :: n
logical :: r
interface
recursive function is_odd(m) result(ro)
integer, intent(in) :: m
logical :: ro
end function is_odd
end interface
if (n == 0) then
r = .true.
else
r = is_odd(n-1)
end if
end function is_even
end program nested_demo모듈 & 캡슐화
모듈 기본 & 사용
모듈은 Fortran의 주요 캡슐화 메커니즘(common blocks와 외부 프로시저 대체). 모듈 파일 포함: (1) 선언(상수, 변수, 파생 타입), (2) 프로시저가 있는 'contains' 블록. 'use module_name'으로 임포트; 'use module_name, only: x, y'는 특정 엔티티만 임포트(권장 — 이름 공간 오염 방지). 모듈 변수는 영구적(정적)이고 모듈을 사용하는 모든 프로시저에서 공유. 모듈은 명시적 인터페이스 제공(컴파일러가 인수 타입 검사), 외부 프로시저와 달리. 모듈을 사용하는 파일 전에 항상 모듈 파일 컴파일. 모듈의 'implicit none'은 모든 프로시저에 전파.
! geometry.f90 - module file
module geometry
implicit none
private ! default: everything private
public :: circle_area, circle_perimeter, PI
! Module-level constants (persistent)
real, parameter :: PI = 3.14159265
contains
function circle_area(r) result(a)
real, intent(in) :: r
real :: a
a = PI * r * r
end function circle_area
function circle_perimeter(r) result(p)
real, intent(in) :: r
real :: p
p = 2.0 * PI * r
end function circle_perimeter
end module geometry
! main.f90 - using the module
program use_module
use geometry, only: circle_area, circle_perimeter, PI
implicit none
real :: r = 5.0
print *, "Area: ", circle_area(r) ! 78.5398
print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
print *, "PI: ", PI
end program use_module
! Compile: gfortran geometry.f90 main.f90 -o main접근 제어 (Public/Private)
접근 제어: 'private'는 엔티티를 모듈 내부로; 'public'은 내보냄. 모듈 수준에서 기본 설정 가능('private' 그다음 선택적으로 'public :: ...') — 이것이 모범 사례(명시적 인터페이스). 파생 타입 구성 요소는 타입 자체가 public이더라도 private 가능 — 호출자는 타입을 사용할 수 있지만 내부에 직접 접근 불가; 프로시저를 거쳐야 함. 'save'는 모듈 변수를 영속적으로(호출 간 값 유지) — 모듈 변수는 기본적으로 저장됨. 'final'은 소멸자 정의(객체가 범위를 벗어날 때 호출). 이 캡슐화는 프로시저를 통해 불변식이 강제되는 진정한 OOP를 가능하게.
module bank_account
implicit none
private ! default: everything private
! Explicitly export:
public :: account_t, deposit, withdraw, get_balance
! Derived type - can expose type but hide internals
type :: account_t
private ! components are private
real :: balance = 0.0
integer :: id = 0
contains
procedure :: balance => get_bal ! type-bound procedure
final :: cleanup ! destructor
end type account_t
! Module-level counter (private, not exported)
integer, save :: next_id = 1000
contains
! Constructor (factory function)
function create_account(initial) result(acc)
type(account_t) :: acc
real, intent(in) :: initial
acc%balance = initial
acc%id = next_id
next_id = next_id + 1
end function create_account
subroutine deposit(acc, amount)
type(account_t), intent(inout) :: acc
real, intent(in) :: amount
acc%balance = acc%balance + amount
end subroutine deposit
function get_bal(acc) result(b)
class(account_t), intent(in) :: acc
real :: b
b = acc%balance
end function get_bal
subroutine cleanup(acc)
type(account_t) :: acc
! cleanup code (e.g., log closure)
end subroutine cleanup
end module bank_account모듈의 파생 타입
모듈에 정의된 파생 타입은 가질 수 있음: 타입 바인딩 프로시저(procedure :: name => impl), 생성자(타입 이름으로 오버로드된 인터페이스), allocatable 구성 요소. 타입 바인딩 프로시저의 'class(keyword)'는 다형성 활성(실제 타입이 하위 클래스일 수 있음). 타입 바인딩 프로시저는 obj%method(args)로 호출 — OOP 구문. 타입 이름을 인터페이스로 오버로드하면 여러 생성자 가능(vector_from_array, vector_from_size). allocatable 구성 요소는 자동 할당/할당 해제. 이것이 현대 Fortran OOP: 모듈 시스템 내에서 캡슐화, 메서드, 생성자, 다형성.
module vector_mod
implicit none
private
public :: vector_t, vector_add, vector_scale
! Derived type with type parameters (Fortran 2003)
type :: vector_t
real, allocatable :: data(:)
integer :: length = 0
contains
procedure :: norm => vector_norm
procedure :: print => vector_print
end type vector_t
! Constructor interface (overloaded)
interface vector_t
procedure vector_from_array
procedure vector_from_size
end interface vector_t
contains
function vector_from_array(arr) result(v)
real, intent(in) :: arr(:)
type(vector_t) :: v
v%data = arr
v%length = size(arr)
end function
function vector_from_size(n, fill) result(v)
integer, intent(in) :: n
real, intent(in) :: fill
type(vector_t) :: v
allocate(v%data(n))
v%data = fill
v%length = n
end function
function vector_norm(self) result(n)
class(vector_t), intent(in) :: self
real :: n
n = sqrt(sum(self%data**2))
end function
subroutine vector_print(self)
class(vector_t), intent(in) :: self
print *, "Vector(len=", self%length, "): ", self%data
end subroutine
function vector_add(a, b) result(c)
type(vector_t), intent(in) :: a, b
type(vector_t) :: c
c%data = a%data + b%data
c%length = a%length
end function
function vector_scale(a, s) result(c)
type(vector_t), intent(in) :: a
real, intent(in) :: s
type(vector_t) :: c
c%data = a%data * s
c%length = a%length
end function
end module vector_mod제네릭 프로시저 & 오버로딩
제네릭 인터페이스는 임시 다형성(오버로딩) 제공: 하나의 이름이 인수 타입에 따라 다른 특정 프로시저로 디스패치. 'interface name / module procedure proc1, proc2 / end interface' 블록은 모든 특정 프로시저 나열. 컴파일러가 컴파일 타임에 인수 타입/순위로 일치하는 것 선택. 제네릭의 모든 특정 프로시저는 구별 가능한 시그니처(인수 타입으로 구별 가능)를 가져야 함 — 그렇지 않으면 모호성. 이것이 Fortran이 템플릿 없이 연산자/함수 오버로딩을 하는 방식. 제네릭 디스패치는 컴파일 타임에 해결(런타임 오버헤드 없음). 타입 간 균일 API 제공에 제네릭 사용.
module generics_mod
implicit none
private
public :: print_value, add
! Generic interface: one name, multiple specific procedures
interface print_value
module procedure print_int
module procedure print_real
module procedure print_str
module procedure print_int_array
end interface print_value
interface add
module procedure add_int
module procedure add_real
module procedure add_arrays
end interface add
contains
subroutine print_int(x)
integer, intent(in) :: x
print *, "Integer: ", x
end subroutine
subroutine print_real(x)
real, intent(in) :: x
print *, "Real: ", x
end subroutine
subroutine print_str(s)
character(*), intent(in) :: s
print *, "String: ", trim(s)
end subroutine
subroutine print_int_array(arr)
integer, intent(in) :: arr(:)
print *, "Array: ", arr
end subroutine
function add_int(a, b) result(c)
integer, intent(in) :: a, b
integer :: c
c = a + b
end function
function add_real(a, b) result(c)
real, intent(in) :: a, b
real :: c
c = a + b
end function
function add_arrays(a, b) result(c)
real, intent(in) :: a(:), b(:)
real :: c(size(a))
c = a + b
end function
end module generics_mod
program use_generics
use generics_mod
implicit none
call print_value(42) ! Integer: 42
call print_value(3.14) ! Real: 3.14
call print_value("Hello") ! String: Hello
call print_value([1,2,3]) ! Array: 1 2 3
print *, add(2, 3) ! 5
print *, add(2.5, 3.5) ! 6.0
end program use_generics연산자 오버로딩
연산자 오버로딩은 +, -, *, /, == 등이 파생 타입에서 어떻게 작동하는지 정의 가능. interface operator(+) / module procedure vec_add / end interface는 + 연산자를 함수에 바인딩. 이항 연산자의 경우 두 순서 모두 오버로드 가능(vec*scalar와 scalar*vec) 별도 프로시저로. assignment(=)는 할당 연산자 오버로드(프로시저는 intent(out) LHS와 intent(in) RHS를 가진 서브루틴). 이것은 수학 같은 구문 활성화: c = vec_add(a, b) 대신 c = a + b. 가독성을 향상시키는 수학 타입(벡터, 행렬, 복소수)에 연산자 오버로딩 사용. 명백하지 않은 의미에는 오버로딩 피하세요. 구조 생성자 vec3(x,y,z)는 파생 타입에 내장.