Buscar

CP_Apontamentos_2005_FORTRAN

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 100 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 100 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 100 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Introdução à Programação na linguagem F 
 
 
 
 
 
Jaime Ramos, Amílcar Sernadas e Paulo Mateus 
 
 
 
 
 
 
 
 
 
 
DMIST, Setembro de 2005 
 
 
 2 
 
Capítulo 1 Introdução à linguagem F 
 
Objectivos 
 
Introdução à linguagem F. Edição e compilação de programas. Tipos primitivos e expressões. Declaração 
de variáveis. Atribuição. Composição sequencial, iterativa e alternativa de comandos. Definição de 
funções e subrotinas. Efeitos colaterais. Programação recursiva. 
 
Conceitos básicos 
 
A linguagem F é um fragmento cuidadosamente escolhido da linguagem de programação Fortran 95. 
Considere-se o seguinte programa F, para somar os números inteiros entre 1 e 50. 
 
program exemplo1 
 
integer :: i, r 
 
r=0 
do i=1,50 
 r=r+i 
end do 
print *,"Resultado:",r 
end program exemplo1 
 
Antes de proceder à análise do programa, convém perceber como é que se edita, compila e executa um 
programa em F. A edição do programa é feita do modo usual: recorrendo a um editor de texto (por 
exemplo o Notepad), escreve-se o código do programa num ficheiro com extensão f95. Neste caso, deu-
se ao ficheiro o nome exemplo1.f95 (embora não seja obrigatório, é usual e conveniente dar ao 
ficheiro o mesmo nome do programa). 
 
Em seguida, é necessário compilar o programa. Para tal, é necessário ter instalado no computador em que 
se está a trabalhar um compilador para a linguagem F (que pode ser obtido em www.fortran.com/F). 
A compilação do ficheiro é feita através do editor de comandos do Windows (command prompt). Antes 
de compilar é necessário indicar ao computador onde se encontra o ficheiro que se pretende compilar. O 
processo mais fácil consiste em ir para esse local. No caso do exemplo, o ficheiro exemplo1.f95 
encontra-se na pasta C:\Fortran\CP\examples\cap01. Para chegar até é necessário executar 
comando cd (change directory) no editor de comandos: 
 
> cd Fortran\CP\examples\cap01 
 
Podemos finalmente compilar o programa: 
 
> f exemplo1.f95 
 
No caso da compilação ser bem sucedida (não ocorrerem erros), o resultado é um ficheiro executável 
(extensão exe), que pode ser corrido também através do editor de comandos. Neste caso, a compilação 
anterior gerou um ficheiro a.exe, que pode ser executado da seguinte forma: 
 
> a.exe 
 Resultado: 1275 
 
sendo apresentado no ecrã o resultado da execução do programa exemplo1, que neste caso consiste na 
soma dos 50 primeiros números naturais. Alternativamente, podemos compilar o programa para um 
ficheiro executável com um nome mais apelativo. Para tal, utiliza-se uma opção disponível no compilador 
que permite fixar o nome do ficheiro executável resultante da compilação, a opção –o. Por exemplo, 
podemos compilar o programa anterior para o ficheiro exemplo1.exe através do comando: 
 
> f exemplo1.f95 –o exemplo1.exe 
 3 
 
Neste caso, para executar o programa é necessário mandar executar o seguinte comando: 
 
> exemplo1.exe 
 
Analisemos este programa. Após a declaração de início do programa (program exemplo1) surge a 
declaração das variáveis utilizadas. Nesta declaração, deverão ser incluídas todas as variáveis que sejam 
utilizadas pelo programa bem como o tipo de objecto que cada uma dessas variáveis irá guardar durante a 
execução do programa. Neste caso, são declaradas duas variáveis i e r, para guardar números inteiros, 
através da declaração: 
 
integer :: i, r 
 
Com esta declaração, as variáveis i e r apenas poderão ser utilizadas para guardar números inteiros. 
Qualquer tentativa de lhes atribuir um valor que não seja um número inteiro (por exemplo um número 
real) resultará num erro do programa. 
 
Segue-se o corpo do programa constituído por uma composição sequencial de três instruções: a atribuição 
r=0, a instrução composta do ... end do e a instrução de escrita print *,"Resultado:",r. A 
composição sequencial de comandos é feita através da mudança de linha, ou seja, em F apenas é 
permitido uma instrução atómica por linha (por exemplo, uma atribuição ou uma instrução de escrita). A 
estrutura das instruções compostas será discutida à medida que estas forem sendo apresentadas. 
 
A forma genérica da instrução iterativa é a seguinte: 
 
do guarda 
 corpo 
end do 
 
em que guarda é uma expressão da forma 
 
variável = expressão1, expressão2 
 
e corpo é uma instrução. 
 
A variável recebe como valor inicial o valor da expressão1 e o corpo do ciclo é executado. Em 
seguida o valor da variável é incrementado de uma unidade e, caso o valor da expressão2 não 
tenha sido excedido, o corpo do ciclo volta a ser executado. Este processo repete-se até que o valor da 
expressão2 seja excedido. Mais à frente, serão apresentadas outras formas de controlar a execução de 
um ciclo. 
 
No caso do exemplo, a instrução 
 
do i=1,50 
 r=r+i 
end do 
 
repete a atribuição r=r+i, começando a variável i com o valor 1 (expressão1) até se atingir o valor 
50 (expressão2). De cada vez que a atribuição é executada o valor de i é incrementado. 
 
A última instrução do programa é uma instrução de escrita. Em F, a instrução print pode ser 
formatada para apresentar os resultados de diferentes formas. Por agora, apenas nos preocupamos em 
escrever informação no ecrã de uma forma não formatada, através da opção *. Mais tarde analisaremos 
algumas formas de formatar os resultados. 
 
O programa termina com end program exemplo1. 
 
 4 
Existem também em F instruções de leitura. O programa seguinte, guardado no ficheiro 
exemplo2.f95, utiliza a instrução read para ler do terminal dois números inteiros para as variáveis x 
e y. Em seguida soma os números inteiros entre x e y: 
 
program exemplo2 
 
integer :: x, y, i, r 
 
print *,"Valor inicial:" 
read *,x 
print *,"Valor final:" 
read *,y 
r=0 
do i=x,y 
 r=r+i 
end do 
print *,"Resultado:",r 
end program exemplo2 
 
Então, após compilar o programa: 
 
> f exemplo2.f95 –o exemplo2.exe 
 
podemos executá-lo. O resultado é o seguinte: 
 
> exemplo2.exe 
 Valor inicial: 
1 
 Valor final 
50 
 Resultado: 1275 
 
Os valores 1 e 50 foram introduzidos pelo utilizador, a partir do terminal, e lidos para as variáveis x e y, 
respectivamente, através da instrução read. 
 
Tipos primitivos e expressões 
 
Existem 5 tipos de dados primitivos disponíveis em F: integer, real, complex, logical e 
character. Mais tarde, serão apresentadas construções que permitem definir tipos mais complexos em 
F. 
 
Tipo integer: os objectos deste tipo correspondem aos números inteiros. Por exemplo, 45, 0, -100 
são objectos de tipo integer. Há ainda a possibilidade de escolher a representação destes números 
(long ou short); essa discussão é adiada para um capítulo subsequente. 
 
Tipo real: os objectos deste tipo correspondem aos números reais. Por exemplo, 3.1416, 0.13, 3.0 
são objectos de tipo real. Tal como no tipo integer, é possível escolher a representação destes 
números. 
 
Tipo complex: os objectos deste tipo correspondem aos números complexos (da forma a+bi, em que 
tanto a como b são números reais). Por exemplo, (1.5, 1.3) é um objecto de tipo complex que 
corresponde ao número complexo 1.5 + 1.3i. 
 
Tipo logical: os objectos deste tipo correspondem aos valores de verdade. As constantes são .true. 
e .false. correspondendo a verdadeiro e a falso, respectivamente. 
 
Tipo character: os objectos deste tipo correspondem a cadeias de caracteres (delimitadas por aspas). 
Por exemplo, "Joao" e "ola" são objectos de de tipo character. 
 
 5 
Estão disponíveis operações aritméticas definidas sobre cada os tipos números, como por exemplo, 
operações usuais + (adição), - (subtracção), * (multiplicação), / (divisão). Estas operações, quando 
aplicadas a dois objectos do mesmo tipo, resultam num objecto desse tipo, ou seja, adicionar dois 
números inteiros resulta num número inteiro, multiplicar doisnúmeros reais resulta num número real, etc. 
Nomeadamente, a divisão de dois números inteiros resulta num número inteiro. Por exemplo, o resultado 
de avaliar a expressão 15/2 em F é o número inteiro 7. Obviamente, estas operações numéricas podem 
ser aplicadas a objectos de diferentes tipos. Nesse caso, prevalece o tipo mais geral. Por exemplo, somar 
um número inteiro com um número real resulta num número real, enquanto multiplicar um número real 
por um número complexo resulta num número complexo. Para além destas, existem outras operações 
aritméticas definidas em F, cuja listagem exaustiva se omite. Deixa-se como exercício interpretar 
expressão n-(n/i)*i em que n e i são variáveis de tipo integer. 
 
Para além das operações aritméticas, estão disponíveis também as habituais operações relacionais. As 
mais frequentes são: == (igualdade), /= (diferente), < (menor), <= (menor ou igual), > (maior), >= 
(maior ou igual). O resultado de uma operação relacional é um objecto de tipo logical. Por exemplo, a 
expressão 2>=0 será avaliada em F para o valor .true., enquanto a expressão 0>3 será avaliada para 
.false.. É preciso cuidado na comparação de números reais e complexos usando as operações == e 
/=, devido a eventuais erros de aproximação. 
 
Relativamente aos objectos de tipo logical, estão disponíveis as seguintes operações: .not., .and., 
.or., .eqv., and .neqv.. Estas operações podem ser aplicadas a objectos de tipo logical sendo o 
resultado também um objecto de tipo logical. Por exemplo, a expressão 2>0 .and. 3>2 quando 
avaliada em F resulta no valor .true.. 
 
Composição alternativa de instruções 
 
Em F, a composição alternativa de instruções tem duas formas principais. A primeira manda executar 
uma instrução caso a condição seja verdadeira e não faz nada se esta for falsa: 
 
if (condição) then 
 instrução1 
end if 
 
A segunda forma permite mandar executar uma instrução no caso da condição ser falsa: 
 
if (condição) then 
 instrução1 
else 
 instrução2 
end if 
 
A condição tem que estar entre parênteses. 
 
O exemplo seguinte, guardado no ficheiro exemplo3.f95, ilustra a utilização destas duas formas: 
 
