16
1 LÓGICA DE PROGRAMAÇÃO LINGUAGEM C MODULARIZAÇÃO DE ALGORITMOS 1 INTRODUÇÃO: Definição : Conjunto de comandos agrupados em um bloco que recebe um nome e através deste pode ser ativado. Objetivo: Reduzir a complexidade dos problemas através da modularização de algoritmos, ou seja, da decomposição de problemas em partes menores, simplificando as soluções. Vantagens Permitem o reaproveitamento de código já construído; Evitam que um trecho de código que seja repetido várias vezes dentro de um mesmo programa; Permitem a alteração de um trecho de código de uma forma mais rápida. Com o uso de uma função é preciso alterar apenas dentro da função que se deseja; Simplificam os blocos do programa para que não fiquem grandes demais e, por conseqüência, mais difíceis de entender; Facilitam a leitura do programa-fonte; Decompõem o programa em partes(blocos) que possam ser logicamente compreendidos de forma isolada. Na modularização é utilizada a técnica de Refinamentos Sucessivos, conhecida também como Top-Down, que parte da decomposição dos problemas, sucessivamente, até conseguir o nível de detalhamento desejado e, então, desenvolver um subalgoritmo ou módulo para cada subproblema. Subalgoritmos s ão trechos de algoritmos que efetuam um ou mais cálculos determinados que, não isoladamente, mas em conjunto, resolvem o problema proposto. É conveniente utilizá-los quando uma determinada tarefa é efetuada em diversos lugares no mesmo algoritmo. Podem ser funções que retornam algum valor ou procedimentos (sub-rotinas) que não retornam nada. Devem ser declarados no início do algoritmo e podem ser chamados em qualquer ponto após sua declaração. 2 PROCEDIMENTO Um procedimento (procedure), também chamado de sub-rotina, é um conjunto de instruções que realiza uma determinada tarefa. È identificado por um nome, por meio do qual é referenciado em qualquer parte do programa que o chamou. Quando um procedimento é chamado por um programa, ele é executado e, quando termina, o controle do processamento retorna automaticamente para a primeira linha de instrução após a linha que fez a chamada do procedimento. A sintaxe em português estruturado é: procedimento <identificador> (lista de parâmetros) var <declaração das variáveis locais do procedimento> inicio <código do procediemto> fim procedimento Na linguagem C não existem definições para sub-rotinas ou procedimentos. A funções em C são equivalentes a essas construções.

modularização

Embed Size (px)

Citation preview

Page 1: modularização

1

LÓGICA DE PROGRAMAÇÃO – LINGUAGEM C

MODULARIZAÇÃO DE ALGORITMOS

1 INTRODUÇÃO:

Definição : Conjunto de comandos agrupados em um bloco que recebe um nome e através deste pode ser ativado.

Objetivo: Reduzir a complexidade dos problemas através da modularização de algoritmos, ou seja, da decomposição

de problemas em partes menores, simplificando as soluções.

Vantagens

Permitem o reaproveitamento de código já construído;

Evitam que um trecho de código que seja repetido várias vezes dentro de um mesmo programa;

Permitem a alteração de um trecho de código de uma forma mais rápida. Com o uso de uma função é preciso alterar apenas dentro da função que se deseja;

Simplificam os blocos do programa para que não fiquem grandes demais e, por conseqüência, mais difíceis de entender;

Facilitam a leitura do programa-fonte;

Decompõem o programa em partes(blocos) que possam ser logicamente compreendidos de forma isolada.

Na modularização é utilizada a técnica de Refinamentos Sucessivos, conhecida também como Top-Down, que parte

da decomposição dos problemas, sucessivamente, até conseguir o nível de detalhamento desejado e, então,

desenvolver um subalgoritmo ou módulo para cada subproblema.

Subalgoritmos são trechos de algoritmos que efetuam um ou mais cálculos determinados que, não isoladamente,

mas em conjunto, resolvem o problema proposto. É conveniente utilizá-los quando uma determinada tarefa é

efetuada em diversos lugares no mesmo algoritmo. Podem ser funções que retornam algum valor ou procedimentos

(sub-rotinas) que não retornam nada. Devem ser declarados no início do algoritmo e podem ser chamados em

qualquer ponto após sua declaração.

2 PROCEDIMENTO

Um procedimento (procedure), também chamado de sub-rotina, é um conjunto de instruções que realiza uma

determinada tarefa. È identificado por um nome, por meio do qual é referenciado em qualquer parte do programa

que o chamou. Quando um procedimento é chamado por um programa, ele é executado e, quando termina, o

controle do processamento retorna automaticamente para a primeira linha de instrução após a linha que fez a

chamada do procedimento. A sintaxe em português estruturado é:

procedimento <identificador> (lista de parâmetros) var <declaração das variáveis locais do procedimento> inicio <código do procediemto> fim procedimento

Na linguagem C não existem definições para sub-rotinas ou procedimentos. A funções em C são equivalentes a essas

construções.

Page 2: modularização

2

3 FUNÇÕES

