Sage-Code Laboratory
index<--

Data Structures

Modern Fortran enable data to be organized using collections and structured data. In this article you can learn how to define and manipulate data structures.

Page bookmarks

Arrays

Arrays are multidimensional variables that contain more than one value where each value is accessed using one or more indices. First element along any dimension is at index 1 in contrast to C and Java where first element start from 0. Index can't have gaps.

Declaration

You can declare arrays of any type but all elements in a single array must have the same type. There are three notations for declaring array variables:

1. You can use dimension attribute after type, separated by comma to specify 1 or N fix dimensions in paranthesis. This will reserve the space necesary for multiple elements.


  ! 1D integer array of 9 elements
  integer, dimension(9) :: array_nae

  ! 2D real array, we call it matrix
  real, dimension(3, 3) :: matrix_name

2. You can specify the number of values using round paranthesis after the variable name. This is more intuitive and not so verbose. Whatever method you use, the elements are not initialized.


  ! An array of 9 real elements
  real :: array_name(9)

  ! A matrix of 9 integer elements
  integer :: matrix_name(3,3)

3. You can specify lower and upper bounds for indexes. In special cases this could be useful. This method is rarely used by advanced developers to create custom index that start with other than 1, for example: a yearly schedule.


  ! Custom lower and upper bounds
  real :: zeroid_array(0:9)

  ! Negative index is also possible
  real :: negid_array(-5:5)

Initialization

You can initialize all the values of one entire array in a single statement. Actually you should do this only if you do not use other method to give initial value to all elements.

Array literals

Array literal is enclosed in square brackets [...], literal values are separated by comma. The same notation is used for constructors to initialize the array. Notice that [] is not used for index, and array dimension but (). Very strange!


  ! using array literal
  real :: my_array(10) = [1,2,3,4,5,6,7,8,9,10]

Array constructor

To avoid loops for Array initialization we can use a special expression that generate a series of nambers, called range. A range expression is enclosed in round brackets.

example: arrays.f95
!fortran example
program arrays
  ! 1D integer array
  integer, dimension(3) :: A
  integer i

  ! give value to all elements
  A=[(i, i=1,3)]

  ! print all values
  print "(3i4)", A(1:3)
end program    
console
~>./arrays
   1   2   3
~>

Memory Model

In memory elements are organized linear, columns first. We say that matrix are in column major order, in contrast to C where row major order is used. It is important to use the right order when you travers a 2d array.

column major order

Column Major Order

Nested loops can be used to access every element in the matrix. When you do this kind of loops, pay atention to order of indexes. In fortran you must use second index for outer loop and first index for inner loop. For large matrix this can improve performance.

example: matrix.f95
program matrix
  ! 2D integer array
  integer, dimension(3, 3) :: M

  ! give value to all elements
  do j=1,3
    do i=1,3
       M(i,j)=j*10+i
    end do
  end do
  ! output matrix elements
  print "(3i4)", M ! 3 columns
  print "(9i4)", M ! 9 columns
end program
console

>./matrix
  11  12  13
  21  22  23
  31  32  33
  11  12  13  21  22  23  31  32  33

Slicing

A powerful feature of the Fortran language is its built-in support for array operations. You can perform operations part of an array using array slicing notation: A(x:y) for array or M(x:y, a:b) for a matrix. Let's analyze the example:

example: slices.f95
!fortran example
program slices
  implicit none

  integer :: i, j
  integer :: m(9, 9) 

  ! Implied do loop constructor
  do i=1,9
    m(i,:)=[(j*10+i, j = 1, 9)]
  end do
  print "(9i4)", m

  ! Set value elements by range
  m(:,:) = 0  
  m(1:5,1:5) = 1  
  m(6:,6:)   = 2  
  print *, repeat("_",36)
  ! Print new values
  print "(9i4)", m
end program
console

~>./slices
11  12  13  14  15  16  17  18  19
21  22  23  24  25  26  27  28  29
31  32  33  34  35  36  37  38  39
41  42  43  44  45  46  47  48  49
51  52  53  54  55  56  57  58  59
61  62  63  64  65  66  67  68  69
71  72  73  74  75  76  77  78  79
81  82  83  84  85  86  87  88  89
91  92  93  94  95  96  97  98  99
__________________________________
1   1   1   1   1   0   0   0   0
1   1   1   1   1   0   0   0   0
1   1   1   1   1   0   0   0   0
1   1   1   1   1   0   0   0   0
1   1   1   1   1   0   0   0   0
0   0   0   0   0   2   2   2   2
0   0   0   0   0   2   2   2   2
0   0   0   0   0   2   2   2   2
0   0   0   0   0   2   2   2   2   

Dynamic Arrays

Arrays can be defined resizable, then allocate memory later. You can use allocatable attribute in declaration and then allocate function to reserve the required memory.

example: dynamic.f95