program exemplo3 
 
integer :: n, i, d 
 
print *,"Introduza um numero:" 
read *,n 
d=0 
do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 end if 
end do 
if (n>1 .and. d == 0) then 
 print *,"O numero e primo." 
else 
 6 
 print *,"O numero nao e primo." 
end if 
end program exemplo3 
 
Este programa determina se um dado número n, lido do terminal, é primo ou não. Para tal, contam-se os 
números entre 2 e n-1 que dividem o número dado. O ciclo do programa percorre os números entre 2 e 
n-1 e caso i seja um divisor de n (o que, como já vimos, corresponde à expressão aritmética n-
(n/i)*i ser igual a zero), a variável d, que conta o número de divisores, é incrementada. Caso 
contrário, não se faz nada. No fim, observa-se o valor da variável d e se for igual a zero então é porque o 
número é primo. Caso contrário o número tem pelo menos um divisor diferente dele e da unidade logo 
não é primo. Considerem-se alguns exemplos de execução: 
 
> exemplo3.exe 
 Introduza um numero: 
3 
 O numero e primo. 
 
> exemplo3.exe 
 Introduza um numero: 
51 
 O numero nao e primo. 
 
> exemplo3.exe 
 Introduza um numero: 
1021 
 O numero e primo. 
 
Deixa-se como exercício repetir alguns dos programas MATLAB apresentados anteriormente. 
 
Composição iterativa revisitada 
 
Até agora, a execução de um ciclo obriga a variável do ciclo a percorrer todos os valores entre o valor da 
expressão1 e o valor da expressão2. Suponha-se que se pretende apenas analisar os números 
pares. Por exemplo, pretende-se um programa que imprima no ecrã todos os números pares até 50. 
Obviamente que com as instruções disponíveis é possível construir tal programa (como?). No entanto, tal 
solução obriga a percorrer todos os números entre 1 e 50, quando nós apenas estamos interessados nos 
números pares entre 2 e 50. A linguagem F disponibiliza um mecanismo para controlar o progresso da 
variável do ciclo. A guarda do ciclo pode ter a forma 
 
variável = expressão1, expressão2, expressão3 
 
em que expressão3 especifica o incremento da variável. Por exemplo, no programa exemplo4 
seguinte, a guarda i=2,50,2 especifica que a variável i toma o valor inicial 2 e é incrementada de 2 
em 2 até se atingir 50. 
 
program exemplo4 
 
integer :: i 
 
do i=2,50,2 
 print *,i 
end do 
end program exemplo4 
 
Ao executar este programa o resultado obtido é a lista de todos os números pares escritos no ecrã: 
 
> exemplo4 
2 
4 
6 
 7 
 
O incremento também pode ser negativo, mas neste caso há que garantir que o valor inicial é maior do 
que o valor final. Considere-se o programa seguinte, para calcular o factorial de um número. 
 
program exemplo5 
 
integer :: n, r, i 
 
print *,"Introduza um número: " 
read *,n 
r=1 
do i=n,1,-1 
 r=r*i 
end do 
print *,"O factorial e: ",r 
end program exemplo5 
 
Neste caso, a variável i toma o valor inicial n e é decrementada até se atingir o valor 1. 
 
Existem ainda outras formas de controlar a execução de uma instrução iterativa que serão ilustradas mais 
à frente. 
 
Funções e subrotinas auxiliares 
 
Ao desenvolver um programa, é muitas vezes necessário definir funções e subrotinas auxiliares. No 
seguimento, apresentam-se alguns exemplos onde a definição de funções e subrotinas auxiliares se revela 
útil. Mais à frente, estes conceitos voltarão a ser aplicados ao desenvolvimento de grandes programas, 
segundo a metodologia da programação modular por camadas baseadas em objectos. 
 
Suponha-se que se pretende desenvolver um programa para contar o número de números primos que 
existem até um determinado número n, dado pelo utilizador. Embora seja possível desenvolver um 
programa em F sem recorrer a procedimentos auxiliares, tal solução tem diversos inconvenientes, entre os 
quais se destacam: o desenvolvimento do programa é mais complexo, a detecção de erros é mais difícil e 
a leitura do programa mais complicada. Uma solução consiste em definir uma função auxiliar que testa se 
um número é primo e utilizar essa função para construir o programa principal. 
 
Uma função em F pode ser utilizada como qualquer função primitiva. Não pode ter efeitos colaterais e 
devolve sempre um valor. Uma subrotina assemelha-se a uma função, mas não devolve valor, embora 
alguns dos seus parâmentros possam ser utilizados para devolver valores ao programa que a invocou, e 
pode também ter efeitos colaterais. 
 
Suponha-se então que se dispõe de uma função prime para testar se um determinado número é primo. 
Uma solução para o problema apresentado é o programa seguinte: 
 
program exemplo6 
 
integer :: n, i, r 
 
print *,"Introduza um numero:" 
read *,n 
r=0 
do i=1,n 
 if (prime(i)) then 
 r=r+1 
 end if 
end do 
print *,"Existem",r,"primos ate",n 
end program exemplo6 
 
Se tentarmos compilar o programa anterior obtemos um erro de compilação pois a função prime não é 
conhecida. É necessário definir esta função e torná-la conhecida para o programa. 
 8 
 
Comecemos pela definição da função. A estrutura de uma função em F é semelhante à de um programa: 
 
function prime(n) result(b) 
integer, intent(in) :: n 
logical :: b 
integer :: i, d 
 
if (n<=1) then 
 b= .false. 
else 
 d=0 
 do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 end if 
 end do 
 if (d==0) then 
 b=.true. 
 else 
 b=.false. 
 end if 
end if 
end function prime 
 
As principais diferenças de uma função para um programa são a utilização da instrução function em 
vez da instrução program, e a necessidade de declarar os parâmetros e o resultado da função. Neste 
caso, a função prime tem um parâmetro de entrada nde tipo inteiro e um resultado b de tipo logical. 
A declaração intent(in) serve para indicar que o parâmetro apenas pode ser utilizado para passar 
valores à função. Veremos a seguir que podem existir outros tipos de argumentos quando se definirem 
subrotinas. Em tudo o resto a definição da função é semelhante a um programa. 
 
Falta apenas disponibilizar esta função ao programa anterior. A solução mais simples consiste em declarar 
esta função no fim do programa, após o corpo do programa e antes da instrução end program, usando 
a instrução contains. O ficheiro exemplo6.f95 contem a versão completa do programa: 
 
program exemplo6 
 
integer :: n, i, r 
 
print *,"Introduza um numero:" 
read *,n 
r=0 
do i=1,n 
 if (prime(i)) then 
 r=r+1 
 end if 
end do 
print *,"Existem",r,"primos ate",n 
 
contains 
 
function prime(n) result(b) 
integer, intent(in) :: n 
logical :: b 
integer :: i, d 
 
if (n<=1) then 
 b= .false. 
else 
 d=0 
 9 
 do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 end if 
 end do 
 if (d==0) then 
 b=.true. 
 else 
 b=.false. 
 end if 
end if 
end function prime 
end program exemplo6 
 
Após compilarmos o programa anterior (para o ficheiro exemplo6.exe) podemos testá-lo. Seguem-se 
alguns exemplos: 
 
> exemplo6.exe 
 Introduza um numero: 
4 
 Existem 2 primos ate 4 
 
> exemplo6.exe 
 Introduza um numero: 
10 
 Existem 4 primos ate 10 
 
Uma subrotina assemelha-se a uma função mas existem algumas diferenças. As principais diferenças 
residem no facto de uma subrotina não devolver valor e poder provocar efeitos colaterais. No programa 
anterior, em vez de uma função, podia ter sido definida uma subrotina prime, com dois parâmetros, um 
parâmetro de entrada n e um parâmetro de saída b, em que se devolve o resultado. 
 
subroutine prime(n,b) 
integer, intent(in) :: n 
logical, intent(out) :: b 
integer :: i, d 
 
if (n<=1) then 
 b= .false. 
else 
 d=0 
 do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 end if 
 end do 
 if (d==0) then 
 b=.true. 
 else 
 b=.false. 
 end if 
end if 
end subroutine prime 
 
A declaração intent(out) define o parâmetro b como sendo de saída. A chamada de uma subrotina 
também é diferente da chamada de uma função. Neste caso, a chamada da subrotina anterior é feita 
através da instrução call. O programa exemplo7 ilustra a utilização desta subrotina, para contar os 
números primos até um determinado número. 
 
 10 
program exemplo7 
 
integer :: n, i, r 
logical :: b 
 
print *,"Introduza um numero:" 
read *,n 
r=0 
do i=1,n 
 call prime(i,b) 
 if (b) then 
 r=r+1 
 end if 
end do 
print *,"Existem",r,"primos ate",n 
 
contains 
 
subroutine prime(n,r) 
integer, intent(in) :: n 
logical, intent(out) :: r 
integer :: i, d 
 
if (n<=1) then 
 r= .false. 
else 
 d=0 
 do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 end if 
 end do 
 if (d==0) then 
 r=.true. 
 else 
 r=.false. 
 end if 
end if 
end subroutine prime 
end program exemplo7 
 
Ao chamar a subrotina prime, o resultado da chamada fica na variável b do programa, e é esta variável 
b que é usada para verificar se a variável r tem ou não que ser incrementada. 
 
Do que foi visto, podemos concluir que uma função pode ser vista como um valor, isto é, pode ser 
utilizada numa expressão, enquanto uma subrotina pode ser encarada como uma nova instrução, que, ao 
ser executada, pode provocar uma mudança no estado do sistema. No caso exemplo anterior, cada 
chamada à subrotina prime provoca (eventualmente) uma alteração da variável b. 
 
Apresentam-se em seguida alguns exemplos complementares de definição de funções e subrotinas 
auxiliares. 
 
Recorde-se o problema de calcular o factorial de um número. A primeira solução passa por definir uma 
função auxiliar fact com um parâmetro que devolve o factorial desse parâmetro 
 
program exemplo8 
 
integer :: n 
 
print *,"Introduza um numero:" 
read *,n 
 11 
print *,"Factorial:", fact(n) 
 
contains 
 
function fact(n) result(y) 
integer, intent(in) :: n 
integer :: y, i 
 
y=1 
do i=2,n 
 y=y*i 
end do 
 
end function fact 
end program exemplo8 
 
Eis alguns exemplos de execução: 
 
> exemplo8.exe 
 Introduza um numero: 
3 
 Factorial: 6 
 
> exemplo8.exe 
 Introduza um numero: 
5 
 Factorial: 120 
 
