Baixe o app para aproveitar ainda mais
Prévia do material em texto
Procedimentos e Funções Conforme os nossos algoritmos vão se tornando mais complexos, nós começamos a sentir a necessidade de utilizar algum mecanismo capaz de ajudar a organizá- los e simplificá-los. Uma forma de realizar essa simplificação é através de sub- rotinas. O conceito de sub-rotinas é criar pequenos algoritmos capazes de realizar tarefas simples, e através da composição desses pequenos algoritmos, criar outros algoritmos maiores e mais complexos. Na linguagem algorítmica as sub-rotinas são implementadas através de procedimentos e funções. Apesar de ainda não sabermos como fazer um procedimento ou uma função, nós já utilizamos muitos procedimentos e funções em nossos programas. Os compiladores das linguagens de programação normalmente disponibilizam uma série de procedimentos e funções que realizam as tarefas mais comuns, como calcular raiz quadrada, seno, cosseno, escrever uma mensagem na tela do computador, etc. Exemplos de procedimentos muito utilizados são os procedimentos que realizam a leitura de informações do teclado e a escrita de informações na tela. Um exemplo de função é a que calcula a raiz quadrada de um número (sqrt). Procedimentos e funções possuem diversos elementos de um verdadeiro programa. Na verdade, um bom procedimento e uma boa função são aqueles que são independentes do resto do programa. Dessa forma, é possível utilizá-los em vários programas, sem realizar modificações. Nas próximas seções vamos ver como são declarados os procedimentos na linguagem algorítmica. Vamos deixar a declaração de funções para depois. Os conceitos de variáveis locais e parâmetros serão vistos e exemplificados através de procedimentos. Apesar de que os conceitos de variáveis locais e parâmetros são aplicáveis tanto aos procedimentos quanto às funções, por simplicidade, vamos começar estudando esses conceitos aplicados apenas aos procedimentos. Depois disso, é fácil entender como eles funcionam nas funções. Procedimentos Os procedimentos são declarados antes do corpo principal do algoritmo. Normalmente, logo depois da declaração de variáveis. A declaração de um procedimento pode ser bastante complexa, entretanto, para declarar um procedimento simples basta utilizar a seguinte sintaxe: Procedimento <nome do procedimento> { <comandos>; } Para entender como funciona um procedimento, vamos ver um exemplo bastante simples: 01 | Algoritmo exemplo; 02 | 03 | Procedimento escreve_ola 04 | { 05 | escreva('Olá'); 06 | } 07 | 08 | { 09 | escreve_ola; 10 | } No algoritmo anterior, nós temos um exemplo de um procedimento bem simples. Esse procedimento está declarado nas linhas de 03 a 06. Dentro desse procedimento existe somente um único comando escreva. Nas linhas 08 a 10 está o corpo principal do algoritmo. A execução de um algoritmo sempre inicia no corpo principal do algoritmo, independentemente desse algoritmo possuir procedimentos e/ou funções declaradas ou não. Portanto, a execução desse algoritmo inicia na linha 08. Na linha 09 é encontrado o identificador escreve_ola. escreve_ola é o nome do procedimento declarado nas linhas 03 a 06. Sempre que o nome de um procedimento ou função aparece em um algoritmo, isso significa que o procedimento ou a função devem ser executados. Mais formalmente, a linha 09 é uma chamada ao procedimento escreve_ola. Portanto, na linha 09 é feito um salto para dentro do procedimento, o procedimento será executado, e ao final da execução do procedimento, a execução voltará para a linha 09 e a execução do corpo principal do algoritmo continuará normalmente. Ao executar o procedimento escreve_ola, é executado o único comando presente nesse procedimento, o escreva na linha 05. Portanto, esse algoritmo escreverá na tela a mensagem Olá. Variáveis Locais e Variáveis Globais Os procedimentos e as funções podem possuir variáveis próprias. Essas variáveis são chamadas de variáveis locais. De aqui por diante, as variáveis declaradas no início de um programa serão chamadas de variáveis globais. Uma variável local a um procedimento ou uma função possui esse nome porque essa variável somente existe enquanto o procedimento ou a função são executados. Por outro lado, as variáveis globais existem durante toda a execução do algoritmo. Uma variável local é declarada dentro de um procedimento com a seguinte sintaxe: Procedimento <nome do procedimento> { <nome da variável>: <tipo>; ... <comandos>; } Vamos ilustrar como funcionam as variáveis locais através de um exemplo: 01 | Algoritmo exemplo_variavel_local; 02 | 03 | { 04 | x: inteiro; 05 | 06 | Procedimento exemplo 07 | { 08 | y: inteiro; 09 | 10 | 11 | y ← 2; 12 | escreva('O valor de y é ', y); 13 | } 14 | 15 | { 16 | x ← 10; 17 | exemplo; 18 | escreva('O valor de x é ', x); 19 | } Neste exemplo, o procedimento exemplo possui uma variável local com identificador y. Essa variável é criada no início da execução desse procedimento, e é descartada no final da execução do procedimento. Dessa forma, a variável local y somente pode ser acessada dentro do procedimento exemplo. Qualquer referência a essa variável fora do procedimento que a pertence, é inválida e resultará em um erro de compilação. Por outro lado, x é uma variável global (foi declarada no início do algoritmo). Por esse motivo, x existe durante toda a execução do algoritmo, e pode ser acessada (lida e modificada) tanto no corpo principal do algoritmo quanto dentro de eventuais procedimentos e funções declarados no algoritmo. A execução do algoritmo acima inicia no corpo principal (linhas 15 a 19). Na linha 16 a variável x recebe o valor 10. Na linha 17, é feita uma chamada ao procedimento exemplo, portanto é feito um salto na execução do programa da linha 17 para a linha 06. No início da execução do procedimento exemplo é criada a variável local y. A partir deste momento, essa variável passa a estar disponível. Na linha 11 a variável y recebe o valor 2, e na linha 12 é escrita a mensagem O valor de y é 2 na tela do computador. A execução chegou ao final do procedimento exemplo. Nesse momento, todas as variáveis locais ao procedimento (no nosso caso se resume à variável y) são descartadas. Portanto y passa a não estar mais disponível e seu valor é perdido. Com o final da execução do procedimento exemplo, a execução volta para a linha 17. Na linha 18 é escrita na tela do computador a mensagem O valor de x é 10. A variável x é uma variável global. Por esse motivo, x existe durante toda a execução do algoritmo, e pode ser acessada (lida e modificada) tanto no corpo principal do algoritmo quanto nos procedimentos. Por exemplo: 01 | Algoritmo exemplo_variavel_global; 02 | 03 | { 04 | x: inteiro; 05 | 06 | Procedimento exemplo 07 | { 08 | y: inteiro; 09 | 10 | 11 | y ← 2; 12 | x ← 4; 13 | escreva('O valor de x é ', x, ' e o valor de y é ', y); 14 | } 15 | 16 | { 17 | x ← 10; 18 | exemplo; 19 | escreva('O valor de x é ', x); 20 | } O exemplo acima é muito semelhante ao exemplo anterior. Somente foi adicionada a expressão x ←←←← 4 na linha 12. É permitido acessar a variável x de dentro do procedimento exemplo uma vez que x é uma variável global. Vamos à execução do algoritmo anterior. A execução inicia na linha 16 e apenas é alocado espaço na memória para as variáveis globais. Portanto, neste momento, somente a variável x está disponível. Na linha 17, x recebe o valor 10, e na linha 18, é feita uma chamada ao procedimento exemplo. Essa chamada faz com que a execução do algoritmo salte para a linha 06. Neste momento são criadas as variáveis locais ao procedimento exemplo, neste caso somente a variável y. Na linha 11 a variável local y recebe o valor 2 e, na linha12 a variável global x recebe o valor 4. Portanto, x que tinha o valor 10 passa a ter o valor 4. Na linha 13 é escrita a mensagem O valor de x é 4 e o de y é 2, e o procedimento exemplo termina a sua execução. O final da execução do procedimento exemplo faz com que todas as variáveis locais a esse procedimento sejam descartadas, no caso, a única variável local é a variável y. O valor de y é perdido e essa variável passa a não estar mais disponível. A execução do programa volta para o corpo principal. Na linha 19 é escrita a mensagem na tela do computador O valor de x é 4, uma vez que o valor dessa variável foi alterado dentro do procedimento exemplo. Apesar de que variáveis globais podem ser acessadas (lidas e modificadas) de dentro de procedimentos e funções, isso é considerado um mau hábito de programação, e deve ser evitado ao máximo. Veremos mais adiante como procedimentos e funções podem trocar dados com o algoritmo principal sem o uso de variáveis globais. Isso pode ser feito através de parâmetros (em procedimentos e funções) e do uso do retorno de valores da função (somente em funções). Um caso especial ocorre quando uma variável local a um procedimento ou a uma função possui o mesmo identificador de uma variável global. Esse caso é considerado válido e não gera nenhum tipo de erro na maioria das linguagens de programação. Apesar de que duas variáveis podem possuir o mesmo identificador (como por exemplo x), elas são consideradas duas variáveis distintas. Dentro do procedimento/função, a variável local (por exemplo, x) é acessada e a variável global (por exemplo, x) se torna inacessível. Fora do procedimento/função somente a variável global x estará disponível. Vamos ver um exemplo. 01 | Algoritmo exemplo_variavel_mesmo_identificador; 02 | 03 | { 04 | x: inteiro; 05 | 06 | Procedimento exemplo 07 | { 08 | x: inteiro; 09 | 10 | 11 | x ← 2; 12 | escreva('O valor de x é ', x); 13 | } 14 | 15 | { 16 | x ← 10; 17 | exemplo; 18 | escreva('O valor de x é ', x); 19 | } Repare que o algoritmo possui duas variáveis com identificador x. Uma delas é global e a outra é local ao procedimento exemplo. Apesar de possuirem o mesmo identificador, essas variáveis são distintas. Vamos ver passo a passo a execução do algoritmo acima. A execução inicia no corpo principal do algoritmo, mais precisamente na linha 15. Esse algoritmo possui apenas uma variável global x, criada (ou seja, carregada na "memória") quando o algoritmo se inicia. Na linha 16, a variável global x recebe o valor 10. Na linha 17 é feita uma chamada ao procedimento exemplo. A execução salta da linha 17 para a linha 06. Ao entrar no procedimento exemplo, a variável local x é criada. A partir deste momento, somente a variável local pode ser acessada, a variável global x se torna inacessível. Na linha 11 a variável local x recebe o valor 2, a variável global x permanece com valor 10. Na linha 12 é escrito na tela do computador O valor de x é 2, e o procedimento exemplo termina. Neste momento, as variáveis locais ao procedimento exemplo são descartadas (no caso somente existe a variável local x). O valor da variável local x é perdido. A execução retorna para o corpo principal do algoritmo. Na linha 18 é escrito na tela do computador o valor da variável global x, através da mensagem O valor de x é 10. Resumindo, procedimentos e funções podem possuir variáveis locais. As variáveis locais somente existem enquanto o procedimento/função é executado(a). Variáveis locais são distintas das variáveis globais, mesmo quando possuem o mesmo identificador. Passagem de Parâmetros Como foi visto anteriormente, as variáveis locais são desalocadas da memória ao final da execução de um procedimento ou função. Também, deve-se evitar, ao máximo, utilizar variáveis globais dentro de procedimentos e funções. Surge, então a questão: como um procedimento ou uma função podem trocar dados com o resto do algoritmo? A resposta é através de parâmetros (para procedimentos e funções) e através do retorno da função (somente para funções). Veremos nas próximas seções como funcionam os parâmetros, e mais a frente como funcionam as funções. Parâmetros são variáveis especiais que permitem a troca de dados entre um procedimento/função e o resto do algoritmo. Existem dois tipos de parâmetros: os de entrada e os de entrada e saída. Veremos como esses parâmetros funcionam nas próximas seções. Parâmetros de Entrada ou Parâmetros Passados por Valor Os parâmetros de entrada utilizam a passagem de parâmetro por valor. Nessa forma de passagem de parâmetro somente é passado o dado (o valor) para o parâmetro. Um parâmetro de entrada é declarado da seguinte forma: Procedimento <nome procedimento>(<nome parâmetro>: <tipo parâmetro>; ...); Vamos ver um exemplo de passagem de parâmetro por valor, e discutir o seu funcionamento: 01 | Algoritmo exemplo_passagem_por_valor; 02 | 03 | { 04 | x: inteiro; 05 | 06 | Procedimento exemplo(y: inteiro) 07 | { 08 | escreva('O valor de y é ', y); 09 | y ← 2; 10 | } 11 | 12 | { 13 | x ← 10; 14 | exemplo(x); 15 | escreva('O valor de x é ', x); 16 | exemplo(5); 17 | } No exemplo acima, o procedimento exemplo possui um parâmetro de entrada (passado por valor) y. Um parâmetro de entrada (por valor) funciona da mesma forma que uma variável local. Entretanto, o parâmetro é inicializado com um valor durante a chamada ao procedimento/função. O parâmetro de entrada pode ter o seu valor modificado livremente dentro do procedimento/função. No caso do exemplo anterior, a primeira chamada ao procedimento exemplo ocorre na linha 14 do corpo do algoritmo principal. A chamada é feita na forma exemplo(x). Repare como a variável x é especificada entre parênteses, nessa situação dizemos que x é o argumento na chamada ao procedimento. No caso dos parâmetros de entrada, os argumentos emprestam os seus valores para os parâmetros. Existe uma segunda chamada ao procedimento exemplo na linha 16 do corpo principal do algoritmo. Nessa chamada é passado como argumento o valor 5. A passagem de constantes como argumento é válida somente para os parâmetros de entrada (ou seja, parâmetros passados por valor). Para que uma chamada a um procedimento seja válida, ambos, o argumento e o parâmetro devem possuir o mesmo tipo. A seguir veremos a execução do algoritmo acima. Na linha 13 é atribuído o valor 10 a variável x. Na linha 14 é feita uma chamada ao procedimento exemplo passando a variável x como argumento. Essa chamada é válida pois o tipo da variável x é inteiro e do parâmetro y também. É feito um salto da execução para dentro do procedimento exemplo. Nesse procedimento, é alocada memória para o parâmetro y. y funciona como se fosse uma variável local ao procedimento exemplo, com a exceção de que y é inicializada com o valor do argumento (no caso do exemplo o argumento é a variável x que possui o valor 10). Como y é um parâmetro de entrada (passado por valor), a única coisa que ocorre é que o argumento x empresta o seu valor para o parâmetro y. Não existe qualquer ligação entre essas duas variáveis, e modificações no valor de y não implicarão em modificações no valor de x. A execução do procedimento exemplo inicia com y valendo 10. Na linha 08 é escrita a mensagem O valor de y é 10, e na linha 09 é alterado o valor de y, o qual passa a ser 2. O procedimento exemplo termina e a execução retorna para o corpo principal do algoritmo. Na linha 15 é escrita a mensagem O valor de x é 10. E na linha 16 é feita mais uma chamada ao procedimento exemplo. Nessa chamada, o valor 5 é passado como argumento, e portanto o procedimento exemplo é executado novamente com o parâmetro y sendo inicializado com 5. Parâmetros de Entrada e Saída ou ParâmetrosPassados por Referência Os parâmetros de entrada e saída utilizam a passagem de parâmetro por referência. Nessa forma de passagem de parâmetro, o parâmetro é "ligado" com o argumento associado, de forma que qualquer modificação no valor do parâmetro acarreta em uma modificação no valor do argumento. Um parâmetro de entrada e saída é declarado da seguinte forma: Procedimento <nome procedimento>(var <nome parâmetro>: <tipo>; ...) Repare que a única diferença entre a declaração de um parâmetro de entrada e um de entrada e saída é que os parâmetros de entrada e saída possuem a palavra reservada var antes do identificador do parâmetro. Vamos ver como os parâmetros de entrada e saída funcionam através de um exemplo: 01 | Algoritmo exemplo_passagem_por_referencia; 02 | 03 | { 04 | x: inteiro; 05 | 06 | Procedimento exemplo(var y: inteiro) 07 | { 08 | escreva('O valor de y é ', y); 09 | y ← 2; 10 | } 11 | 12 | { 13 | x ← 10; 14 | exemplo(x); 15 | escreva('O valor de x é ', x); 16 | } Repare que o parâmetro y do procedimento exemplo é um parâmetro por referência (de entrada e saída) uma vez que sua declaração utiliza a palavra reservada var. No algoritmo acima a execução inicia na linha 12 e na linha 13 a variável x recebe o valor 10. Na linha 14 é feita uma chamada ao procedimento exemplo e x é passado como argumento. A chamada é válida pois ambos x e y são variáveis do tipo inteiro. Como y é um parâmetro de entrada e saída, isso faz com y e x fiquem ligadas de tal forma que qualquer modificação em y dentro do procedimento exemplo acarretará em uma modificação no valor de x. É feito, portanto, um salto de execução para dentro do procedimento exemplo. Na linha 08 é escrito o valor de y, no caso o valor é 10. Na linha 09 é feita uma atribuição do valor 2 à variável y. Isso faz com que y mude de valor juntamente com x. A partir deste momento, ambos y e x possuem o valor 2. O procedimento exemplo é terminado e a execução retorna para o corpo principal do algoritmo. Na linha 15 é escrito o valor da variável x que é atualmente 2. Vamos fazer um exercício: implementar um procedimento que calcule o valor de xn. Antes de começar, é importante lembrar que queremos evitar utilizar variáveis globais dentro do nosso procedimento. Isso fará com que o procedimento funcione em qualquer algoritmo, sem nenhum tipo de dependência entre o algoritmo e o procedimento. Se não podemos utilizar variáveis globais, como faremos para dizer ao procedimento quais são os valores de x e n? A primeira idéia pode ser ler esses valores dentro do procedimento. Isso não é uma boa solução pois estamos impondo uma limitação muito grande para o nosso procedimento: ele somente funcionará se os valores de x e n forem lidos do teclado. O nosso procedimento não mais poderá ser executado em qualquer algoritmo, mas somente nos algoritmos nos quais x e n devem ser lidos do teclado. Uma segunda solução é utilizar parâmetros de entrada para informar os valores de x e de n. Uma pergunta similar pode ser feita quanto ao valor de xn, o que deve ser feito com esse valor após ele ser calculado? Uma possibilidade é escrever esse valor na tela. Entretanto, essa seria uma outra limitação que estariamos impondo ao nosso procedimento. Na realidade, podemos "retornar" esse valor para o algoritmo que chamou o procedimento, com isso fica a cargo do algoritmo fazer o que for preciso com o valor de xn. Temos então duas informações de entrada: x e n, e uma informação de saída: o valor calculado de xn. Podemos utilizar dois parâmetros de entrada, um para entrar o valor de x e o outro para entrar o valor de n. E um parâmetro de entrada e saída, para retornar o valor calculado de xn. Vamos dar um nome ao nosso procedimento, digamos pot. A sua declaração no algoritmo ficará assim: Procedimento pot(x, n: inteiro; var resultado: inteiro) O parâmetro resultado será responsável por conter o valor calculado de xn, e portanto retornar esse valor. Uma chamada ao procedimento pot pode ser feita da seguinte forma: pot(3, 2, a); Nesse caso, quando é retornado da chamada ao procedimento pot, a variável a deverá conter o valor de 32, ou seja, 9. Vamos montar o procedimento pot e um algoritmo simples capaz de ler dois valores do teclado e chamar o procedimento pot. 01 | Algoritmo exemplo_1; 02 | 03 | { 04 | a, b, potencia: inteiro; 05 | 06 | Procedimento pot(x, n: inteiro; var resultado: inteiro) 07 | { 08 | i: inteiro; 09 | 10 | resultado ← 1; 11 | para i ← 1 até n faça 12 | { 13 | resultado ← resultado * x; 14 | } 15 | } 16 | 17 | { 18 | leia(a); 19 | leia(b); 20 | pot(a, b, potencia); 21 | escreva(a, ' elevado a ', b, ' = ', potencia); 22 | } Repare que na linha 20 é feita a chamada a pot. As variáveis a e b emprestam os seus valores para as variáveis x e n, respectivamente. Ainda, a variável potencia fica ligada ao parâmetro resultado. Na realidade, não existe nenhum motivo para que nós declaremos as variáveis globais com identificadores diferentes dos utilizados nos parâmetros locais. O mesmo algoritmo poderia ser reescrito da seguinte forma: 01 | Algoritmo exemplo_2; 02 | 03 | { 04 | x, n, resultado: inteiro; 05 | 06 | Procedimento pot(x, n: inteiro; var resultado: inteiro) 07 | { 08 | i: inteiro; 09 | 10 | resultado ← 1; 11 | para i ← 1 até n faça 12 | { 13 | resultado ← resultado * x; 14 | } 15 | } 16 | 17 | { 18 | leia(x); 19 | leia(n); 20 | pot(x, n, resultado); 21 | escreva(x, ' elevado a ', n, ' = ', resultado); 22 | } Funções Uma outra forma de criar sub-rotinas é através de funções. Funções são similares aos procedimentos com a principal diferença que as funções sempre retornam um valor através do retorno da função. As funções, assim como os procedimentos, podem ter variáveis locais, parâmetros passados por valor e por referência. Um exemplo bem conhecido de função é a função sqrt, a qual calcula a raiz quadrada de um valor passado como parâmetro. Por exemplo, para calcular o valor da raiz quadrada de 4, faz-se: x ← sqrt(4); Repare que a função é capaz de retornar um valor que pode ser armazenado em uma variável através de uma atribuição. Para declarar uma função é preciso fazer: Função <nome da função>(<parâmetros>): <tipo> Na qual <tipo> é o tipo do valor retornado pela função (por exemplo: inteiro, real, texto, caracter, etc.). Para que a função retorne um determinado valor é necessário atribuir ao nome da função o valor que se deseja retornar. Na realidade, o nome da função funciona como uma variável local. O tipo dessa variável é o tipo especificado para o valor de retorno da função. O valor contido nessa variável ao final da execução da função é retornado pela função. Vamos refazer o procedimento que calcula o valor de xn na forma de função. 01 | Algoritmo exemplo_3; 02 | 03 | { 04 | x, n, resultado: inteiro; 05 | 06 | Função pot(x, n: inteiro): inteiro 07 | { 08 | i, resultado: inteiro; 09 | 10 | resultado ← 1; 11 | para i ← 1 até n faça 12 | { 13 | resultado ← resultado * x; 14 | } 15 | pot ← result; 15 | } 16 | 17 | { 18 | leia(x); 19 | leia(n); 20 | resultado ← pot(x, n); 21 | escreva(x, ' elevado a ', n, ' = ', resultado); 22 | } Função sem valor de retorno = Procedimento.
Compartilhar