A linguagem C é baseada no conceito de blocos de construção chamados de funções. Um programa em C é uma

coleção de uma ou mais funções, onde cada função deve realizar uma tarefa bem específica.

Uma função é uma sub-rotina que contém uma ou mais declarações e realiza uma ou mais tarefas, podendo atuar

sobre dados e retornar um valor. Cada função tem seu próprio nome. Quando um nome é encontrado, a execução

do programa é desviada para a função e, quando retorna, a execução recomeça na próxima linha da função que tiver

feito a chamada.

Exemplo:

Figura 1 – Esquema de execução programas com diversas funções

A chamada de uma função é feita através da citação do seu nome no programa seguido opcionalmente de seus

argumentos iniciais entre parênteses, separados por vírgulas. Toda função inicia com abre chaves “{“ e é encerrada

com fecha chaves “}”e, então, a execução retorna para a instrução seguinte à chamada da função.

Exemplo

Considerando um programa que necessita imprimir a mensagem "Pressione a tecla ENTER" várias vezes durante sua

execução e, a cada vez, tenha que esperar o usuário teclar ENTER. Caso o usuário tecle algo diferente o programa

deve emitir um BEEP.

Neste caso, pode ser feito um laço de WHILE sempre que isto seja necessário e, uma alternativa é criar uma função.

Com o uso de funções, este processo de repetição fica simplificado.

Exemplo #include <conio.h> #include <dos.h> #include <stdio.h> void EsperaEnter() // Definição da função "EsperaEnter" { int tecla; // variável local da função “EsperaEnter” printf("Pressione ENTER\n");

Programa

funcao_1 ( );

instrucao;

instrucao;

instrucao;

funcao_4 ( );

funcao_2 ( );

instrucao_1;

main( ) {

instrucao;

instrucao; }

{ funcao_3 ( ); instrucao; instrucao; return; }

funcao_2

{ instrucao; : instrucao; return; }

funcao_3

{ instrucao; : instrucao; return;

}

funcao_4

{ instrucao; : instrucao; return;

}

funcao_1

Page 3: modularização

3

do // início do laço { tecla = getch(); //função predefinida if (tecla !=13) // Se não for ENTER { sound(700); // Ativa a emissão de um BEEP delay(10); // Mantém a emissão do som por 10 ms nosound(); // Para de emitir o som } } while(tecla != 13); // 13 é o código ASCII do ENTER } void main() { EsperaEnter(); // Chamada da função EsperaEnter(); EsperaEnter(); }

3.1 DECLARANDO E DEFININDO FUNÇÕES

A definição da função informa ao compilador o nome da função, o tipo de retorno, os parâmetros e como ela

trabalha.

A única exigência é que a função seja definida antes de ser utilizada, que pode causar problemas, uma vez que a

maioria dos usuários prefere que o programa principal main( ) esteja no início do programa. A solução é declarar a

função separadamente no início do programa.

Portanto, inicialmente a função deve ser declarada e depois definida no local desejado.

3.1.1 DEFINIÇÃO DE FUNÇÕES

Conforme mencionado anteriormente, a definição da função informa ao compilador o nome da função, o tipo de

retorno, os parâmetros e como ela trabalha. Sua forma geral é:

<tipo de retorno> <identificador> (lista de parâmetros) { <código da função> }

Identificador: nome da função sempre seguido pro parêntesis

Tipo de retorno: (opcional) determina o tipo de valor que a função retorna através da declaração return. Pode ser qualquer tio válido. Se um tipo for especificado a função retorna um resultado inteiro, que é o tipo padrão do C.

Lista de parâmetros: lista de todos s tipos e nomes das variáveis que recebem valores dos argumentos quando a função for chamada.

Na definição da função está implícita sua declaração

Exemplo: #include <stdio.h> int Mensagem() { printf(“Olá!” ); return (0)

Page 4: modularização

4

} int main() ( printf(“Comovai?!”); }

Este programa define uma função Mensagem( ) que coloca uma string na tela e retorna 0.Esta função é chamada a partir da função main( ), que é a primeira função a ser executada em um programa C. Este programa imprime na tela do monitor a frase: Olá! Como vai?!

3.1.2 DECLARAÇÃO DE FUNÇÕES

Alguns programadores preferem que o início do programa seja a primeira parte de seu programa. Para isto a

linguagem C permite que se declare uma função, antes de defini-la. Esta declaração é feita através do protótipo da

função. O protótipo da função é o trecho de código que especifica o o tipo de retorno, o nome e os parâmetros da

função. Por ser uma instrução, deve ser encerrada com ponto e vírgula.

Em geral, pode-se dar qualquer nome a uma função, com exceção de main(). Os tipos de retorno podem ser os tipos

básicos, com modificadores de tipos da linguagem C, ou ainda, tipos definidos pelo usuário.

Sintaxe do protótipo de uma função:

<tipo de retorno> nome da função ( <lista de parâmetros>);

Exemplo: u n s i g n e d l o n g i n t C a l c u l a A r e a ( i n t p a r a m _ 1 , i n t p a r a m _ 2 ) ;