Este problema poderia também ter sido resolvido através da definição de uma subrotina. Neste caso, 
temos duas hipóteses: utilizar um parâmetro como entrada e outro parâmetro como saída, ou utilizar 
apenas um parâmetro para entrada e saída dos dados. A primeira solução não apresenta nada de novo e 
deixa-se como exercício. A segunda tem a novidade de podermos ter parâmetros de entrada e saída 
simultânea. 
 
program exemplo9 
 
integer :: n 
 
print *,"Introduza um numero:" 
read *,n 
call fact(n) 
print *,"Factorial:", n 
 
contains 
 
subroutine fact(n) 
integer, intent(inout) :: n 
integer :: y, i 
 
y=n 
do i=2,y-1 
 n=n*i 
end do 
end subroutine fact 
end program exemplo9 
 
Observe-se que na definição da subrotina foi utilizada a declaração intent(inout) para o parâmetro 
n. Isto significa que não só podemos passar valores à subrotina através do parâmetro, como este poderá 
ser alterado durante a execução da subrotina e essas alterações reflectir-se-ão para fora. Com efeito, a 
chamada a esta subrotina a partir do programa principal reflecte isso mesmo. Antes da chamada, n guarda 
 12 
o número de que pretendemos calcular o factorial. Após a chamada da subrotina, n guarda o valor do 
factorial. 
 
> exemplo9.exe 
 Introduza um numero: 
3 
 Factorial: 6 
 
> exemplo9.exe 
 Introduza um numero: 
5 
 Factorial: 120 
 
Deixa-se como exercício definir a subrotina anterior usando apenas o parâmetro n e a variável i. 
 
 
Efeitos colaterais 
 
Pode acontecer que uma subrotina modifique o valor de uma variável global (uma variável do programa 
que a invocou). Como já foi dito atrás, a este tipo de efeitos dá-se o nome de efeito colateral. Considere-
se o problema de calcular o valor do factorial de um número. Uma solução, diferente das apresentadas 
anteriormente, consiste em definir uma subrotina sem parâmetros, mas que manipule directamente o valor 
da variável. 
 
program exemplo10 
 
integer :: n 
 
print *,"Introduza um numero:" 
read *,n 
call fact() 
print *,"Factorial:", n 
 
contains 
 
subroutine fact() 
integer :: y, i 
 
y=n 
do i=2,y-1 
 n=n*i 
end do 
end subroutine fact 
end program exemplo10 
 
Repare-se que a subrotina é em tudo semelhante à do exemplo9. Do ponto de vista do programa 
principal, a variável n foi alterada sem indicação explícita. No entanto, o programa funciona como 
esperado: 
 
> exemplo10.exe 
 Introduza um numero: 
3 
 Factorial: 6 
 
> exemplo10.exe 
 Introduza um numero: 
5 
 Factorial: 120 
 
Como já foi dito atrás, os efeitos colaterais apenas podem ser provocados por uma subrotina e nunca por 
uma função. 
 13 
 
 
Programação recursiva 
 
Consiste em definir funções à custa de si mesmas. A definição de uma função ou subrotina recursiva em F 
é semelhante à definição de uma função ou subrotina. O facto de a função ou subrotina ser recursiva tem 
que ser indicado explicitamente através da instrução recursive. 
 
Recorde-se a expressão recursiva para definir o factorial de um número natural: 
 
 n! = 