>program main
  implicit none
  ! define dynamic arrays
  integer, allocatable :: darray(:) 
  integer:: i = 0, d = 0 ! define the dimension
  print *, "dimension:", read *, d
  allocate(darray(d))  ! resize the arrays
  darray = [(i,i=1,d)] ! using constructor

  ! create a dynamic format and print
  print "("//trim(str(d))//"i4)" , darray
  deallocate(darray)   ! remove from memory
contains
  ! "Convert an integer to string."
  character(len=20) function str(k)
    integer, intent(in) :: k
    write (str, *) k
    str = adjustl(str)
  end function
end program

Strings

In Fortran, strings are defined using character type, follow (len) argument. An array can be defined as fixed size or allocatable. For allocatable strings you can allocate memory later after you can calculate the required capacity.

example: allocate.f95

>program main
   ! declare two empty strings
   character(len=5) :: string_one
   character(len=:), allocatable:: string_two 

   ! allocate memory for string two
   allocate(character(6) :: string_two)

   string_one = "first"
   string_two = "second"

   ! concatenate and print
   print *, string_one //" "// string_two
end program

Derived Types

Fortran has 5 built-in data types that can be used to define derived types. These are special form of data types that can encapsulate multipe values of different built-in types as well as other derived types. Derived types are like struct or classes.

Symple syntax

Most of the time a type has only a name and a group of variables called "attributes" or sometimes "fields". In Fortran the "derived types" are similar to "classes" you encounter in OOP languages.


type :: identifier_name 
  ! attributes
  ...
end type

Advanced syntax

You can define more advanced data types. We try to show you many optional features by using square brackets []. However in next examples you will realize, square brackets are not actually part of syntax. It just show that a feature is optional.


type ,attribute-list :: identifier_name [("parameter list")]
  ["parameterized definition"]
  ["private statements or sequence statements"]
  ["member variables (fields or attribute)"]
contains
  ["type bound subprograms"]
end type

Program Example

In this example we create 2 items. Each item is a variable declared from a derived type "wheel" and initialize the attributes using symbol "%". Notice this symbol is not modulo, like in C. For module we use built-in function mod() instead of "%".

example: wheels.f95

program objects
  character(len=*), parameter :: fmt = "(i3,i3,f6.2,' ',a,' ',a)"
  ! derived data type
  type :: wheel
    integer :: spokes, diameter
    real :: width
    character(len=4):: breaks 
    character(len=15) :: material
  end type
  ! define and create two wheels 
  type(wheel) :: mountain, road
  mountain = wheel(32, 24, 2.2, "disk", "steel")
  ! second weel attributes
  road%diameter=26; road%width=1.5
  road%spokes=22; road%breaks="rim"
  road%material="aluminium"
  ! list the two weels with attributes
  print fmt, road
  print fmt, mountain
end program
console

~>gfortran wheels.f95 -o wheels
~>./wheels 
 22 26  1.50 rim  aluminium      
 32 24  2.20 disk steel 
~>  

Pointers

A pointer is a new type of variable which can be a reference the data stored by other variable (called target) or area of dynamically allocated memory. Pointers are vary useful to create advanced data structures and complex algorithms.

Pointer Target

Pointers are best thought of as variables which are dynamically reffering to or are associated with a particular target. Pointers are pointing to something. Valid pointer targets include:

Note: Pointers may take advantage of dynamic storage but do not require the allocatable attribute. The ability to allocate and deallocate storage is an native property of pointer variables.

!declare target variable
integer, target :: var

Declare Ponter

A pointer is not just a number. It is a structure, it has data type and the phisical memory address of the target. Therefore after declare a pointer need to be associated to a target.

!declare a null pointer
integer, pointer :: ptr

! associate pointer to var
ptr => var    

Using a pointer

A pointer can hold a reference to one variable then switch to another variable. The pointer is used almost like a variable. It can be modified using regular operators. Take a look at example and read the comments:

example: point.f95
program point
  real, target :: a, b
  real, pointer:: pa
  if (associated(pa)) then
     print *, 'test 1: pa is currently associated'
  else
     print *, 'test 1: pa is currently not associated'
  endif
  a = 1.2; b = 2.2 
  pa => a ! associate pointer pa to variable a
  print *, "pa = ", pa
  if (associated(pa)) then
     print *, 'test 2: pa is currently associated to a'
  else
     print *, 'test 2: pa is currently not associated'
  endif
  nullify (pa)
  if (associated(pa)) then
     print *, 'test 3: pa is currently associated'
  else
     print *, 'test 3: pa is currently not associated'
  endif
  pa => b ! associate pointer pa to variable b
  if (associated(pa)) then
     print *, 'test 4: pa is currently associated to b'
  else
     print *, 'test 4: pa is currently not associated'
  endif
  ! modify the variable b using it's alias (pa)
  pa = pa + 1 
  print *, "b = ", b
end program
console
./point
 test 1: pa is currently not associated
 pa =    1.20000005    
 test 2: pa is currently associated to a
 test 3: pa is currently not associated
 test 4: pa is currently associated to b
 b =    3.20000005  

Read next: Using Modules