Tipo de retorno Nome da função Lista de parâmetros

No exemplo a seguir a função SOMA( ) é prototipada antes de ser usada e assim pôde ser chamada antes de ser definida.

Exemplo: #include <stdio.h> void SOMA(float a, int b); // Protótipo da função SOMA( ) void main() { SOMA(16.7,15); // Chamada da função SOMA antes de sua definição, mas após sua prototipação } void SOMA(float a, int b) // Definição da função SOMA( ) { float result; // a declaração de variáveis é igual ao que se faz na função main( ) result = a+b; printf("A soma de %d com %6.3f é %6.3f\n”, a,b,result); }

Nomes dos parâmetros Tipos de parâmetros

Page 5: modularização

5

3.2 PARÂMETROS E ARGUMENTOS DAS FUNÇÕES

O uso de parâmetros torna mais amplo o uso de uma função,pois possibilitam que seja definida sobre quais dados a

função deve operar. A função sound(freq), por exemplo, recebe como parâmetro a freqüência do som a ser gerado,

permitindo que se defina seu comportamento a partir deste valor.

Para definir os parâmetros de uma função o programador deve explicitá-los como se estive declarando uma variável,

entre os parênteses do cabeçalho da função. Caso precise declarar mais de um parâmetro, basta separá-los por

vírgulas. Um parâmetro, portanto, é uma declaração de que tipo de valor será passado para função, o verdadeiro

valor passado pela função que fez a chamada é denominado argumento. No exemplo abaixo, a função SOMA( )

possui dois parâmetros, sendo o primeiro um float e o segundo um int.

Exemplo: void SOMA(float a, int b) // basta separar cada parâmetro por vírgulas { float result; // a declaração de variáveis é igual ao que se faz na função main( ) result = a+b; printf("A soma de %d com %6.3f é %6.3f\n”, a,b,result); }

Os parâmetros da função na sua declaração são chamados parâmetros formais. Através dos argumentos na

chamada da função são passados valores para os parâmetros da função, de acordo com a posição de cada

parâmetro. Ou seja, o primeiro argumento (da chamada) define o valor o primeiro parâmetro (na definição da

função), o segundo argumento define o valor do segundo parâmetro e assim por diante. Os nomes dos argumentos

na chamada não têm relação com os nomes dos parâmetros na definição da função.

Exemplo: Cálculo do Produto

Neste exemplo, é a função produto( ) é definida com três parâmetros inteiros A, B e C, e retorna um inteiro. Na

função main( ) é feita uma chamada para a função produto ( ), sendo passado os argumentos X=12, Y=3 e 7. Esses

valores são copiados nas variáveis A, B e C, respectivamente. Uma vez efetuado o produto, o valor é retornado para

a variável SAIDA. O argumento de função é simplesmente um valor que é passado para a função no momento em

que ela é chamada (12, 3 e 7).

Page 6: modularização

6

Resumindo, argumento refere-se ao valor que é usado para chamar uma função. A variável que recebe o valor dos

argumentos usados na chamada da função é um parâmetro da função. As funções que levam argumentos são

chamadas de funções parametrizadas, como por exemplo as funções printf( ) e scanf( ).

O tipo do argumento usado para chamar uma função deve ser igual ao tipo do parâmetro que está recebendo o

dado argumento. Por exemplo, não se deve chamar a função quadrado( ) a seguir, com um argumento de ponto

flutuante.

Exemplo:

1. #include <stdio.h> // Definição da função quadrado()

2. int quadrado (int B) //parâmetro B declarado dentro dos parênteses 3. { 4. printf (“%d elevado ao quadrado é %d\n”, B, B * B); 5. }

// Programa Principal 6. void main() 7. { 8. int num; // declaração da variável local da função main() 9. num = 30; 10. quadrado(num); //chama a função quadrado() com o argumento num 11. }

Na linha 2 do algoritmo acima, é definida a função quadrado( ) que retornar um dado do tipo inteiro. A variável B é o

parâmetro desta função e está declarada dentro dos parênteses. No programa principal, função main( ), a variável

num é declarada na linha 10, inicializada com o valor 30 na linha 11 e, este valor, é passado como argumento da

função quadrado( ) quando esta é chamada na linha 12 (o valor 30 é copiado na variável B). A seguir, a função

quadrado( ) imprime o resultado da expressão B*B calculada na linha 5. Assim, a mensagem “30 elevado ao

quadrado é 900” é apresentada na tela do monitor. Depois da impressão, o programa retorna para a função main() e

executa a instrução seguinte à chamada da função, ou seja, a instrução da linha 13 que encerra o programa.

3.3 DECLARAÇÃO RETURN

Existem duas maneiras de uma função terminar a execução e retornar ao seu chamado.

A primeira forma é quando a última instrução na função é executada e o finalizador da função } é encontrado. Por

exemplo, a função a seguir imprime o inverso de um número na tela e é encerrada com o fecha chaves }.