0n se1)!n.(n
0n se1
 
 
A função F seguinte calcula recursivamente o factorial de um número natural: 
 
recursive function factR(n) result(r) 
integer, intent(in) :: n 
integer :: r 
 
if (n==0) then 
 r = 1 
else 
 r = n*factR(n-1)end if 
end function factR 
 
O programa seguinte utiliza esta função para calcular o factorial de um número fornecido pelo utilizador: 
 
program factorialR 
 
integer :: n 
 
print *,"Introduza um numero:" 
read *,n 
print *,"Factorial:",factR(n) 
 
contains 
 
recursive function factR(n) result(r) 
integer, intent(in) :: n 
integer :: r 
 
if (n==0) then 
 r = 1 
else 
 r = n*factR(n-1) 
end if 
end function factR 
end program factorialR 
 
Considerem-se alguns exemplos de utilização: 
 
> factorialR.exe 
 Introduza um numero: 
4 
 Factorial: 24 
 
> factorialR.exe 
 Introduza um numero: 
1 
 14 
 Factorial: 1 
 
Deixa-se como exercício proteger a função contra argumentos indesejados. nomeadamente, contra 
números negativos. 
 
Considere-se a sucessão Fn, n≥0, dos números de Fibonacci definida por 
 
Fn=
1n se 
1n se 
0n se 
FF
1
0
1n2n 







 
 
 
A correspondente definição em F é a seguinte: 
 
recursive function fibR(n) result(r) 
integer, intent(in) :: n 
integer :: r 
 
if (n==0) then 
 r = 0 
else if (n==1) then 
 r = 1 
else 
 r = fibR(n-2)+fibR(n-1) 
endif 
end function fibR 
 
Podemos testar esta função através do programa: 
 
program fib 
 
integer :: n 
 
print *,"Introduza um numero:" 
read *,n 
print *,"Numero de Fibonacci:",fibR(n) 
 
contains 
 
recursive function fibR(n) result(r) 
integer, intent(in) :: n 
integer :: r 
 
if (n==0) then 
 r = 0 
else if (n==1) then 
 r = 1 
else 
 r = fibR(n-2)+fibR(n-1) 
endif 
end function fibR 
end program fib 
 
 
Apresentam-se em seguida alguns exemplos de utilização: 
 
> fib.exe 
 Introduza um número: 
2 
 Numero de Fibonacci: 1 
 
 15 
> fib.exe 
 Introduza um número: 
6 
 Numero de Fibonacci: 8 
 
 
 
 16 
Capítulo 2 Complementos de programação em F 
 
Objectivos 
 
Vectores e matrizes. Programação imperativa sobre vectores e matrizes. Programação recursiva sobre 
vectores. 
 
 
Vectores 
 
Para declarar uma variável vectorial, é necessário indicar qual o tamanho do vector em causa e qual o tipo 
de objectos desse vector. Na instrução seguinte declara-se uma variável v para guardar vectores de 
números inteiros com tamanho 5: 
 
integer, dimension(5) :: v 
 
Alternativamente, podia ter sido utilizada a declaração seguinte, em que se delimitam o limite inferior e 
superior do vector 
 
integer, dimension(1:5) :: v 
 
Para aceder aos elementos do vector, utiliza-se a expressão v(i), que se refere ao elemento na posição i 
do vector guardado em v. 
 
Pretende-se definir uma função que recebe como argumento um vector de tamanho 5 e retorna a soma dos 
seus elementos. Como primeira solução, considere-se a seguinte função: 
 
function somaVec1(v) result(s) 
integer, dimension(5), intent(in) :: v 
integer :: s, i 
 
s=0 
do i=1,5 
 s=s+v(i) 
end do 
end function somaVec1 
 
Tal como pretendido, esta função recebe como argumento um vector de tamanho 5, soma as suas 
componentes uma a uma recorrendo a um ciclo do e devolve essa soma. O programa seguinte ilustra a 
utilização desta função: lê do terminal cinco números inteiros, guarda-os num vector, e calcula a soma 
dos seus elementos recorrendo à função anterior. 
 
program exemplo11 
 
integer, dimension(5) :: v 
integer :: i 
 
do i=1,5 
 print *,"Introduza um numero:" 
 read *,v(i) 
end do 
print *,"O resultado e:",somaVec1(v) 
 
contains 
 
function somaVec1(v) result(s) 
integer, dimension(5), intent(in) :: v 
integer :: s, i 
s=0 
 17 
do i=1,5 
 s=s+v(i) 
end do 
end function somaVec1 
end program exemplo11 
 
Considere-se a seguinte execução: 
 
> exemplo11.exe 
 Introduza um numero: 
1 
 Introduza um numero: 
2 
 Introduza um numero: 
3 
 Introduza um numero: 
4 
 Introduza um numero: 
5 
 O resultado e: 15 
 
A pergunta seguinte surge naturalmente: e se pretendermos somar os elementos de vectores com outros 
tamanhos? Podem surgir duas situações: ou o vector tem tamanho superior a 5 e nesse caso a função 
apenas soma os cinco primeiros elementos, ou o vector tem tamanho inferior a 5 e nesse caso ocorre um 
erro de compilação. 
 
No entanto a função anterior pode ser generalizada para vectores de qualquer tamanho. O problema da 
declaração do parâmetro resolve-se recorrendo à declaração: 
 
integer, dimension(:), intent(in) :: v 
 
Neste caso está-se a declarar um parâmetro que consiste um vector de tamanho arbitrário. O limite do 
ciclo resolve-se recorrendo à função size. A função seguinte soma os elementos de um vector de 
números inteiros de dimensão arbitrária: 
 
function somaVec(v) result(s) 
integer, dimension(:), intent(in) :: v 
integer :: s, I 
 
s=0 
do i=1,size(v) 
 s=s+v(i) 
end do 
end function somaVec 
 
O programa seguinte lê seis números inteiros do terminal, guarda-os num vector e, recorrendo à função 
anterior, soma-os e apresenta o resultado: 
 
program exemplo12 
 
integer, dimension(6) :: v 
integer :: i 
 
do i=1,6 
 print *,"Introduza um numero:" 
 read *,v(i) 
end do 
print *,"O resultado e:",somaVec(v) 
 
contains 
 
 18 
function somaVec(v) result(s) 
integer, dimension(:), intent(in) :: v 
integer :: s, i 
s=0 
do i=1,size(v) 
 s=s+v(i) 
end do 
end function somaVec 
end program exemplo12 
 
Considere-se a seguinte execução: 
 
> exemplo12 
 Introduza um numero: 
1 
 Introduza um numero: 
2 
 Introduza um numero: 
3 
 Introduza um numero: 
4 
 Introduza um numero: 
5 
 Introduza um numero: 
6 
 O resultado e: 21 
 
No próximo exemplo, define-se uma função para contar o número de números primos que ocorrem num 
vector de números inteiros, recorrendo à função prime definida no capítulo anterior. A função contaP 
recebe como argumento um vector de números inteiros e devolve o número de números primos que 
ocorrem nesse vector. O programa seguinte começa por construir um vector com dez números inteiros 
gerados aleatoriamente (entre 1 e 100) recorrendo à subrotina random, que gera números aleatórios entre 
0 e 1. Em seguida chama a função contaP para contar quantos desses números gerados foram números 
primos. 
 
program contaprimos 
 
integer, dimension(10) :: v 
integer :: i 
real :: x 
 
do i=1,10 
 call random_number(x) 
 v(i)=int(x*100)+1 
end do 
print *,"Numero de primos:",contaP(v) 
 
contains 
 
function prime(n) result(b) 
integer, intent(in) :: n 
logical :: b 
integer :: i, d 
 
if (n<=1) then 
 b= .false. 
else 
 d=0 
 do i=2,n-1 
 if (n-(n/i)*i == 0) then 
 d=d+1 
 19 
 end if 
 end do 
 if (d==0) then 
 b=.true. 
 else 
 b=.false. 
 end if 
end if 
end function prime 
 
function contaP(v) result(s) 
integer, dimension(:), intent(in) :: v 
integer :: s, i 
s=0 
do i=1,size(v) 
 if (prime(v(i))) then 
 s=s+1 
 end if 
end do 
end function contaP 
end program contaprimos 
 
Considerem-se os seguintes exemplos de execução: 
 
> contaprimos 
Numero de primos: 1 
 
> contaprimos 
Numero de primos: 4 
 
Considere-se agora o problema de calcular o produto interno de dois vectores. A função seguinte calcula 
o produto interno de dois vectores, desde que eles sejam do mesmo tamanho. No caso de vectores com 
tamanhos diferentes, o resultado é uma mensagem de erro no ecrã. O valor devolvido neste caso é 
arbitrário. 
 
function prodInterno(v1,v2) result(x) 
real, dimension(:), intent(in) :: v1,v2 
real :: x 
integer :: i 
 
if (size(v1)/=size(v2)) then 
 print *,"Erro! Os vectores nao sao do mesmo tamanho!" 
else 
 x=0 
 do i=1,size(v1) 
 x=x+(v1(i)*v2(i)) 
 end do 
end if 
end function prodInterno 
 
Deixa-se como exercício testar esta função. 
 
Considere-se o problema decalcular o produto de um vector por um escalar. A primeira solução consiste 
numa função que recebe como argumentos o vector v e o escalar x e devolve o vector u resultante do 
produto do vector pelo escalar, que deverá ser do mesmo tamanho do vector argumento. Como é que se 
declara esse vector? Através da declaração: 
 
integer, dimension(size(v)) :: u 
 
A este tipo de declaração, em que o tamanho de um vector é fixado a partir do tamanho de outros 
argumentos, dá-se o nome de vector automático. 
 20 
Uma solução para a função prodEscalar é a seguinte: 
 
function prodEscalar(v,x) result(u) 
integer, dimension(:), intent(in) :: v 
integer, intent(in) :: x 
integer, dimension(size(v)) :: u 
integer :: i 
 
do i=1,size(v) 
 u(i)=v(i)*x 
end do 
end function prodEscalar 
 
Apresenta-se em seguida um programa para testar esta função. Este programa atribui à variável v o vector 
(1,2,3,4,5); repare-se na atribuição simultânea de um vector a uma variável. 
 
program prodEscalar1 
 
integer, dimension(5) :: v 
 
v(1:5)=(/ 1,2,3,4,5 /) 
print *,prodEscalar(v,2) 
 
contains 
 
function prodEscalar(v,x) result(u) 
integer, dimension(:), intent(in) :: v 
integer, intent(in) :: x 
integer, dimension(size(v)) :: u 
integer :: i 
 
do i=1,size(v) 
 u(i)=v(i)*x 
end do 
end function prodEscalar 
end program prodEscalar1 
 
O resultado de execução deste programa é: 
 
> prodEscalar1 
2 4 6 8 10 
 
Alternativamente, podíamos ter optado por definir uma subrotina. Deixa-se como exercício definir uma 
subrotina com três parâmetros, dois de entrada, o vector e o número, e um de saída, o vector resultado. 
Alternativamente, pode definir-se uma subrotina que altera directamente o vector, evitando assim a 
utilização de dois vectores. Para tal, declara-se o vector como parâmetro de entrada e saída (inout). 
 
subroutine prodEscalar(v,x) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: x 
integer :: i 
 
do i=1,size(v) 
 v(i)=v(i)*x 
end do 
end subroutine prodEscalar 
 
O programa anterior pode ser ligeiramente alterado para testar esta subrotina: 
 
program prodEscalar2 
 
 21 
integer, dimension(5) :: v 
 
v(1:5)=(/ 1,2,3,4,5 /) 
call prodEscalar(v,2) 
print *,v 
 
contains 
 
subroutine prodEscalar(v,x) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: x 
integer :: i 
 
do i=1,size(v) 
 v(i)=v(i)*x 
end do 
end subroutine prodEscalar 
end program prodEscalar2 
 
O resultado de execução é o mesmo e omite-se. 
 
Finalmente, apresenta-se uma subrotina para ordenar vectores de números inteiros (in situ), baseada no 
método da selecção. Este método consiste em procurar o elemento mínimo e colocá-lo na primeira 
posição o vector, em seguida procurar o menor elemento do vector de entre os restantes e colocá-lo na 
segunda posição, e assim sucessivamente. 
 
program selsort 
 
integer, dimension(5) :: v 
integer :: i 
 
do i=1,5 
 print *,"Introduza um numero:" 
 read *,v(i) 
end do 
call selectSort(v) 
print *,v 
 
contains 
 
subroutine swap(v,m,k) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: m,k 
integer :: x 
 
x=v(m) 
v(m)=v(k) 
v(k)=x 
end subroutine swap 
 
subroutine selectSort(v) 
integer, dimension(:), intent(inout) :: v 
integer :: m, k 
 
if (size(v)>1) then 
 do m=1,size(v)-1 
 do k=m+1,size(v) 
 if(v(m)>v(k)) then 
 call swap(v,m,k) 
 end if 
 end do 
 22 
 end do 
end if 
end subroutine selectSort 
end program selsort 
 
Este programa, para além da subrotina selectSort, utiliza ainda a subrotina swap que permite trocar 
os elementos nas posições m e k do vector v. 
 
Ao executar este programa, obtém-se o seguinte resultado: 
 
> selsort 
 Introduza um numero: 
9 
 Introduza um numero: 
4 
 Introduza um numero: 
7 
 Introduza um numero: 
3 
 Introduza um numero: 
10 
 Vector ordenado: 3 4 7 9 10 
 
 
Matrizes 
 
A declaração de matrizes é semelhante à dos vectores, mas em vez de apenas uma dimensão, há que 
considerar duas (ou mais) dimensões. A instrução seguinte permite declarar uma variável m para guardar 
matrizes 4x3: 
 
integer, dimension (4,3) :: m 
 
ou, alternativamente, como para vectores: 
 
integer, dimension (1:4,1:3) :: m 
 
A função pré-definida size continua a poder ser aplicada a matrizes. Neste caso, há que utilizar um 
segundo argumento indicando a dimensão de que se pretende calcular o tamanho, isto é, size(m,1) 
permite obter o tamanho da dimensão 1, ou seja, o número de linhas (no caso do exemplo, 4), 
size(m,2) permite obter o tamanho da dimensão 2, ou seja, o número de colunas (no caso do exemplo, 
3). Esta função pode ser aplicada a estruturas de qualquer dimensão. 
 
Como primeiro exemplo, apresenta-se uma função para somar todos os elementos de uma matriz. 
 
function somaEl(m) result(x) 
real, dimension(:,:), intent(in) :: m 
real :: x 
integer :: i, j 
 
x=0 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 x=x+m(i,j) 
 end do 
end do 
end function somaEl 
 
Esta função recorre a dois ciclos encaixados, o primeiro para percorrer as linhas da matriz e o segundo 
para percorrer as colunas. Para cada valor de i e j, soma-se o valor da entrada correspondente da matriz 
a x. Esta função foi definida num módulo mmatrices, que inclui outras operações sobre matrizes, 
 23 
apresentadas em seguida. O programa seguinte utiliza esta função para somar os elementos de uma 
matriz. 
 
program testsomaEl 
 
real, dimension(3,2) :: m 
 
m(1,1)=1 
m(1,2)=3 
m(2,1)=2 
m(2,2)=1 
m(3,1)=1.5 
m(3,2)=2.5 
print *,somaEl(m) 
 
contains 
 
function somaEl(m) result(x) 
real, dimension(:,:), intent(in) :: m 
real :: x 
integer :: i, j 
 
x=0 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 x=x+m(i,j) 
 end do 
end do 
end function somaEl 
end program testsomaEl 
 
Com efeito, 
 
> testsomaEl 
 11.0000000 
 
Tal como no caso dos vectores, podemos definir uma subrotina prodEscalar para multiplicar uma 
matriz por um escalar. A chamada desta subrotina altera a matriz, ficando o argumento com o resultado 
da multiplicação da matriz pelo escalar. 
 
subroutine prodEscalar(m,x) 
real, dimension(:,:), intent(inout) :: m 
real, intent (in) :: x 
integer :: i,j 
 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 m(i,j)=m(i,j)*x 
 end do 
end do 
end subroutine prodEscalar 
 
Deixa-se como exercício resolver o problema anterior recorrendo a uma função e testar quer a subrotina 
quer a função. 
 
Considere-se agora o problema de somar duas matrizes. Pretendemos definir uma função que receba 
como argumentos duas matrizes compatíveis (com o mesmo número de linhas e de colunas) e devolva 
como resultado a matriz resultante da soma. Tal como no caso vectorial, há que declarar a matriz 
resultado com as mesmas dimensões das matrizes argumento: 
 
function somaM(m1,m2) result(a) 
 24 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m1,2)) :: a 
 
Este tipo de declaração (vectores e matrizes automáticas) já tinha sido utilizado anteriormente, para o 
caso dos vectores. Note-se, no entanto, que este tipo de declaração apenas pode ser aplicada ao tamanho. 
O número de dimensões está fixo, ou seja, no caso da matriz a anterior, esta tem sempre duas dimensões 
(linhas e colunas). A função somaM que soma duas matrizes é a seguinte: 
 
function somaM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m1,2)) :: a 
integer :: i,j 
 
if ((size(m1,1)==size(m2,1)).and.(size(m1,2)==size(m2,2))) then 
 do i=1,size(m1,1) 
 do j=1,size(m1,2) 
 a(i,j)=m1(i,j)+m2(i,j) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes não são da mesma forma" 
end if 
end function somaM 
 
O programaseguinte testa esta função: 
 
program testsomaM 
 
real, dimension(3,2) :: m1,m2,m 
 
m1(1,1:2)=(/ 3,2 /) 
m1(2,1:2)=(/ 2,1 /) 
m1(3,1:2)=(/ 1.5,4.5 /) 
m2(1,1:2)=(/ 4.5,3.0 /) 
m2(2,1:2)=(/ 1.2,1.5 /) 
m2(3,1:2)=(/ 2,3 /) 
m=somaM(m1,m2) 
print *,m(1,1),m(1,2) 
print *,m(2,1),m(2,2) 
print *,m(3,1),m(3,2) 
 
