CPC782 - Fundamentos de Programação - nacad/coppe-ufrj
Download
Report
Transcript CPC782 - Fundamentos de Programação - nacad/coppe-ufrj
CPC782
Fundamentos de Programação
Introdução ao Fortran
aula 3/3
Renato N. Elias / Marcos A. D. Martins
material disponível em:
http://www.nacad.ufrj.br/~rnelias/fortran
Aula 3
Alocação dinâmica de memória
– ALLOCATABLE, ALLOCATE, DEALLOCATE e ALLOCATED
Ponteiros em Fortran90
Subprogramação
– SUBROUTINE, FUNCTION
– RECURSIVE, PURE, ELEMENTAL
Passagem de argumentos para subprogramas
– Formas tradicionais
– MODULE
Exercício proposto
Fazer um programa que calcule as coordenadas dos nós de uma
grade e grave as coordenadas obtidas em um arquivo de texto.
O programa deverá conter as seguintes linhas iniciais:
program Grade
real*8, parameter :: xmin
ymin
xmax
ymax
dx
dy
= 0.d0, &
= 0.d0, &
= 10.d0, &
= 10.d0, &
= 2.d0, &
= 2.d0
real*8, allocatable :: xyz(:,:)
! Desenvolva o algoritmo aqui
End program
Alocação Dinâmica de Memória
A alocação dinâmica de memória foi um grande avanço no Fortran a partir do padrão
Fortran90 pois permitiu um uso mais racional da memória do sistema;
A alocação dinâmica de memória permite ao programador decidir pela requisição de
uma quantidade de memória para uso em arrays somente quando esta se faz,
realmente, necessária;
Tal recurso também viabilizou a compilação e execução de programas; que antes se
utilizavam de alocação estática e que não eram viáveis devido ao excessivo
consumo de memória; em computadores com disponibilidade de recursos limitado;
A tarefa de gerenciamento de memória em Fortran77 era árdua e normalmente feita
através da manipulação explícita de grandes áreas de memória estaticamente
alocadas. Tais áreas eram mutuamente utilizadas por arrays de tipos distintos e
frequentemente era necessária alguma operação de manutenção dos espaços
vazios deixados. Além disso, o programador tinha de se certificar que a alocação
estática assumida era suficiente para o programa rodar.
Uma outra limitação da alocação estática era a necessidade de recompilação do
programa a cada vez que a quantidade de memória requerida fosse diferente
daquela existente no programa. Isso pode parecer algo simples para programas
caseiros, mas imaginem um programa comercial tendo que ser recompilado
simplesmente porque necessitou de mais memória.
Alocação Dinâmica de Memória - Curiosidades
Quanto representa a memória do computador para um programa? Para
respondermos a essa pergunta, vamos fazer algumas considerações:
– Suponha um computador com 1Gb de memória e que 100% desta memória
estará disponível para o nosso programa.
– Suponha também que o programa só utilizará números reais de precisão dupla,
então:
1 GBytes = 1024 MBytes = 10242 KBytes = 10243 Bytes
se 1 número real de dupla precisão consome 16 bytes, então:
10243/16 = 67108864 números reais de precisão dupla
Parece muito... mas o quanto isso equivale em relação a um array quadrado?
671088641/2 = 8192
CONCLUSÃO: O tamanho máximo do array, neste caso, seria:
real*8, dimension(8192,8192) :: array
Obviamente que nosso programa não terá 100% da memória disponível
É óbvio também que a alocação dinâmica não soluciona esse problema, mas
ajuda a amenizá-lo, pois a memória pode ser alocada e desalocada de acordo
com a necessidade do programa;
Alocação dinâmica e seus comandos
Os comandos associados a alocação dinâmica de memória em
Fortran90 são:
– allocatable : específica que um array pode ser dinamicamente
alocado durante a execução do programa;
– allocate : comando utilizado no ato de alocação de memória;
– deallocate: comando utilizado para desalocar, e consequentemente,
devolver a memória para o sistema;
– allocated : verifica se um array está alocado ou não
Declarando arrays alocáveis
ALLOCATABLE: O comando allocatable é usado na
declaração de um array para designar que o mesmo poderá ser
dinâmicamente alocado durante a execução do programa.
! Forma 1: shape no nome da variavel
real*8, allocatable :: arr1(:,:)
! Forma 2: shape no comando dimension
real*8, allocatable, dimension(:,:) :: arr1
! A forma 2 é útil quando se deseja declarar
! vários arrays alocáveis com o mesmo shape
Alocando arrays alocáveis
ALLOCATE: O comando allocate efetivamente faz a requisição
de memória do sistema para que possa ser usada pelo array.
Forma geral:
allocate( <lista de arrays>, stat=<istatus> )
<lista de arrays>: um ou mais arrays que se deseja alocar
juntamente com o tamanho do array.
<istatus> (OPCIONAL): Inteiro que retorna o valor 0 se o array
for alocado com sucesso e maior que zero em caso de erro
! Exemplo: alocando 3 vetores
real*8, allocatable :: a(:), b(:), c(:)
n = 1000
allocate(a(n),b(n),c(n),stat=ierr)
if (ierr.ne.0) print *, ‘Erro ‘, ierr, ‘ na alocacao de memoria’
Alocando arrays alocáveis
Outros exemplos:
real*8, allocatable, dimension(:) :: arr1, arr2, arr3, arr4, arr5
print *, ‘ Entre com o tamanho do vetor arr1:’
read(*,*) n
allocate(arr1(n)) ! A partir desse ponto o vetor arr1 pode ser usado
! alocacao com tamanho explicito
allocate(arr2(1000))
! alocacao com indice inicial diferente de 1
allocate(arr3(-100:100))
! alocacao com verificacao
allocate(arr4(10),stat=i)
if (i.ne.0) print *, ‘ Algum problema ocorreu...’
! alocacao de array nulo.
allocate(arr5(0))
Desalocando arrays previamente alocados
DEALLOCATE: O comando Deallocate devolve a memória
utilizada por um array desalocando-o da memória.
Forma geral:
deallocate( <lista de arrays>, stat=<istatus> )
<lista de arrays>: um ou mais arrays que se deseja alocar
<istatus> (OPCIONAL): Inteiro que retorna o valor 0 se o array
for alocado com sucesso e maior que zero em caso de erro
! Exemplo: desalocando 3 vetores simultaneamente
deallocate(a,b,c,stat=ierr)
if (ierr.eq.0) ‘ Arrays desalocados com sucesso...’
Verificando se um array está alocado
ALLOCATED: O comando allocated é utilizado para verificar o
status de alocação de um array e é normalmente utilizado em
associação com o comando allocate. O comando retorna
.true. caso o array esteja alocado e .false. caso contário.
Forma geral:
allocated(<array>)
! verifica se a matriz já esta ALOCADA para não alocá-la novamente
if (.not.allocated(arr1)) allocate(arr1(-10:10))
! Só DESALOCA a matriz arr1 caso ela esteja ALOCADA
if (allocate(arr1)) deallocate(arr1)
Programa-exemplo
Ler os dados do seguinte
arquivo e armazená-los em um
array devidamente alocado:
arquivo dados.txt
10 3
-3.13
-7.32
-7.44
-6.35
1.56
-0.54
7.05
-1.80
8.98
6.57
-8.31
-4.43
1.65
9.87
-5.45
-3.67
-9.53
-1.82
6.84
-2.82
0.25
9.86
3.53
4.24
2.84
5.73
-5.25
3.38
9.96
-0.84
program MemoryAllocation
real*4, allocatable :: a(:,:)
open(9,file="dados.txt")
read(9,*) n1, n2
if (.not.allocated(a)) then
allocate(a(n1,n2))
endif
do i=1,n1
read (9,*) (a(i,j),j=1,n2)
write(*,*) (a(i,j),j=1,n2)
enddo
close(9)
end program
1-Alocacao
Ponteiros em Fortran90
PONTEIROS:
– Diferentemente do que ocorre com as variáveis, os ponteiros
armazenam o endereço de memória ao invés do dado propriamente
dito;
– Os ponteiros não ocupam espaço de memória além do necessário para
armazenar o valor de um endereço;
– O Fortran não suporta aritmética com ponteiros, ou seja, em Fortran
não há como incrementar posições de memória como é feito em C/C++;
– Por questões de otimização o Fortran obriga que uma variável
“apontável” seja explicitamente especificada através da adição do
atributo target junto da declaração da variável;
– A declaração de um ponteiro (pointer) e de um alvo (target)
seguem as mesmas regras previamente vistas, ou seja:
real*8, target :: a
real*8, pointer :: p_a
Ponteiros em Fortran90
PONTEIROS:
– Alterar um ponteiro significa alterar a posição de memória para a qual
ele aponta, ou seja, o conteúdo da memória permanece inalterado;
– Alterar o CONTEÚDO do ponteiro significa modificar o valor
armazenado no endereço de memória para o qual o ponteiro aponta;
– Em Fortran 90 utiliza 2 operadores para realizar as operações descritas
acima:
• Fazendo um ponteiro apontar para uma variável
a => c
• Modificando o conteúdo da memória apontada por “a”
a = 10.0
– Os ponteiros são famosos por serem flexíveis o suficiente para serem
difíceis de se trabalhar tornando o gerenciamento do programa algo
bastante complexo e em certos casos incompreensível, ou seja, SE
NÃO PRECISAR DELES, NÃO USE!!! “A eficiência computacional é
diretamente proporcional a simplicidade e inversamente proporcional a
sofisticação”
Ponteiros em Fortran90
integer, target :: ivar = 3
integer, target :: jvar = 7
integer, pointer :: p_var
991B33AA
3
5
ivar
F1D7E700
7
9
jvar
p_var => ivar
p_var
ivar = 5
p_var => jvar
p_var = 9
991B33AA
F1D7E700
Ponteiros e seus comandos
ASSOCIATED: Verifica se um ponteiro está atribuído (apontando) para
alguma variável;
NULLIFY: Faz com que o ponteiro não esteja atribuído a nenhuma
variável;
integer, dimension(:), pointer :: a_pointer
integer, dimension(3), target :: z_target
a_pointer => z_target ! a_pointer aponta para z_pointer
! verificando se a_pointer esta associado a z_pointer
if (associated(a_pointer, TARGET=z_pointer) then
! fazendo a_pointer não apontar para mais ninguém
nullify(a_pointer)
endif
OBS: O comando associated também poderia ser usado sem o parâmetro TARGET. Neste caso, o comando
verificaria se o ponteiro está associado a qualquer variável.
2-ponteiros
Criando um array diferente
Suponha que você precise de um array bidimensional no qual
as linhas possuem tamanho variável, ou seja, uma linha possui
2 colunas, outra possui 10, etc... . Isso poderia ser facilmente
criado através do uso de ponteiros.
5
8
7
-2
3
3
-1
6
2
4
8
5
-5
0
2
6
1
2
1
8
3
9
-2
7
6
0
7
2
3
3
-3
3
2
-2
9
2
2
-7
7
1
-3
5
3
0
4
-5
3
1
2
3
-1
-5
3
-4
-6
-7
0
1
Criando um array diferente
program ArrayDiferente
close(7)
type TypeArray
integer :: nterms
integer, pointer :: terms(:)
end type TypeArray
! imprimindo as linhas do array
do i=1,13
j = array(i)%nterms
print *, array(i)%terms(1:j)
enddo
type(TypeArray), allocatable :: array(:)
integer :: buffer(10)
allocate(array(13)) ! alocando as linhas
! Abrindo o arquivo onde os dados estao
open(7,file=“dados.txt”)
! desalocando o “array diferente”
do i=1,13
deallocate(array(i)%terms)
enddo
end program
! lendo e alocando as colunas do array
do i=1,13
read(7,*)n,(buffer(k),k=1,n)
array(i)%nterms = n
allocate(array(i)%terms(n))
array(i)%terms(:) = buffer(1:n)
enddo
3-ArrayDiferente
Programação estruturada
O que é?!
– A programação estruturada é um paradigma de programação no qual
um programa é visto como uma linha de montagem em que os trechos
de programas são segmentados e agrupados em unidades específicas
(os subprogramas) de acordo com sua utilidade e freqüência com que é
(re)utilizada.
Subprogramas
– Os subprogramas, também conhecidos como sub-rotinas, funções ou
simplesmente rotinas, são módulos especializados na realização de
uma determinada tarefa.
– O Fortran possui duas estruturas básicas para construção de subprogramas:
SUBROUTINE: As subrotinas realizam uma determinada tarefa sem
retornar um valor explicitamente
FUNCTION: As funções se assemelham as subrotinas, porém, elas
retornam explicitamente um valor
Funções
Funções declaração: São funções que apresentam uma única
expressão (aritmética, lógica ou caractere)
– FORMA GERAL:
<nome da função>(<lista de parâmetros>) = expressão
real*8 :: TRIG, x, y
! criando a função declaracao TRIG
TRIG(x,y) = SIN(x)**2 + COS(y)**2
! usando a função criada
print *, TRIG(2.d0,1.2d0)
Funções
Funções externas: Funções que retornam um valor para o programa requisitante.
As funções externas são implementadas em uma unidade separada do programa
principal. As funções externas possuem a seguinte forma geral:
<prefixo> FUNCTION <nome> (<args>) [RESULT <variavel>]
...
end function
<prefixo> pode ser:
• A declaração do tipo da função
• Uma das seguintes declarações: RECURSIVE, PURE, ELEMENTAL
<nome> é o nome da função
<args> são os argumentos passados para a função
[RESULT <variavel>] variável que conterá o resultado da função (essa
construção é opcional)
Funções externas (exemplo)
real*8 function AreaCircle(radius)
real*8, intent(IN) :: radius ! <<< COMANDO NOVO: INTENT
real*8, parameter :: pi = 3.14159265359d0
AreaCircle = pi*radius*radius
return
end function AreaCircle
O que é INTENT?
A declaração INTENT é opcional e serve para identificar se um
parâmetro passado para a função ou subrotina é um parâmetro de
entrada, INTENT(IN), de saída, INTENT(OUT), ou ambos
INTENT(INOUT)
4-AreaCircle
Subrotinas
Subrotinas: São subprogramas que podem ou não retornar valores para
o programa solicitante. As subrotinas são chamadas pelo programa
principal através do comando CALL e possuem a seguinte forma geral:
<prefixo> SUBROUTINE <nome> (<args>)
<prefixo> pode ser:
• Uma das seguintes declarações: RECURSIVE, PURE, ELEMENTAL
<nome> é o nome da função
<args> são os argumentos passados para a função
Subrotinas (exemplo)
! Subrotina para inverter um vetor de n posicoes
subroutine inverter(ivet,nsize)
integer :: i, itmp
integer, intent(in
) :: nsize
integer, intent(inout) :: ivet(1)
do i=1,nsize/2
itmp = ivet(nsize+1-i)
ivet(nsize+1-i) = ivet(i)
ivet(i) = itmp
enddo
return
end subroutine inverter
5-Inverter
Passagem de argumentos para subprogramas
Os programas em Fortran somente suportam a passagem de
valores por referência para os subprogramas, ou seja, os valores
passados para os subprogramas ao serem modificados retornam as
suas modificações para o programa principal.
program argumentos
integer :: nval
nval = 10
print *, altera(nval)! imprimira o valor 15
end program
integer function altera(n)
integer :: n
n = 15
altera = n
end subroutine
6-Argumentos
Passagem de arrays para subprogramas
O Fortran 90 herdou as 3 formas clássicas de declaração de arrays
passados para subprogramas e acrescentou mais uma. Nos slides a seguir
veremos cada uma dessas formas
Declarando arrays unitários: O array é declarado no subprograma como
um array unitário. Neste caso o programador tem que garantir que os
limites do array não serão violados.
program Principal
integer, dimension(10) :: nval = (/(i,i=1,10)/)
call Imprime(nval,10)
...
subroutine Imprime(nums,n)
integer :: nums(1)
print *, nums(1:n)
! Neste caso não há como utilizar as funções intrinsecas e operações
! com arrays do Fortran 90 no interior da subrotina
Passagem de arrays para subprogramas
Declarando um array de tamanho assumido: Neste caso a
dimensão do array é declarada dentro dos subprogramas com um
símbolo de * para especificar que o array terá o mesmo tamanho
que foi definido no programa principal.
program Principal
integer, dimension(10) :: nval = (/(i,i=1,10)/)
call Imprime(nval,10)
...
subroutine Imprime(nums,n)
integer :: nums(*)
print *, nums(1:n)
! Neste caso também não há como utilizar as funções
! intrinsecas e operações com arrays do Fortran 90 no interior
! da subrotina
Passagem de arrays para subprogramas
Declarando explicitamente o array exceto pela última dimensão: Neste
caso o shape é utilizado mas o ultimo tamanho omitido, ou seja, os arrays
são declarados em todas as dimensões exceto pela última que é deixada
ilimitada.
program Principal
integer, dimension(2,10) :: nval = (/(i,i=1,10)/)
call Imprime(nval,2,10*2)
...
subroutine Imprime(nums,n,lenght)
integer :: nums(n,*)
k=lenght/n
do i=1,n
do j=1,k
print *, nums(i,j)
...
! Novamente não haverá como utilizar as funções intrinsecas e operações
! com arrays do Fortran 90 no interior da subrotina
Opções de subprogramas
Como vimos anteriormente os subprogramas podem ser declarados como
RECURSIVE, PURE e ELEMENTAL e agora veremos o que cada declaração
dessa significa.
RECURSIVE: Um rotina recursiva é uma rotina que pode chamar a si
própria.
! novamente calculando o fatorial de um numero...
recursive function fact(n) result(answer)
integer :: n, answer
if (n >= 1) then
answer = n*fact(n-1)
else
answer = 1
endif
end function fact
Opções de subprogramas
PURE: Uma rotina pura é uma rotina que não provoca efeitos
colaterais no programa principal, ou seja, uma rotina pura não
modifica nenhum parâmetro de entrada ou qualquer outro dado. As
rotinas puras somente irão produzir:
– Em FUNCTIONS: somente retornarão o resultado da função;
– Em SUBROUTINES: somente modificarão os valores do argumentos
declarados como INTENT(OUT) ou INTENT(INOUT);
O argumento PURE é de grande importância pois declara ao
compilador que a rotina poderá ser seguramente otimizada, ou
seja, embora essa declaração seja opcional ela pode tornar um
programa mais eficiente.
Um exemplo de função PURA
pure function length(x,y)
implicit none
real, intent(in) :: x, y
real :: length
length = sqrt(x**2 + y**2)
end function length
obs.: Qualquer rotina chamada de dentro de uma rotina pura também deverá ser pura.
A Seguinte função, pode ser considerada PURA?
integer function altera(n)
integer :: n
n = 15
altera = n
end subroutine
Opções de subprogramas
ELEMENTAL: As rotinas elementares são rotinas declaradas para
argumentos escalares porém elas também podem ser aplicadas em
arrays. Se os argumentos passados para a rotina forem escalares o
resultado será escalar e se os argumentos forem arrays o resultado
também será um array de mesma forma dos argumentos de entrada.
elemental function length(x,y)
implicit none
real, intent(in) :: x, y
real :: length
length = sqrt(x**2 + y**2)
end function length
OBS: A parte chata dos subprogramas elementares é que para eles funcionarem
com arrays o programador deve declarar uma interface no programa que
utiliza a rotina elementar. Veremos nos próximo slide como isso funciona na
prática.
Funções elementares na prática
program ElementalFunctions
! INTERFACE DA FUNÇÃO ELEMENTAR
interface
elemental real*8 function length(x,y)
real*8, intent(in) :: x, y
end function length
end interface
integer, parameter :: n
real*8, dimension(n) ::
real*8, dimension(n) ::
real*8, dimension(n) ::
elemental real*8 function length(x,y)
implicit none
real*8, intent(in) :: x, y
length = dsqrt(x**2 + y**2)
end function length
= 10
p1= (/(i,i=1,n)/)
p2= (/(i,i=1,n)/)
dists = 0.d0
print '(2F15.8)', ((p1(i),p2(i)),i=1,n)
dists = length(p1,p2)
print *, (dists(i),i=1,n)
end program
7-Elemental
Módulos – uma nova forma de passar parâmetros
MODULE: Os módulos foram adicionados ao padrão da linguagem
Fortran 90 para substituir as antigas formas empregadas pelo
Fortran 77 para disponibilizar dados entre subprogramas. Mais
abrangentes do que os antigos “COMMOM BLOCKS” e INCLUDES os
módulos permitem não somente a disponibilização de variáveis
como também estruturas, tipos, ponteiros e até subprogramas
completos.
Os módulos podem ser construídos em qualquer parte do programa
ou em unidades separadas, porém, eles devem ser compilados de
acordo com sua ordem de dependência pelo programas e rotinas.
Os módulos permitem que as operações e funções referentes a
manipulação de arrays possam ser realizadas dentro dos
subprogramas.
Usando os modules
Para compartilharmos as informações contidas em um MODULE
entre outras partes do programa basta adicionarmos o
comando USE <nome do module> no inicio da unidade que
desejamos que acesse os dados e rotinas do módulo.
subroutine A
use B ! A subrotina A estará acessando os dados do modulo B
...
function B
use valores
program Teste
use valores
...
module Valores
...
subroutine A
end module
use valores
subroutine D
function C
...
...
Usando modules (um exemplo simples)
module Dados
real*8 :: xmin, xmax, ymin, ymax, dx, dy
integer :: ndivx, ndivy
real*8, allocatable :: xy(:,:)
end module dados
Program CalcularFuncao
subroutine Grade
use Dados
use Dados
...
...
print *, “Entre com os dados”
! calculando as coords da grade
call Grade
...
...
end subroutine Grade
...
end program CalcularFuncao
Um exemplo mais complexo
module <nome do
...
! Declaracao
...
private :: !
...
public :: !
module>
de variaveis, tipos, estruturas, arrays, pointers
variaveis e rotinas privadas
variaveis e rotinas publicas
contains
subroutine A
! implementação da subrotina A
end subroutine A
...
function B
! implementacao da função B
end function B
end module <nome do module>
Notem que com os módulos nós podemos criar estruturas que
lembram as estruturas existentes na programação orientada a objetos
Praticando um pouco
Transformar o programa GRADE em um módulo e utilaza-lo em
um programa principal que avalie a seguinte função:
– f(x,y) = x**2 – y**2
Ao final, use um plotador qualquer para ver se o resultado
produzido foi o esperado
Obrigado pela atenção …