Exemplo: void pr_inverso(float num) { float inverso; Inverso = 1/num printf (“o inverso de %6.3f é %6.3f\n”, num, inverso); }

Uma vez que a mensagem é exibida, não há nada para a função fazer a seguir, então ela retorna ao lugar de onde foi

chamada.

A segunda maneira é quando a declaração return é encontrada. Esta declaração tem dois importantes usos:

provoca uma saída imediata da função corrente, isto é, faz com que a execução do programa retorne para o código chamador assim que a declaração return é encontrada.

pode ser usada para retornar um valor.

Page 7: modularização

7

A declaração return pode ser usada sem qualquer valor associado a ela. Por exemplo, a função a seguir imprime o

resultado de um número elevado a uma potência positiva. Se o expoente é negativo, a declaração return faz com

que a função termine antes que a chave final seja encontrada, mas nenhum valor é retornado.

Exemplo: 1 #include <stdio.h> 2 void potencia(int base, int exp) 3 { 4 int i; 5 if (exp<0) return; /* nao pode calcular com expoente negativo */ 6 i = 1; 7 for ( ; exp; exp--) i = base * i; 8 printf ("A resposta é: %d\n ", i); 9 } 10 main() 11 { 12 int b,e; 13 printf("Digite a base e expoente x,y : "); 14 scanf("%d,%d",&b,&e); 15 potencia(b,e); 16 }

3.3.1 FUNÇÕES QUE RETORNAM VALORES

Em C, uma função pode retornar um valor para uma rotina chamadora usando a palavra reservada return. Para uma

função com retorno, o tipo de retorno da função deve ser declarado. Muitas das funções de biblioteca pré-definidas

retornam valores.

Por exemplo, o programa anterior que imprime a potenciação de número, pode ser reescrito como o programa a

seguir, onde o valor de retorno é associado a uma variável, colocando-se a função do lado direito de uma declaração

de atribuição (linha 15)

Exemplo:

1 #include <stdio.h> 2 int potencia (int base, int exp) 3 { 4 int i; 5 if (exp<0) return; /* nao pode calcular com expoente negativo */ 6 i = 1; 7 for ( ; exp; exp--) i = base * i; 8 return i; 9 } 10 main() 11 { 12 int b,e, resposta; 13 printf("Digite a base e expoente x,y : "); 14 scanf("%d,%d",&b,&e); 15 resposta = potencia(b,e); 16 printf ("A resposta é: %d\n ", resposta); 17 }

Na linha 8 a função potencia( ) retorna o valor de “ i “ usando a declaração return. Esse valor é atribuído à variável

resposta. Isto é, o valor retornado pela declaração return torna-se o valor da função potencia ( ) na chamada de

rotina na linha 15.

A variável que recebe o valor de retorno de uma função tem que ser do mesmo tipo que o valor retornado pela

função. O tipo retornado pela rotina potencia( ) é int

Page 8: modularização

8

O valor a ser retornado deve ser colocado após a declaração return. A função max() retorna o maior valor entre dois

argumentos:

Exemplo:

max (int a, int b) { int temp; if (a > b) temp = a; else temp = b; return temp; }

O tipo de retorno padrão de uma função se nenhum outro é explicitamente especificado na definição da função é o

inteiro. É possível uma função conter duas ou mais declarações return. Por exemplo, a função max( ) é melhor

escrita como mostrado aqui:

Exemplo:

max (int a, int b) { if (a > b) return a; else return b; }

Todas as funções, exceto aquelas declaradas para serem do tipo void, retornam um valor. Isso significa que uma

função pode ser usada como um operando em qualquer expressão válida em C e, portanto, cada uma das seguintes

expressões é válida em C:

x = abs(y);

if (max(x, y) > 100) printf(“excelente”);

for (ch = getchar(); digito(ch); ) ...;

Porém, uma função não pode ser alvo de uma atribuição e, a declaração abaixo é incorreta.

soma(x, y) = 50 ; /* declaração incorreta */

Não é necessário usar o valor de retorno de uma função. Se não há atribuição especificada, o valor retornado é

descartado. Considere o seguinte programa que usa a função mul( ).

Exemplo: 1 #include <stdio.h> 2 int mul (int a, int b) 3 { 4 return (a * b) 5 } 6 void main() { 7 int x, y, z; 8 x = 10; 9 y = 20; 10 z = mul(x, y); 11 printf (“%d”, mul (x, y)); 12 mul (x, y); 13 }

Na linha 10, o valor de retorno de mul( ) é associado a variável z. Na linha 11, o valor de retorno é usado pela função

printf( ) e, na linha 12, o valor de retorno é perdido porque não é associado a outra variável nem usado em uma

expressão.

Page 9: modularização

9

3.3.2 FUNÇÕES RETORNANDO VALORES NÃO-INTEIROS

O programa abaixo calcula a área de um círculo, dado o seu raio. A função retorna um valor do tipo float, dado o

argumento do tipo float. Neste caso, é declarado o protótipo da função área( ),permitindo que esta seja definida

após a função mai( ).

Exemplo:

1. #include <stdio.h> 2. float area(float raio); //protótipo da função area( ) 3. void main() 4. { 5. float r,res; 6. printf(“Informe o raio: ”); 7. scanf(“%f”, &r); 8. res = area(r); 9. printf(“A área é: %f\n”, res); 10. } 11. float area(float raio) // definição da função area( ) 12. { 13. return 3.1416 * raio*raio; 14. }

3.3.3 FUNÇÕES DO TIPO VOID

Funções que não retornam valor podem sem declaradas como void, que impede o seu uso em qualquer expressão e

ajuda a evitar um uso incorreto acidental. Funções do tipo void são puramente procedimentos.

Por exemplo, as funções func1( ) e func2( ) imprimem em cem linhas, uma seqüência de nove caracteres. Uma vez

que não retornam valores, as funções são declaradas como void.

Exemplo:

1. #include <stdio.h> 2. void func1(void), func2(void); /* protótipos das funções func1( ) e func2( ) 3. void main() 4. { 5. func1(); 6. getch(); 7. } 8. void func1(void){ 9. int count; 10. for (count = 1; count < 100; count++) func2(); 11. } 12. void func2(void){ 13. int count; 14. for (count = 1; count < 10; count++) printf ("*"); 15. printf("\n"); 16. }

Page 10: modularização

10

3.4 ESCOPO DE VARIÁVEIS

Escopo: conjunto de regras que determinam o uso e a validade das variáveis nas diversas partes do problema.

Na linguagem C, cada função é um bloco de código que pode ser acessado por qualquer declaração. Uma função

pode chamar outras funções, mas o código que compreende o corpo de uma função (bloco entre {}) está escondido

do resto do programa, ele não pode afetar nem ser afetado por outras partes do programa, a não ser que o código

use variáveis globais.

Tipos básicos:

Variáveis Locais

Parâmetros -

Variáveis Globais

3.4.1 VARIÁVEIS LOCAIS

São variáveis declaradas dentro de uma função ou de um bloco de código e seus escopos estão restritos ao bloco

onde foram declaradas. Toda variável declarada entre um bloco { } só podem ser referenciadas dentro deste bloco e

existem apenas durante a execução deste bloco.

As variáveis locais são criadas na entrada e destruídas na saída de um código.

Exemplo1: void func1() { int X; X=10; } void func1() { int X; X=-15; }

A variável X foi declarada duas vezes, uma em cada função e O X em func1() não tem relação com o X em func2(),

pois cada X conhecido somente dentro do código que foi declarado.