contains 
 
function somaM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m1,2)) :: a 
integer :: i,j 
 
if ((size(m1,1)==size(m2,1)).and.(size(m1,2)==size(m2,2))) then 
 do i=1,size(m1,1) 
 do j=1,size(m1,2) 
 a(i,j)=m1(i,j)+m2(i,j) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes não são da mesma forma" 
end if 
end function somaM 
end program testsomaM 
 
Com efeito, 
 25 
 
> testsomaM 
 7.5000000 5.0000000 
 3.2000000 2.5000000 
 3.5000000 7.5000000 
 
Alternativamente, podemos optar por definir uma subrotina que some as duas matrizes, alterando a 
primeira, que fica com o resultado da soma. 
 
subroutine somaM(m1,m2) 
real, dimension(:,:), intent(inout) :: m1 
real, dimension(:,:), intent(in) :: m2 
integer :: i,j 
 
if ((size(m1,1)==size(m2,1)).and.(size(m1,2)==size(m2,2))) then 
 do i=1,size(m1,1) 
 do j=1,size(m1,2) 
 m1(i,j)=m1(i,j)+m2(i,j) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes não são da mesma forma" 
end if 
end subroutine somaM 
 
Neste caso, o resultado da soma de m1 e m2 é deixado em m2, como se comprova com o programa 
seguinte: 
 
program testsomaM1 
 
real, dimension(3,2) :: m1,m2 
 
m1(1,1:2)=(/ 3,2 /) 
m1(2,1:2)=(/ 2,1 /) 
m1(3,1:2)=(/ 1.5,4.5 /) 
m2(1,1:2)=(/ 4.5,3.0 /) 
m2(2,1:2)=(/ 1.2,1.5 /) 
m2(3,1:2)=(/ 2,3 /) 
call somaM(m1,m2) 
print *,m1(1,1),m1(1,2) 
print *,m1(2,1),m1(2,2) 
print *,m1(3,1),m1(3,2) 
 
contains 
 
subroutine somaM(m1,m2) 
real, dimension(:,:), intent(inout) :: m1 
real, dimension(:,:), intent(in) :: m2 
integer :: i,j 
 
if ((size(m1,1)==size(m2,1)).and.(size(m1,2)==size(m2,2))) then 
 do i=1,size(m1,1) 
 do j=1,size(m1,2) 
 m1(i,j)=m1(i,j)+m2(i,j) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes não são da mesma forma" 
end if 
end subroutine somaM 
end program testsomaM1 
 26 
 
> testsomaM1 
 7.5000000 5.0000000 
 3.2000000 2.5000000 
 3.5000000 7.5000000 
 
Considere-se agora o problema de pesquisar um elemento numa matriz. A primeira solução capitaliza no 
facto de pesquisar numa linha ser equivalente a pesquisar num vector. Assim, a solução consiste em 
utilizar a uma função de pesquisa em vector e aplicá-la a cada uma das linhas. 
 
function pesquisaV(x,v) result(b) 
real, intent(in) :: x 
real, dimension(:), intent(in) :: v 
logical :: b 
integer :: j 
 
b=.false. 
do j=1,size(v) 
 if (x==v(j)) then 
 b=.true. 
 exit 
 end if 
end do 
end function pesquisaV 
 
Esta função percorre os elementos do vector um a um até encontrar o elemento x e, caso encontre, coloca 
a variável resultado a .true. e termina a execução do ciclo do (atraves do comando exit). Podemos 
agora utilizar esta função para pesquisar numa linha de uma matriz. Podemos referir-nos a uma 
determinada linha i de uma matriz m através da expressão: 
 
m(i,1:size(m,2)) 
 
assim como nos podemos referir a uma determinada coluna j da mesma matriz através da expressão 
 
m(1:size(m,1),j) 
 
Assim, pesquisar se um elemento ocorre numa matriz consiste em pesquisar se ele ocorre em alguma das 
linhas e, caso ocorra, terminar a pesquisa com sucesso. Apresenta-se em seguida uma função para 
pesquisar um elemento numa matriz, utilizando o método descrito: 
 
function pesquisa(x,m) result(b) 
real, dimension(:,:), intent(in) :: m 
real, intent(in) :: x 
logical :: b 
integer :: i 
 
b=.false. 
do i=1,size(m,1) 
 if (pesquisaV(x,m(i,1:size(m,2)))) then 
 b=.true. 
 exit 
 end if 
end do 
end function pesquisa 
 
Alternativamente, podíamos ter optado por definir a função de pesquisa anterior sem recorrer à função 
auxiliar sobre vectores. Esta solução tem a vantagem de não duplicar informação, que resulta de calcular 
cada uma das linhas para em seguida lhes aplicar a função pesquisaV. Neste caso, há que recorrer a 
dois ciclos. A pesquisa termina quando se percorrer a matriz toda ou quando se encontrar o elemento. 
 
function pesquisa(x,m) result(b) 
 27 
real, dimension(:,:), intent(in) :: m 
real, intent(in) :: x 
logical :: b 
integer :: i, j 
 
b=.false. 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 if (x==m(i,j)) then 
 b=.true. 
 exit 
 end if 
 end do 
 if (b) then 
 exit 
 end if 
end do 
end function pesquisa 
 
Por fim, considere-se a função para multiplicar duas matrizes, desde que estas sejam compatíveis. Tal 
como no exemplo anterior, começamos por apresentar uma solução que recorre a uma função auxiliar 
para calcular o produto interno de uma linha por uma coluna, ou seja, uma função que recebe dois 
vectores do mesmo tamanho e calcula o respectivo produto interno. 
 
function prodLC(l,c) result(x) 
real, dimension(:), intent(in) :: l,c 
real :: x 
integer :: k 
 
x=0 
do k=1,size(l) 
 x=x+l(k)*c(k) 
end do 
end function prodLC 
 
Recorrendo a esta função, podemos definir o produto de duas matrizes, desde que estas sejam 
compatíveis, calculando para cada (i,j) o produto interno da linha i da primeira matriz pela coluna j 
da segunda matriz. 
 
function prodM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m2,2)) :: a 
integer :: i,j 
 
if (size(m1,2)==size(m2,1)) then 
 do i=1,size(m1,1) 
 do j=1,size(m2,2) 
 a(i,j)=prodLC(m1(i,1:size(m1,2)),m2(1:size(m2,1),j)) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes nao sao compativeis" 
end if 
end function prodM 
 
Alternativamente, podemos definir a função directamente, como se ilustra em seguida: 
 
function prodM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m2,2)) :: a 
real :: x 
 28 
integer :: i,j,k 
 
if (size(m1,2)==size(m2,1)) then 
 do i=1,size(m1,1) 
 do j=1,size(m2,2) 
 x=0 
 do k=1,size(m1,2) 
 x=x+m1(i,k)*m2(k,j) 
 end do 
 a(i,j)=x 
 end do 
 end do 
else 
 print *,"Erro! As matrizes nao sao compativeis" 
end if 
end function prodM 
 
Deixa-se como exercício testar as funções anteriores. 
 
Programação recursiva 
 
Apresentam-se em seguida algumas das funções sobre vectores apresentadas anteriormente, mas 
desenvolvidas num estilo recursivo. Como já se viu, uma função ou subrotina recursiva deve ser 
declarada como tal, ou seja, com a declaração recursive. 
 
Como primeiro exemplo, considere-se o problema de somar os elementos de um vector. A função 
somaVecR recebe como argumento um vector e devolve a soma dos seus elementos. Utiliza a função 
recursiva auxiliar somaVecAux, que tem dois argumentos, um vector e uma posição, e calcula a soma 
dos elementos do vector a partir da posição dada. Note-se que a soma dos elementos de um vector pode 
ser definida recursivamente, observando que a soma dos elementos a partir de uma dada posição é o 
resultado de somar o elemento que está nessa posição com a soma dos elementos nas posições seguintes, 
isto é: 
 
somaVecAux(v,i)=v(i)+somaVecAux(v,i+1) 
 
Claro que a expressão anterior apenas se verifica se i for menor ou igual do que o comprimento do 
vector. Caso exceda este valor, a soma dos elementos do vector deverá ser 0. 
 
Apresenta-se em seguida a definição destas duas funções: 
 
function somaVec(v) result(x) 
integer, dimension(:), intent(in) :: v 
integer :: x 
 
x=somaVecAux(v,1) 
end functionsomaVec 
 
recursive function somaVecAux(v,i) result(x) 
integer, dimension(:), intent(in) :: v 
integer, intent(in) :: i 
integer :: x 
 
if (i>size(v)) then 
 x=0 
else 
 x=v(i)+somaVecAux(v,i+1) 
end if 
end function somaVecAux 
 
Deixa-se como exercício testar esta função. 
 
 29 
Considera-se em seguida o problema de calcular o produto interno de dois vectores. Novamente, 
considera-se uma função prodInterno, que recorre a uma função recursiva auxiliar prodIntAux 
para calcular o produto interno de dois vectores a partir de uma determinada posição. Observa-se 
facilmente que o problema de calcular o produto interno a partir de uma determinada posição pode ser 
definido recursivamente somando o produto da posição que está a ser considerada dois vectores com o 
produto interno dos dois vectores a partir da posição seguinte: 
 
prodIntAux(v1,v2,i)=v1(i)*v2(i)+prodIntAux(v1,v2,i+1) 
 
Novamente, a condição anterior só se verifica se i for menor do que o comprimento dos dois vectores. A 
função principal deverá começar por testar se os dois vectores são do mesmo tamanho. 
 
function prodInterno(v1,v2) result(x) 
integer, dimension(:), intent(in) :: v1,v2 
integer :: x 
 
if (size(v1)/=size(v2)) then 
 print *,"Erro! Os vectores nao sao do mesmo tamanho." 
else 
 x=prodIntAux(v1,v2,1) 
end if 
end function prodInterno 
 
recursive function prodIntAux(v1,v2,i) result(x) 
integer, dimension(:), intent(in) :: v1,v2 
integer, intent(in) :: i 
integer :: x 
 
if (i>size(v1)) then 
 x=0 
else 
 x=v1(i)*v2(i)+prodIntAux(v1,v2,i+1) 
end if 
end function prodIntAux 
 
