• Nenhum resultado encontrado

Introdução à Programação na linguagem F. Jaime Ramos, Amílcar Sernadas e Paulo Mateus

N/A
N/A
Protected

Academic year: 2021

Share "Introdução à Programação na linguagem F. Jaime Ramos, Amílcar Sernadas e Paulo Mateus"

Copied!
14
0
0

Texto

(1)

Introdução à Programação na linguagem F

Jaime Ramos, Amílcar Sernadas e Paulo Mateus

(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 deu-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). No caso da compilação ser bem sucedida (não ocorrerem erros), o resultado é um ficheiro executável (extensão 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

(3)

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.

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 podemos executá-lo. O resultado é o seguinte: 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.

(4)

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.

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 dois nú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

(5)

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

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 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:

Introduza um numero: 3

O numero e primo. Introduza um numero: 51

O numero nao e primo. 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

(6)

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ã: 2

4 6

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.

(7)

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.

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 n de 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

(8)

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 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 podemos testá-lo. Seguem-se alguns exemplos: Introduza um numero:

4

Existem 2 primos ate 4 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

(9)

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: Introduza um numero: 3 Factorial: 6 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 o número de que pretendemos calcular o factorial. Após a chamada da subrotina, n guarda o valor do factorial.

(12)

Introduza um numero: 3 Factorial: 6 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 apreConsidere-sentadas 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: Introduza um numero: 3 Factorial: 6 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.

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.

(13)

Recorde-se a expressão recursiva para definir o factorial de um número natural: n! =       0 n se 1)! n.(n 0 n se 1

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) endif

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) endif

end function factR end program factorialR

Considerem-se alguns exemplos de utilização: Introduza um numero: 4 Factorial: 24 Introduza um numero: 1 Factorial: 1

Deixa-se como exercício proteger a função contra argumentos indesejados. nomeadamente, contra números negativos.

(14)

Fn= 1 n se 1 n se 0 n se F F 1 0 1 n 2 n           

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: Introduza um número: 2 Numero de Fibonacci: 1 Introduza um número: 6 Numero de Fibonacci: 8

Referências

Documentos relacionados

A seqüência de operações básicas anteriores, para resolver o problema de adicionar dois números, está em uma linguagem de baixo nível para o nosso computador

A humanização do ambiente físico hospitalar deve ter como objetivo tornar mais eficaz o processo terapêutico do paciente, tornando ambientes mais agradáveis para pacientes

Quando os dois termos da divisão de números inteiros são positivos, procedemos de forma análoga à divisão com números naturais?. Na divisão de dois números

122 do CP, o qual prevê a conduta de instigação, auxílio ou induzimento ao suicídio, não admite a forma tentada (art. Nesse sentido, como Maria teve apenas alguns arranhões,

Números inteiros: conjunto Z e seus subconjuntos, representação dos números inteiros na reta numerada, valor absoluto de um número inteiro, operações com números inteiros

(ENEM) O gerente de um cinema fornece anualmente ingressos gratuitos para escolas. Este ano serão distribuídos 400 ingressos para uma sessão vespertina e 320 ingressos para uma

Geralmente é posicionada para disparo um pouco acima de uma forte resistência, ou também usada como stop para liquidar uma venda a descoberto, que nada mais é do que uma

Vale notar que as Tecnologias Digitais de Informação e Comunicação (TDIC) permeiam o seu dia a dia, tal como acontece na chamada vida real dos leitores dessas