Exemplo2: void func(void) { int X; void main() { int X=10; printf(“O valor de X na função main ( ) e %d \n”, X); // imprime :“O valor de X na main ( ) e 10 func(); printf(“O valor de X na função main ( ) e %d \n”, X); // imprime :“O valor de X na main ( ) e 10 } void func() { int X; X=-15; printf(“O valor de X na função func( ) e %d \n”, X); // imprime :“O valor de X na func ( ) e -15 } Exemplo3: void linha( ); //protótipo da função linha main() { int tamanho; // variável local da função main() printf("Digite o tamanho: "); scanf("%d",&tamanho); linha(tamanho); // chamada da função linha() } void linha(x) // definição da função linha() int x; // variável

Page 11: modularização

11

{ int i; // variável local da função linha() for(i=0;i<=x;i++) putchar(95); // imprime o caracter “_” X vezes }

A variável i na função linha não é reconhecida pela função main()

Não é obrigatório declarar as variáveis no início do código de uma função. Elas podem ser declaradas em qualquer

bloco de código.

Exemplo4: void f1(void ) { //definição da função f1 char ch; // variável local da função f1() printf(“Continua? (s/n)”); ch=getche(); if(ch=’s’) { char str[80]; // variável local neste bloco printf(“Informe o nome”); gets(str); processa(str); // função que realizará algum processo }

A variável local str será criada na entrada do bloco de código if e destruída na saída, sendo conhecida somente

dentro deste bloco e não pode ser referenciada em outro lugar – mesmo em outras partes da função que a contém.

Como as variáveis locais são criadas e destruídas a cada entrada e saída de um bloco, os seus conteúdos são

perdidos quando o bloco é deixado. Isso significa que variáveis locais não podem reter os seus valores entre

chamadas.

3.4.2 PARÂMETROS FORMAIS

Se uma função usa argumentos, ela deve declarar as variáveis que receberão valores daqueles argumentos. Estas variáveis são chamadas de parâmetros formais das funções e comportam-se como quaisquer outras variáveis locais dentro da função

3.4.3 VARIÁVEIS GLOBAIS

São conhecidas por todo programa e podem ser usadas em qualquer parte do código. Permanecem com seu valor

durante toda execução do programa. São armazenadas em uma região fixa da memória própria para esse fim.

Normalmente, são criadas declarando-as fora de qualquer função e podem ser acessadas por qualquer expressão,

não importando em qual função esteja. Porém, as variáveis globais podem ser declaradas em qualquer lugar do

código, contando que sua primeira utilização ocorra após ter sido declarada.

Exemplo: void func1(),func2(); // protótipo das funções func1() e func2() int cont; // declaração da variável global cont main() { cont=100; // atribuição do valor 100 para a variável cont func1(); // chamada da função func1() } void func1() { // definição da função func1() int temp; //declaração da variável local temp da função func1() temp=cont; func2(); // chamada da função func2() printf("cont é = %d",cont); // imprime: cont é = 100 } void func2() { // definição da função func2() int cont; // declaração da variável local cont da função func2() for(cont=1;cont<10;cont++) printf("*"); // imprime: ********* }

Page 12: modularização

12

Neste programa, a variável global cont foi declarada antes da função main( ) e, embora não tenha sido declarada

dentro da main() nem da func1( ), estas duas funções podem usá-la. Entretanto, func2( ) tem declarado uma variável

local chamada cont. Quando se referencia a variável cont, func2( ) está se referenciando somente a sua variável

local, não a global.

Se uma variável global e uma local tiverem o mesmo nome, todas as referências àquele nome de variável dentro da

função onde a variável local é declarada se referirão somente a ela e não terão efeito na variável global

Variáveis globais são muito úteis quando o mesmo dados é usado em muitas funções no seu programa. Entretanto,

deve-se evitar a utilização de variáveis globais desnecessárias por três razões:

ocupam a memória durante todo o tempo em que o programa está executando, não somente quando necessário;

torna uma função menos geral, porque ela vai depender de algo que deve ser definido fora dela;

a utilização de um grande número de variáveis globais pode levar o projeto a erros,pois pode se tornar difícil gerenciá-las

Exemplo1: GERAL ESPECÍFICO mul (int x, int y) int x, y; { { return (x * y); return (x * y); } }

As duas funções retornam o produto das duas variáveis x e y. A versão geral, ou parametrizada, pode ser usada para

retornar o produto de dois números quaisquer. A versão específica pode ser usada somente para encontrar somente

o produto das variáveis globais x e y.

3.5 PASSAGEM DE PARÂMETROS ENTRE FUNÇÕES

Os argumentos podem ser passados para as funções de duas maneiras. A primeira é chamada de passagem por

valor que copia o valor de um argumento para um parâmetro formal de uma função. A segunda é chamada por

referência (ou passagem de parâmetro por referência), onde o endereço de um argumento é copiado no seu

parâmetro.

3.5.1 PASSAGEM POR VALOR

A passagem por valor (ou passagem de parâmetro por cópia) copia o valor do argumento na chamada da função

para um parâmetro declarado na função. Portanto, é uma cópia do valor do argumento que é passado para uma

dada função. O que ocorre dentro da função não terá efeito na variável usada na chamada. O valor de um

argumento é copiado para o parâmetro formal da função e as alterações no processamento não alteram as variáveis

usadas para chamar a função.

Exemplo1: int quad(int x); // protótipo da função quad( ) main() { int t=10; // declaração e inicialização da variável local t printf("%d %d",quad(t),t); // chama a função quad( ) e imprime 100 10 } int quad(int x) // definição da função quad( ) e declaração do parâmetro x { x=x*x; return(x) }

Page 13: modularização

13

O valor do argumento passado para a função quad( ), 10, é copiado no parâmetro x. Quando a atribuição x = x * x é

executada, modifica somente a variável local x. A variável t, usada para chamar quad( ), ainda terá o valor 10. Por

isso o resultado será 100 10.

3.5.2 PONTEIROS

Para alterar as variáveis que são passadas para uma função, seus parâmetros formais devem ser declarados como

ponteiros.

Ponteiro é uma variável que contém um endereço de memória, ou seja, é uma variável que guarda um endereço de

memória de outro objeto.

Endereço de memória é cada uma das posições de memória, seqüencialmente numeradas, que pode ser dividida a

memória do computador. Qualquer variável, de qualquer tipo, é posicionada em uma única posição ( 1 byte por

posição).

São usados para o gerenciamento de dados na memória disponível, para acessar dados e funções membros de classe

e para a passagem de variáveis por referência para funções

Uma variável que vai armazenar um ponteiro deve ser declarada como tal. Uma declaração de ponteiro consiste em

um tipo base, um * e o nome da variável. A forma geral para declaração de uma variável ponteiro é:

tipo *<nome da variável>;

onde o tipo pode ser qualquer tipo válido da linguagem C e o nome da variável é o nome da variável ponteiro. O tipo

base do ponteiro define qual tipo de variáveis o ponteiro pode apontar.

As declarações abaixo criam um ponteiro caractere e dois ponteiros inteiros.

char *p;

int *temp, *início;

Existem dois operadores especiais de ponteiro: o & e o *.

O & é um operador unário que retorna o endereço de memória do seu operando. Por exemplo,

end_cont = &cont;

coloca em end_cont o endereço de memória da variável cont. Esse endereço é a localização interna da variável no

computador. Ele não faz nada com o valor de cont.

Para entender melhor essa atribuição, assuma que a variável cont esteja localizada no endereço 2000. Então, após a

atribuição, end_cont terá o valor de 2000.

O operador * é o complemento do operador &. Ele é um operador unário que retorna o valor da variável localizada

no endereço que o segue. Por exemplo, se end_cont contém o endereço de memória da variável cont, então

val = *end_cont;

colocará o valor de cont em val. Supondo que a variável cont originalmente tem o valor 100, então val terá o valor

de 100, porque este é o valor armazenado no endereço 2000, que é o endereço de memória que foi atribuído a

end_cont. Neste caso, então, a declaração pode ser lida como “val recebe o valor que está no endereço end_cont”.

Exemplo: /* imprime 100 na tela */ 1. #include <stdio.h> 2. void main() { 3. int *end_cont, cont, val;

Page 14: modularização

14

4. cont = 100; 5. end_cont= &cont; //pega o endereço de cont 6. val = *end_cont; //pega o valor armazenado no endereço end_cont 7. printf(“%d”, val); //exibe 100 8. }

Infelizmente, o sinal de multiplicação e o sinal “valor no endereço” são os mesmos, porém, esses operadores não se

relacionam um com o outro. Tanto & como * têm precedência maior que todos os outros operadores aritméticos,

exceto o menos unário, com o qual eles se igualam.

3.5.3 CRIANDO UMA CHAMADA POR REFERÊNCIA

Como o C só faz chamadas por valor, é possível usar os parâmetros formais à vontade dentro da função, sem a

preocupação de estar alterando os valores dos argumentos que foram passados para a função. Porém, pode-se

querer mudar os valores fora da função. Ainda que a convenção de passagem de parâmetros da linguagem C seja a

chamada por valor, é possível criar uma chamada por referência passando-se um ponteiro para o argumento. Esta

maneira é chamada por referência (passagem de parâmetro por referência), onde o endereço de um argumento é

copiado no seu parâmetro.

Este tipo de passagem de parâmetros para uma função ocorre quando alterações nos parâmetros formais, dentro da

função, alteram os valores dos parâmetros que foram passados para a função; neste tipo de chamada, não se passa

para a função os valores das variáveis, mas sim suas referências (a função usa as referências para alterar os valores

das variáveis fora da função). Para usar a função é necessário colocar um & na frente das variáveis que estiverem

sendo passadas para a função.

Exemplo: 1. #include <stdio.h> 2. void troca (int *a,int *b); //declaração dos ponteiros a e b 3. void main (void) 4. { 5. int num1,num2; // declaração das variáveis locais num1 e num2 da main( ) 6. num1=100; 7. num2=200; 8. troca (&num1,&num2); /* chamada da função troca passando os endereços das variáveis

num1 e num2 */ 9. printf ("\n\nEles agora valem %d %d\n",num1,num2); 10. } 11. void troca (int *a,int *b) // definição da função troca( ) 12. { 13. int temp; // declaração da variável local temp da função troca( ) 14. temp=*a; // 15. *a=*b; 16. *b=temp; 17. }

Na linha 2 são declarados o protótipo da função troca( ) e duas variáveis do tipo inteiro a e b. Na linha 8, a função

troca é chamada e os endereços das variáveis num1 e num2 são passados (copiados) para os ponteiros a e b, ou

seja, a variável a recebe o endereço da variável num1 e a variável b o endereço de num2.

Na linha 14, o valor da variável num1, armazenada no endereço contido no ponteiro a (&num1) é atribuído à

variável temp, na linha 15, o valor da variável apontada pelo ponteiro b (num2) é atribuído à variável apontada pelo

ponteiro a e, finalmente, o valor da variável apontada pelo ponteiro b recebe o valor de temp

Através do operador * o conteúdo apontado pelos ponteiros é acessado e modificado e, estes conteúdos nada mais

são que os valores armazenados em num1 e num2, que, portanto, estão sendo modificados!

Page 15: modularização

15

Na função scanf() as variáveis chamadas também são precedidas pelo operador de endereços &. Esta função usa

chamada por referência porque ela precisa alterar as variáveis que são passadas para ela! Ela lê uma variável e,

portanto, precisa alterar seu valor. Assim, o endereço da variável a ser modificada é passado para a função com o

operador & sendo colocado na frente dos argumentos da função scanf( ), que recebem valores.

3.6 FUNÇÕES RECURSIVAS

Uma função é recursiva se um comando no corpo da função chama ela mesma. Um algoritmo que para resolver um

problema divide-o em sub-rotinas mais simples, cujas soluções requerem a aplicação dele mesmo, é chamado

recursivo, seja de forma direta ou indireta.

A forma direta ocorre quando uma rotina recursiva R, que é composta por um conjunto de comandos C (que não

chamam a R) mais uma chamada (recursiva) à rotina R. Na forma indireta, as rotinas são conectadas através de uma

cadeia de chamadas recursivas que acaba retornando a primeira que foi chamada.

Para que esteja bem definida, uma função recursiva deve possuir as seguintes propriedades:

(1) Deve haver certos argumentos, chamando valores básicos, para os quais a função não se refere a ela mesma. (2) Sempre que a função se refere a ela mesma o argumento deve estar relacionado a um valor básico e/ou a um valor anterior.

Os algoritmos recursivos têm em geral a forma seguinte:

caso de base (base de recursão), onde o problema é resolvido diretamente (sem chamada recursiva)

caso geral, onde o problema é resolvido com uma chamada recursiva

caso geral onde o tamanho do problema é menor a cada chamada

Esquematicamente, os algoritmos recursivos têm a seguinte forma:

se "condição para o caso de base" então resolução direta para o caso de base

senão uma ou mais chamadas recursivas

Exemplo1: - Para definir um número natural por meio de recursividade basta utilizar os dígitos 0, 1, 2, 3, 4, 5, 6, 7, 8,

9 mais ou menos outro número inteiro. Por exemplo, o número 15 é o número 7 mais o número 8; 21 é 9 mais 12 e

12 é 9 mais 3.

Exemplo2 - exemplo clássico de recursividade: cálculo do fatorial de um número, onde o fatorial de um número N é

o produto de todos os números inteiros entre 1 e N, ou seja:

F(n) = 1 se n = 0 ou n = 1;

F(n) = n.F(n-1), se n>1.

onde n é um numero inteiro positivo. Uma propriedade (facilmente verificável) dos fatoriais é que:

n! = n . (n-1)!

Esta propriedade é chamada de propriedade recursiva: o fatorial de um número pode ser calculado através do

fatorial de seu antecessor. Logo, pode-se, por exemplo, escrever uma rotina recursiva para o cálculo do fatorial de 4

F(0) = 1 F(0) = 1 caso base F(1) = 1*1 F(1) = 1*F(1-1) F(2) = 2*1 F(2) = 2*F(2-1) F(3) = 3*2 F(3) = 3*F(3-1) F(4) = 4*6 F(4) = 4*F(4-1) Assim, F(4) = 4*3*2*1 = 24 Se fosse nº 6, F(6) = 6*5*4*3*2*1=720

Para que o algoritmo termine, as chamadas recursivas devem convergir em direção ao caso de base, senão o

algoritmo não terminará jamais. Convergir significa ter uma parte menor do problema para ser resolvido. No

exemplo acima, as chamadas recursivas ( F(n)=n*F(n-1) convergem em direção ao caso base (F(0) = 1)

Page 16: modularização

16

Exemplo3 : função fatorial_recursivo() e sua equivalente iterativa:

Cálculo iterativo do fatorial de um número

1. #include <stdio.h> 2. unsigned long int fatorial (int n) { // definição da função fatorial( ) 3. unsigned long int t, resposta; // declaração da variável local resposta 4. resposta = 1; // inicialização da variável resposta 5. for (t = 1; t < n; t++) resposta = resposta * t; // cálculo do fatorial 6. return(resposta); 7. } // encerra função fatorial( ) 8. void main(){ // definição da função main( ) 9. unsigned long f; // declaração da variável local f 10. int n; // declaração da variável local n 11. printf(“Digite um número: ”); 12. scanf(“%d”,&n); 13. f = fatorial(n); // chamada da função fatorial( ) 14. printf(“O fatorial de %d é %ld\n”, n, f); 15. }

Cálculo recursivo do fatorial de um número

1. #include <stdio.h> 2. unsigned long int fatorial_recursivo (int n){ //definição da função fatorial_recursivo( ) 3. unsigned long int resposta; // declaração da variável local resposta 4. if ((n == 1) || (n == 0)) return(1); 5. resposta = n * fatorial_recursivo(n – 1); // chamada da função fatorial_recursivo( ) 6. return (resposta); 7. 8. void main(){ // definição da função main( ) 9. unsigned long f; // declaração da variável local f 10. int n; // declaração da variável local n 11. printf(“Digite um número: ”); 12. scanf(“%d”,&n); 13. f = fatorial_recursivo(n); // chamada da função fatorial_recursivo( ) 14. printf(“O fatorial de %d é %ld\n”, n, f); 15. } // encerra função main( )

Quando uma função chama a si própria, as novas variáveis locais e os argumentos são alocado,, e o código da função

é executado com esses novos valores a partir do início. Uma chamada recursiva não faz uma nova cópia da função.

Somente os argumentos e as variáveis são novos. Quando cada chamada recursiva retorna, as antigas variáveis locais

e os parâmetros são removidos e a execução recomeça no ponto de chamada da função dentro da função.

Vantagens da Recursão

Simplifica a solução de alguns problemas;

Geralmente, um código com recursão é mais conciso;

Caso não seja usada, em alguns problemas, é necessário manter o controle das variáveis manualmente.

Desvantagens da Recursão

Funções recursivas são mais lentas que funções iterativas, pois muitas chamadas consecutivas a funções são feitas;

Erros de implementação podem levar a estouro de pilha. Isto é, caso não seja indicada uma condição de parada, ou se esta condição nunca for satisfeita, entre outros.