Em seguida considera-se o problema de pesquisar um número num vector ordenado. Neste caso, recorre-
se ao algoritmo de pesquisa binária: sabendo que o vector está ordenado (por ordem crescente), para 
procurar um elemento x nesse vector basta comparar o elemento com o elemento no meio do vector. Uma 
de três situações pode acontecer: x é igual ao elemento do meio, e nesse caso termina-se a pesquisa com 
sucesso; x é maior do que o elemento do meio, e nesse caso basta pesquisar do elemento do meio para a 
direita; x é menor do que o elemento do meio, e neste caso apenas é necessário pesquisar do elemento do 
meio para a esquerda. Esta pesquisa é feita recursivamente. Neste caso, no entanto, há que considerar 
duas posições do vector delimitando os extremos do intervalo do vector onde está a ser efectuada a 
pesquisa (que podem não coincidir com os extremos do vector). Apresenta-se em seguida a definição da 
função recursiva para pesquisar se um determinado elemento ocorre entre duas posições de um vector 
ordenado: 
 
recursive function pesquisaAux(x,v,p,u) result(b) 
integer, intent(in) :: x,p,u 
integer, dimension(:), intent(in) :: v 
logical :: b 
integer :: m 
 
if (u<p) then 
 b=.false. 
else 
 m=(p+u)/2 
 if (x==v(m)) then 
 b=.true. 
 else if (x>v(m)) then 
 b=pesquisaAux(x,v,m+1,u) 
 30 
 else 
 b=pesquisaAux(x,v,p,m-1) 
 end if 
end if 
end function pesquisaAux 
 
Esta função é utilizada pela função de pesquisa binária: 
 
function pesquisa(x,v) result(b) 
integer, intent(in) :: x 
integer, dimension(:), intent(in) :: v 
logical :: b 
 
b=pesquisaAux(x,v,1,size(v)) 
end function pesquisa 
 
Finalmente, conisdera-se o problema de multiplicar um vector por um escalar. Neste caso, optou-se por 
definir uma subrotina recursiva que altera directamente o vector argumento. Para tal, é necessário definir 
o parâmetro correspondente ao vector como sendo de entrada e saída (inout). De resto, a subrotina 
recursiva é em tudo semelhante à função para somar os elementos de um vector. 
 
recursive subroutine prodEscalarAux(v,x,i) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: x,i 
 
if (i==size(v)) then 
 v(i)=v(i)*x 
else 
 v(i)=v(i)*x 
 call prodEscalarAux(v,x,i+1) 
end if 
end subroutine prodEscalarAux 
 
A função que calcula o produto escalar utiliza a função auxiliar anterior: 
 
subroutine prodEscalar(v,x) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: x 
 
call prodEscalarAux(v,x,1) 
end subroutine prodEscalar 
 
recursive subroutine prodEscalarAux(v,x,i) 
integer, dimension(:), intent(inout) :: v 
integer, intent(in) :: x,i 
 
if (i==size(v)) then 
 v(i)=v(i)*x 
else 
 v(i)=v(i)*x 
 call prodEscalarAux(v,x,i+1) 
end if 
end subroutine prodEscalarAux 
 
 31 
Capítulo 3 Unidades de programa: módulos 
 
Objectivos 
Unidades de programa: módulos. Funções como argumento de funções. 
 
Unidades de programa 
 
A definição de funções e subrotinas em F pode ser feita dentro do corpo principal do programa, como foi 
ilustrado atrás, ou fora deste, numa unidade à parte chamada módulo. Um módulo é uma unidade de 
programa que contém, entre outras, definições de funções e subrotinas, e que não pode ser executada 
directamente, podendo apenas ser utilizada por outros programas. A vantagem de um módulo é que pode 
ser utilizado por diferentes programas sem necessidade de repetir o código. Considerem-se as funções 
sobre matrizes definidas no capítulo anterior. Podemos definir um módulo que disponibilize estas funções 
e que poderá ser utilizado por um ou mais programas que necessitem de algumas dessas funções e 
subrotinas sem ser necessário repetir o respectivo código. 
 
Apresenta-se em seguida um módulo que disponibiliza algumas funções sobre matrizes. 
 
module mmatrices 
 
public :: somaEl, prodEscalar, somaM, prodM, pesquisa 
private :: prodLC 
 
contains 
 
function somaEl(m) result(x) 
real, dimension(:,:), intent(in) :: m 
real :: x 
integer :: i, j 
 
x=0 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 x=x+m(i,j) 
 end do 
end do 
end function somaEl 
 
function pesquisa(x,m) result(b) 
real, dimension(:,:), intent(in) :: m 
real, intent(in) :: x 
logical :: b 
integer :: i, j 
 
b=.false. 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 if (x==m(i,j)) then 
 b=.true. 
 exit 
 end if 
 end do 
 if (b) then 
 exit 
 end if 
end do 
end function pesquisa 
 
subroutine prodEscalar(m,x) 
real, dimension(:,:), intent(inout) :: m 
 32 
real, intent (in) :: x 
integer :: i,j 
 
do i=1,size(m,1) 
 do j=1,size(m,2) 
 m(i,j)=m(i,j)*x 
 end do 
end do 
end subroutine prodEscalar 
 
function somaM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m1,2)) :: a 
integer :: i,j 
 
if ((size(m1,1)==size(m2,1)).and.(size(m1,2)==size(m2,2))) then 
 do i=1,size(m1,1) 
 do j=1,size(m1,2) 
 a(i,j)=m1(i,j)+m2(i,j) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes não são da mesma forma" 
end if 
end function somaM 
 
function prodM(m1,m2) result(a) 
real, dimension(:,:), intent(in) :: m1,m2 
real, dimension(size(m1,1),size(m2,2)) :: a 
integer :: i,j 
 
if (size(m1,2)==size(m2,1)) then 
 do i=1,size(m1,1) 
 do j=1,size(m2,2) 
 a(i,j)=prodLC(m1(i,1:size(m1,2)),m2(1:size(m2,1),j)) 
 end do 
 end do 
else 
 print *,"Erro! As matrizes nao sao compativeis" 
end if 
end function prodM 
 
function prodLC(l,c) result(x) 
real, dimension(:), intent(in) :: l,c 
real :: x 
integer :: k 
 
x=0 
do k=1,size(l) 
 x=x+l(k)*c(k) 
end do 
end function prodLC 
end module mmatrices 
 
O modulo anterior é constituido por duas partes: a primeira parte, entre o início do módulo e a instrução 
contains; a segunda parte, desde a instrução contains até ao fim do módulo. 
 
Na primeira parte declaram-se eventuais módulos que este módulo possa utilizar, funções e subrotinas 
que o módulo disponibiliza (através da instrução public), funções e subrotinas auxiliares (através da 
instrução private), declarações de variáveis, entre outras de que veremosexemplos adiante. No caso 
 33 
do exemplo, o módulo apenas disponibiliza as funções e subroutines somaEl, prodEscalar, somaM, 
prodM e pesquisa, já descritas anteriormente, através da declaração 
 
public :: somaEl, prodEscalar, somaM, prodM, pesquisa 
 
e define ainda uma função auxiliar, prodLC, que embora seja implementada no módulo não pode ser 
utilizada fora deste pois foi declarada como privada, através da declaração 
 
private :: prodLC 
 
Na segunda parte, definem-se as funções e subrotinas do módulo, quer as que são disponibilizadas pelo 
módulo quer as auxiliares. As funções auxiliares apenas podem ser referênciadas dentro do módulo, por 
outras funções, não sendo conhecidas fora deste. No caso do módulo anterior, a função prodLC apenas 
pode ser utilizada dentro do módulo (como de facto é, pela função prodM), não podendo ser utilizada por 
nenhum programa que utilize este módulo. 
 
Um programa pode utilizar um módulo através da instrução use. O programa seguinte utiliza o módulo 
mmatrices para multiplicar duas matrizes. 
 
program testeProd 
 
use mmatrices 
 
real, dimension(2,2) :: m1,m2,m3 
 
m1(1,1:2)=(/ 1.0,2.5 /) 
m1(2,1:2)=(/ 4.5,1.1 /) 
m2(1,1:2)=(/ 1.3,2.3 /) 
m2(2,1:2)=(/ 5.5,0.5 /) 
m3=prodM(m1,m2) 
print *,m3(1,1:2) 
print *,m3(2,1:2) 
end program testeProd 
 
Ao utilizar o módulo mmatrices, o programa anterior tem à disposição todas as funções 
disponibilizadas (públicas) por este módulo. 
 
A compilação do programa anterior é, no entanto, um pouco mais complexa. Em primeiro lugar, há que 
compilar o módulo. Como o resultado não é um ficheiro executável, há que indicar isso aquando da 
compilação, através da opção –c: 
 
> f mmatrices -c 
 
A instrução anterior gera um ficheiro mmatrices.o. Cada módulo só precisa de ser compilado quando 
é criado e de cada vez que é alterado (mas não quando é utilizado). Uma vez compilado o módulo, 
podemos compilar o programa. O processo de compilação é semelhante ao visto anteriormente, sendo 
apenas necessário acrescentar o nome do(s) módulo(s) que o programa utiliza. Assim, o programa 
anterior seria compilado através da instrução: 
 
> f testeProd.f95 mmatrices.o –o testeProd.exe 
 
Enquanto as extensões .f95 e .exe são opcionais, a extensão .o do módulo não é. Tem que se indicar 
explicitamente. 
 
 
A estrutura genérica de um módulo é a seguinte: 
 
module nome 
[use módulos 
private] 
 34 
public :: nomes 
private :: nomes 
outras declarações 
 
contains 
definição de funções e subrotinas 
end module nome 
 
 
Funções como argumento de procedimentos 
 
Tal como foi visto anteriormente, é muitas vezes necessário definir funções ou subrotinas cujos 
parâmetros são eles próprios funções. Pretende-se definir uma função integrate que recebe como 
argumentos uma função real de variável real f e dois números reais a e b e um número inteiro n e calcula 
o integral de f no intervalo [a,b] (dividido em n subintervalos), tal como foi descrito anteriormente. A 
declaração de um parâmetro de tipo função é feita através da declaração da interface da função, onde se 
indicam quais os tipos dos parâmetros e qual o tipo do resultado. No caso presente, pretende-se que o 
parâmetro f seja uma função com um parâmetro real e devolvendo um número real, o que é obtido 
através da declaração seguinte: 
 
interface 
 function f(x) result(y) 
 real, intent(in) :: x 
 real :: y 
 end function f 
end interface 
 
Uma solução possível para a função integrate, disponibilizada pelo módulo mnumeric.f95, é a 
seguinte: 
 
function integrate(f,a,b,n) result(y) 
interface 
 function f(x) result(y) 
 real, intent(in) :: x 
 real :: y 
 end function f 
end interface 
real, intent(in) :: a,b 
integer, intent(in) :: n 
real :: y 
real:: d,x,s 
integer :: i 
 
d=(b-a)/n 
s=0 
x=a 
do i=1,n 
 s=s+f(x) 
 x=x+d 
end do 
y=d*s 
end function integrate 
 
O funcionamento desta função já foi descrito anteriormente. A principal diferença reside na necessidade 
de declaração dos parâmetros e das variáveis auxiliares e, em particular, na declaração do parâmetro f. 
Repare-se que, uma vez declarado, este parâmetro pode ser usado como uma função real de variável real, 
como acontece na expressão s=s+f(x). 
 
O programa seguinte utiliza esta função para calcular o integral da função quad (definida pela expressão 
analítica -x2-x+2), disponibilizada no módulo mquad. 
 35 
 
program testintegrate 
 
use mnumeric 
use mquad 
 
real :: a,b 
integer :: n 
 
read *,a 
read *,b 
read *,n 
print *,"O integral da funcao -x^2-x+2" 
print *,"no intervalo [",a,",",b,"]" 
print *,"e: ",integrate(quad,a,b,n) 
 
end program testintegrate 
 
O programa solicita ao utilizador os extremos do intervalo bem como o número de subdivisões pretendido 
e calcula o respectivo integral, de acordo com o método apresentado. 
 
Seguem-se alguns exemplos de execução: 
 
> integrate 
 -2.0 
 2.0 
 100 
 O integral da funcao -x^2-x+2 
 no intervalo [ -2.0000000 , 2.0000000 ] 
 e: 2.7456055 
 
> integrate 
 -3.0 
 3.0 
 10000 
 O integral da funcao -x^2-x+2 
 no intervalo [ -3.0000000 , 3.0000000 ] 
 e: -5.9991608 
 
Note-se que a função que vai ser passada como argumento (neste caso a função quad) tem que estar 
definida num módulo diferente do da função que a vai receber como parâmetro (neste caso, a função 
integrate). Com efeito, a função quad está definida no módulo mquad e a função integrate está 
definida no módulo mnumeric. 
 
 
 
 36 
Capítulo 4 Programação em grande escala 
 
Objectivos 
 
Objecto como área de memória. Objectos versus valores. Tipos de objectos. Método de programação 
modular por camadas baseadas em objectos. Primeiro exemplo: torres de Hanoi sobre camada disponi-
bilizando pilhas de inteiros. Utilização dos módulos de F para definir camadas. Implementação das pilhas 
com vectores. Apontadores em F. Implementação das pilhas com apontadores. Vectores dinâmicos em F. 
Implementação das pilhas com vectores dinâmicos. Exemplo complementar: filas de espera implemen-
tadas com apontadores. 
 
Programação modular por camadas baseadas em objectos 
 
Recorde-se que programar imperativamente consiste em mandar o computador executar uma sequência 
de instruções para ir alterando os valores guardados na memória até se obter o resultado pretendido. Cada 
célula de memória (variável) pode guardar valores de um certo tipo. Por exemplo, já encontrámos 
variáveis de tipo integer que podem guardar valores inteiros. Para além dos tipos básicos primitivos 
disponibilizados pela linguagem F (integer, real, logical, ...) é possível ainda declarar variáveis 
de tipo tabular (vectores, matrizes,...). Põe-se naturalmente a questão de saber se será útil declarar e 
trabalhar com variáveis de outros tipos definidos por nós. De facto assim é! Mais ainda, um dos critérios 
mais importantes para avaliar quão eficaz é uma linguagem de programação consiste em analisar as 
facilidades da linguagem para definir novos tipos de objectos, isto é, novos tipos de variáveis de 
memória. 
 
A linguagem F é particularmente rica neste aspecto como veremos de seguida. Mas vale a pena motivar 
desde já a importância de definir e trabalhar com objectos de tipos não primitivos, isto é, definidos por 
nós. Considere-se o problema (fictício) das torres de Hanoi (http://www.cs.wm.edu/~pkstoc/toh.html). 
Os monges de Hanoi acreditam que quando acabarem de transferir um a um os sessenta e quatro discos da 
torre 1 para a torre 2, usando a torre 3 como auxiliar, nunca sobrepondo um disco maior a um disco 
menor, o universo acaba por a humanidade ter concluído a tarefa para que foi criada. 
 
 
 
 
 
 
 
 
 
 torre 1 torre 2 torre 3 
 
 
Pretendemos modelar esta tarefa em computador, representando cada torre por um objectode tipo 
apropriado na memória do computador. O tipo relevante é um tipo bem comum que surge em inúmeros 
problemas computacionais. Trata-se do tipo pilha (stack). Os objectos deste tipo são referenciáveis e 
manipuláveis através das funções e subrotinas seguintes: 
 
 new() é uma função sem parâmetros que devolve a pilha vazia; 
 push(x,s) é uma subrotina que quando invocada sobrepõe o elemento x à pilha s; 
 pop(s) é uma subrotina que quando invocada retira o elemento que está no topo da pilha s; 
 top(s) é uma função devolve o topo da pilha s; 
 emptyQ(s) é uma função que testa se a pilha s está vazia. 
 
 37 
Com efeito cada torre pode ser vista como um objecto de tipo pilha de naturais (representando cada disco 
por um número natural reflectindo o seu tamanho). Assim, o facto de só podermos retirar e colocar um 
elemento de cada vez pelo topo da torre é reflectido pelo conjunto de operações disponíveis para 
manipular as pilhas: push serve para colocar um elemento no topo e pop serve para retirar o elemento 
no topo. 
 
Assumindo que, de algum modo, somos capazes de definir em F o tipo stack, o programa seguinte 
modela a tarefa com que se debatem os monges de Hanoi: 
 
n = 64 
call setup(n) 
call move(n,1,2,3) 
 
em que 
 
subroutine setup(n) 
integer, intent(in) :: n 
integer :: i 
 
t(1) = new() 
t(2) = new() 
t(3) = new() 
k = 0 
do i = n,1,-1 
 call push(i,t(1)) 
end do 
call display() 
end subroutine setup 
 
recursive subroutine move(m,s,d,w) 
integer, intent(in) :: m,s,d,w 
 
if (m == 1) then 
 call push(top(t(s)),t(d)) 
 call pop(t(s)) 
 k = k + 1 
 call display() 
else 
 call move(m-1,s,w,d) 
 call push(top(t(s)),t(d)) 
 call pop(t(s)) 
 k = k + 1 
 call display() 
 call move(m-1,w,d,s) 
end if 
end subroutine move 
 
O programa anterior utiliza duas variáveis t e k. A variável t é um vector de tamanho 3 e de tipo 
stack, correspondendo às 3 torres de Hanoi. A variável k é utilizada para contar o número de 
movimentos efectuados. Fazendo correr com n=4 obtemos (a subrotina display é descrita a seguir). 
 
 | 4 3 2 1 
 | 
 | 
 --------- move 0 
 | 4 3 2 
 | 
 | 1 
 --------- move 1 
 | 4 3 
 | 2 
 38 
 | 1 
 --------- move 2 
 | 4 3 
 | 2 1 
 | 
 --------- move 3 
 | 4 
 | 2 1 
 | 3 
 --------- move 4 
 | 4 1 
 | 2 
 | 3 
 --------- move 5 
 | 4 1 
 | 
 | 3 2 
 --------- move 6 
 | 4 
 | 
 | 3 2 1 
 --------- move 7 
 | 
 | 4 
 | 3 2 1 
 --------- move 8 
 | 
 | 4 1 
 | 3 2 
 --------- move 9 
 | 2 
 | 4 1 
 | 3 
 --------- move 10 
 | 2 1 
 | 4 
 | 3 
 --------- move 11 
 | 2 1 
 | 4 3 
 | 
 --------- move 12 
 | 2 
 | 4 3 
 | 1 
 --------- move 13 
 | 
 | 4 3 2 
 | 1 
 --------- move 14 
 | 
 | 4 3 2 1 
 | 
 --------- move 15 
 
Vale a pena explicar como funciona a subrotina move que é o elemento crucial do programa. Em geral, o 
problema de mover n discos de um poste s para um poste d resume-se a mover n-1 discos do poste de s 
para o outro poste, w, ficando apenas um disco em s. Este disco pode ser então movido para o poste d 
usando as operações sobre pilhas definidas. Finalmente, há que mover os n-1 discos que estão em w para 
d. Para mover n-1 discos, há que primeiro mover n-2, e assim sucessivamente, até se chegar a um 
ponto em que apenas falta mover um disco. 
 39 
 
Este problema das torres de Hanoi foi resolvido seguindo o método de programação por camadas 
baseadas em objectos, o qual consiste em: 
 
1. Identificar a camada dos tipos de objectos relevantes. [Neste caso precisamos de uma camada 
que disponibilize objectos de tipo pilha de inteiros.] 
2. Desenvolver o programa abstracto supondo que a camada existe. [Neste caso escrevemos o 
programa acima auxiliado pelas subrotinas setup e move.] 
3. Implementar a camada de modo eficiente sobre os tipos primitivos da linguagem F. [Veremos 
adiante como tal pode ser feito usando a construção module da linguagem F] 
4. Integrar o programa abstracto e a implementação da camada no programa operacional. [Veremos 
adiante como utilizar uma camada já definida por um módulo.] 
 
Note-se que este método pode ser aplicado iterativamente, conduzindo a uma sequência de camadas em 
que a mais inferior se implementa directamente sobre os tipos de objectos primitivos da linguagem F e em 
que cada camada se implementa sobre a que lhe está imediatamente abaixo. Nos capítulos seguintes (por 
exemplo no dedicado à simulação estocástica) veremos exemplos de aplicação iterativa do método. Neste 
primeiro exemplo do problema das torres de Hanoi, como já vimos, é necessária apenas uma camada para 
facultar o tipo das pilhas de inteiros. 
 
As vantagens deste método são inegáveis, incluindo: 
 
 Separação entre os aspectos procedimentais e a estruturação dos objectos de trabalho, que nos 
permite concentrar em cada um dos subproblemas separadamente ou que sejam programadores 
diferentes encarregados do programa abstracto e da implementação da camada. [No caso 
vertente, é realmente vantajoso tentar escrever a subrotina move sem termos de nos preocupar ao 
mesmo tempo com a manipulação das torres (pilhas).] 
 Independência da implementação: o programa abstracto é independente dos detalhes de 
implementação da camada, o que permite ulteriormente alterar a implementação da camada (por 
exemplo, para a optimizar) sem ser necessário modificar o programa. Para tal, é importante que a 
linguagem de programação utilizada permita esconder os detalhes da implementação de uma 
camada. Como veremos, a construção module da linguagem F permite declarar como public / 
private os elementos da camada que pretendemos que sejam visíveis / invisíveis do exterior. 
Assim, o programador responsável pelo programa abstracto nem inadvertidamente poderá 
utilizar os detalhes irrelevantes da implemantação. [No caso vertente, tudo o que foi necessário 
saber sobre a camada das pilhas foi o conjunto das funções (new, top, emptyQ) e subrotinas 
(push, pop) disponibilizadas bem como dos tipos definidos (no caso apenas um, stack).] 
 Reutilização de código: uma camada pode ser utilizada em muitos outros programas pois os 
objectos tendem a ser muito mais universais que as soluções procedimentais. [No caso vertente, 
é de facto assim: as pilhas são úteis em inúmeros problemas, mas a subrotina move só é 
interessante para resolver o problema das torres de Hanoi.] 
 
Voltando ao problema das torres de Hanoi, já vimos antes como foram aplicados os passos 1 e 2 do 
método de programação modular. O passo seguinte (passo 3) consiste em definir a implementação da 
camada que disponibiliza o tipo das pilhas de inteiros: 
 
module mbstacks 
 
public :: new, push, pop, top, emptyQ, fullQ, erase, show 
 
type, public :: stack 
 private 
 integer :: index 
 integer, dimension(1:64) :: array 
end type stack 
 
contains 
 
function new() result(r) 
type(stack) :: r 
 40 
 
r%index=0 
end function new 
subroutine push(x,s) 
integer, intent(in) :: x 
type(stack), intent(inout) :: s 
 
if (s%index<64) then 
 s%index=s%index+1 
 s%array(s%index)=x 
end if 
end subroutine push 
 
subroutine pop(s) 
type(stack), intent(inout) :: s 
 
if (s%index>0) then 
 s%index=s%index-1 
end if 
end subroutine pop 
 
function top(s) result(y) 
type(stack), intent(in) :: s 
integer :: y 
 
if (s%index>0) then 
 y=s%array(s%index) 
end if 
end function top 
 
function emptyQ(s) result(b) 
type(stack), intent(in) :: s 
logical :: b 
 
if (s%index==0) then 
 b=.true. 
else 
 b=.false. 
end if 
end function emptyQ 
 
function fullQ(s) result(b) 
type(stack), intent(in) :: s 
logical :: b 
 
if (s%index==64) then 
 b=.true. 
else 
 b=.false. 
end if 
end function fullQ 
 
subroutine erase(s) 
type(stack),intent(inout) :: s 
 
s%index=0 
end subroutine erase 
 
subroutine show(s) 
type(stack), intent(in) :: s 
 
 41 
print *, "|", s%array(1:s%index) 
end subroutine show 
end module mbstacks 
 
Vale a pena comentar as diversas componentes deste módulo. Comecemos pela definição do tipo 
stack: 
 
type, public :: stack 
 private 
 integer :: index 
 integer, dimension(1:64) :: array 
end type stack 
 
 
Esta construção permite definir um novo tipo: uma estrutura com duas componentes, um número inteiro, 
referenciado por index, e um vector de números inteiros de comprimento 64, referenciado por array. 
A figura seguinte ilustra um objecto deste tipo (uma pilha contendo, por ordem de sobreposição, os 
elementos 11, 22, 33, 44): 
 
 
 
 
 11 22 33 44 ... array 
 
 
 4 index 
 
Os objectos deste tipo vão ser utilizados para representar pilhas de inteiros (de profundidade máxima 64), 
em que a componente array serve para armazenar os elementos da pilha, a componente index guarda 
a posição onde se encontra o elemento topo da pilha. A utilização do comando private na definição do 
tipo impede que a estrutura deste seja conhecida fora do módulo onde este foi definido. 
 
A declaração de variáveis de um tipo definido pelo utilizador é semelhante à definição de variáveis de um 
tipo primitivo. Considere-se a declaração de uma variável t de tipo stack: 
 
type(stack) :: t 
 
A manipulação de objectos deste tipo é feita da seguinte forma: t%index permite aceder à componente 
index, que é de tipo integer; t%array permite aceder à componente array, que é um vector de 
inteiros. 
 
Consideremos agora as funções e subrotinas para manipular objectos deste tipo. A função new devolve a 
pilha vazia, ou seja, cria um novo objecto de tipo pilha (através da declaração da variável r), colocando a 
componente index a zero (a pilha ainda não tem nenhum elemento): 
 
function new() result(r) 
type(stack) :: r 
 
r%index = 0 
end function new 
 
A subrotina push recebe dois argumentos, x inteiro e s stack, e sobrepõe o elemento x na pilha s, ou 
seja, há que incrementar o index de uma posição, o novo topo, e guardar x nessa posição do array: 
 
subroutine push(x,s) 
integer, intent(in) :: x 
type(stack), intent(inout) :: s 
 
if (s%index < 64) then 
 42 
 s%index = s%index + 1 
 s%array(s%index) = x 
end if 
end subroutine push 
 
Se o sistema estiver num estado em que o conteúdo da variável s é: 
 
 
 
 11 22 33 44 ... array 
 
 
 4 index 
 
após a execução push(55,s) o conteúdo da variável s será: 
 
 
 
 11 22 33 44 55 ... array 
 
 
 5 index 
 
A subrotina pop recebe como argumento uma pilha s, à qual é retirado o elemento no topo. Para tal, 
basta decrementar a posição do topo, guardada em index: 
 
subroutine pop(s) 
type(stack), intent(inout) :: s 
 
if (s%index > 0) then 
 s%index = s%index - 1 
end if 
end subroutine pop 
 
Se estivermos num estado em que o conteúdo da variável s é: 
 
 
 
 11 22 33 44 55 ... array 
 
 
 5 index 
 
após a execução pop(s) o conteúdo da variável s será: 
 
 
 
 11 22 33 44 55 ... array 
 
 
 4 index 
 
Repare-se que o conteúdo de array da posição 4 em diante é irrelevante. São posições que estão acima 
do topo (podemos considerar que são lugares livres, disponíveis para armazenar informação no futuro). 
Por isso mesmo a subrotina pop não precisa de apagar o número 55. Basta decrementar o valor de 
index. Se, em seguida, for sobreposto um novo elemento em s, através da subrotina push, esse valor 
será escrito por cima do 55. 
 
 43 
A função top devolve o elemento topo de uma pilha s (caso exista). O topo da pilha é o valor que está 
na posição de array guardada em index: 
 
function top(s) result(y) 
type(stack), intent(in) :: s 
integer :: y 
 
if (s%index > 0) then 
 y = s%array(s%index) 
end if 
end function top 
 
Se estivermos num estado em que o conteúdo da variável s é: 
 
 
 
 11 22 33 44 55 ... array 
 
 
 4 index 
 
então o topo de s será o valor guardado na posição 4 (index) do array, ou seja, 44. 
 
A função emptyQ recebe como argumento uma pilha s testa se esta se encontra vazia, ou seja, testa se 
index é 0. 
 
function emptyQ(s) result(b) 
type(stack), intent(in) :: s 
logical :: b 
 
if (s%index == 0) then 
 b = .true. 
else 
 b = .false. 
 end if 
end function emptyQ 
 
O módulo apresentado inclui outras funções úteis para manipular pilhas, cuja análise se deixa como 
exercício. 
 
Finalmente estamos em condições de ligar o programa abstracto à camada que acabámos de definir. O 
resultado deste passo (o passo 4) do método resulta neste caso no programa operacional seguinte: 
 
program bhanoi 
 
use mbstacks 
 
type(stack), dimension(1:3) :: t 
integer :: n,k 
 
n = 4 
call setup(n) 
call move(n,1,2,3) 
 
contains 
 
subroutine display() 
 
call show(t(1)) 
call show(t(2)) 
 44 
call show(t(3)) 
print *, repeat("-",2*n+1)," move ", k 
end subroutine display 
 
subroutine setup(n) 
integer, intent(in) :: n 
integer :: i 
 
t(1) = new() 
t(2) = new() 
t(3) = new() 
k = 0 
do i = n,1,-1 
 call push(i,t(1)) 
end do 
call display() 
end subroutine setup 
 
recursive subroutine move(m,s,d,w) 
integer, intent(in) :: m,s,d,w 
 
if (m == 1) then 
 call push(top(t(s)),t(d)) 
 call pop(t(s)) 
 k = k + 1 
 call display() 
else 
 call move(m-1,s,w,d) 
 call push(top(t(s)),t(d)) 
 call pop(t(s)) 
 k = k + 1 
 call display() 
 call move(m-1,w,d,s) 
end if 
end subroutine move 
end program bhanoi 
 
Este programa utiliza três variáveis de trabalho: um vector t de tamanho 3, de tipo pilha (correspondente 
aos três postes), uma variável k, para contar o número de movimentos, e uma variável n, para fixar o 
número de discos. A camada das pilhas é invocada pelo comando: 
 
use mbstacks 
 
que viabiliza a utilização das operações sobre pilhas no contexto do programa bhanoi. 
 
Assim, de facto, temos o programa bhanoi definido sobre a camada mbstacks: 
 
 
 
 
 
 
 
 mfstacks 
 
 Linguagem F 
 
 45 
Note-se que a implementação apresentada acima das pilhas sobre vectores é estática no sentido em que o 
tamanho máximo de cada pilha está fixado (em 64). Se o módulo for invocado com o número de discos 
(n) maior do que 64, a execução termina com um erro (exercício: experimente!). Embora tal não seja 
necessário para resolver o problema clássico das torres de Hanoi (em que n=64), seria bom que 
pudéssemos definir uma implementação mais flexível das pilhas que permitisse trabalhar com pilhas de 
tamanho arbitrário não conhecido no momento da compilação do programa, o que, aliás, é essencial na 
maioria dos problemas que envolvem pilhas (como será ilustrado mais adiante). 
 
De facto, na linguagem F é possível definir objectos dinâmicos cujo tamanho não fica fixado no 
momento da compilação, podendo ser fixado só no momento da criação ou até mesmo alterado durante a 
vida do objecto, como se explica de seguida. 
 
Objectos dinâmicos 
 
No caso de um objecto estático (como todos os que vimos até agora), desde os mais pequenos (por 
exemplo uma variável inteira) aos muito grandes (por exemplo, um vector com um milhão de 
componentes), o seu tamanho (espaço na memória) fica fixado no momento em que o programa é 
compilado e o espaço em memória necessário é afectado ao programa no momento em que a execução 
arranca, sendo libertado quando a execução termina. [No caso de variáveis locais de funções e 
procedimentos (eventualmente com chamadas recursivas) os detalhes do processo de afectação de 
memória são um pouco mais complexos mas não vale a pena neste momento analisá-los.] 
 
Mas há situações em que os objectos estáticos não são satisfatórios. Com efeito, considere-se

Outros materiais