Programação Orientada à Procedimentos II Programação Orientada à Procedimentos I Processamento Paralelo Lógica de Programação Introdução à Computação Informática Básica

 

 

Informática Básica
Introdução à Computação
Lógica de Programação
Programação Orientada à Procedimentos I
Programação Orientada à Procedimentos II

Processamento Paralelo

Aula 22

Aula 23

Aula 24





Funções


Um exemplo com Vetor


Exercício


Funções

Compreendendo as Variáveis Locais

C lhe permite declarar variáveis dentro de suas funções. Essas variáveis são chamadas variáveis locais, pois seus nomes e valores somente têm significado dentro da função que contém a declaração da variável. O programa a seguir ilustra o conceito de uma variável local. A função valores_locais declara três variáveis, a, b e c, e atribui às variáveis os valores 1, 2 e 3, respectivamente. A função main tenta imprimir o valor de cada variável. No entanto, como os nomes dos valores são locais à função valores_locais, o compilador gera erros dizendo que os símbolos a, b e cestão indefinidos.

		
#include <stdio.h>
void valores_locais(void)
{
   int a=1, b=2, c=3;
   printf ("a contém %d  b contém %d  c contém %d\n", a, b, c);
	}

void main(void)
{
    printf ("a contém %d  b contém %d  c contém %d\n", a, b, c);
    valores_locais();
}

Como as funções usam a pilha

O propósito principal da pilha é oferecer suporte para as chamadas das funções quando seu programa chama uma função, C coloca o endereço da instrução que segue a chamada da função(chamada endereço de retorno) na pilha. Em seguida, C coloca os parâmetros da função, da direita para a esquerda, na pilha. Finalmente, se a função declara variáveis locais, C aloca espaço na pilha, que a função pode, então, usar para guardar o valor da variável. A figura abaixo mostra como C usa a pilha para uma única chamada de função.

Quando a função termina, C descarta o espaço que continha as variáveis locais e os parâmetros. Em seguida, C usa o valor de retorno para determinar a ilustração que o programa executa em seguida. C remove o valor de retorno da pilha e coloca o endereço no registrador IP (ponteiro da instrução).

Compreendendo a sobrecarga da função

Como você aprendeu, quando seu programa usa uma função armazena o endereço de retorno, os parâmetros e as variáveis locais na pilha. Quando a função termina, C descarta o espaço na pilha que continha as variáveis locais e parâmetros, e, depois, usa o valor de retorno para retornar a execução do programa para a posição correta. Embora o uso da pilha de C seja poderoso porque permite que o programa chame e passe informações para as funções, C também consome tempo de processamento. Os programadores chamam a quantidade de tempo que o computador requer para colocar e retirar informações da pilha de sobrecarga da função. Para compreender melhor o impacto da sobrecarga da função no desempenho do seu programa, considere o programa a seguir.

Na maioria dos sistemas, os cálculos baseados em funções podem requerer quase o dobro do tempo de processamento. Portanto, quando você usar funções dentro de seus programas, precisará considerar os benefícios que elas oferecem (tais como facilidade de uso, reutilização de uma função existente, redução de teste, facilidade de compreensão, e assim por diante) versus a sobrecarga no desempenho que introduzem.

Compreendendo onde C coloca as variáveis locais

Como foi visto, C lhe permite declarar variáveis dentro de suas funções. Essas variáveis são locais à função, o que significa que somente a função na qual você declarou as variáveis conhece seus valores e existência. A seguinte função, usa_abc, declara três variáveis locais chamadas a, b e c:

			
void usa_abc(void)
{
   int a, b, c;
   a = 3;
   b = a + 1;
   c = a + b;
   printf(“ a contém %d b contém %d c contém %d\n”, a, b, c);
}

Toda vez que seu programa chama a função, C aloca espaço na pilha para armazenar as variáveis locais a, b e c. Quando a função termina, C descarta o espaço anteriormente alocado na pilha e os valores que as variáveis locais continham. Mesmo que sua função declare muitas variáveis locais, C armazena o valor de cada variável na pilha.

Declarando variáveis globais

As variáveis locais são definidas dentro de uma função cujos nomes e existência são conhecidos somente para essa função. Além das variáveis locais, C também permite que seus programas usem variáveis globais, cujos nomes, valores e existência são conhecidos em todo o seu programa. Em outras palavras, todos os programas C podem usar variáveis globais. O programa a seguir, ilustra o uso de três variáveis globais, a, b e c:

		
#include <stdio.h>
int a =1, b = 2, c = 3; // Variáveis globais
void valores_globais(void)
{
   printf (“a contém %d b contém %d c contém %d\n”, a, b, c);
}

void main(void)
{
   valores_globais();
   printf(“a contém %d b contém %d c contém %d\n”, a, b, c);
}

Quando você compila e executa este programa, as funções valores globais e main exibem os valores da variável global. Observe que você declara as variáveis fora de todas as funções. Ao declarar variáveis globais deste modo, todas as funções do seu programa podem usar e alterar os valores da variável global simplesmente referenciando o nome da variável global. Embora as variáveis globais possam parecer convenientes, o uso incorreto delas pode causar erros que são difíceis de depurar.

Evite usar variáveis globais

À primeira vista, usar variáveis globais parece simplificar a programação porque elimina a necessidade de parâmetros de funções e, mais importante, a necessidade de compreender a chamada por valor e a chamada por referência. No entanto, infelizmente, as variáveis globais com freqüência criam mais erros do que corrigem. Como seu código pode mudar o valor de uma variável global em virtualmente qualquer ponto dentro do seu programa, é muito difícil para outro programador que esteja lendo seu programa encontrar cada local no programa onde a variável global é alterada. Portanto, outros programadores podem fazer mudanças no seu programa sem compreender totalmente o efeito que a modificação tem em uma variável global. Como regra, as funções somente devem modificar aquelas variáveis passadas para as funções como parâmetros. Isso permite que os programadores estudem os protótipos da função para determinar rapidamente quais variáveis uma função altera.

Solucionando os conflitos de nomes

Como você aprendeu, as variáveis locais são variáveis que você declara dentro de uma função cujos nomes são conhecidos somente para essa função. Por outro lado, quando você declara variáveis globais fora de todas as funções, toda função em todo o seu programa conhecerá os nomes delas. Se seu programa usa variáveis globais, algumas vezes o nome de uma variável global é o mesmo que aquele de uma variável local que seu programa declara dentro de uma função. Por exemplo, o programa a seguir, usa as variáveis globais a, b e c. A função conflito_a usa uma variável local chamada a e as variáveis globais b e c:

			
#include <stdio.h>
int a = 1, b = 2, c = 3; // variáveis globais
void conflito_a(void)
{
   int a = 100;
   printf(“a contém %d b contém %d c contém %d\n”, a, b, c);
}

void main(void)
{
   conflito_a();
   printf(“a contém %d b contém %d c contém %d\n”, a, b, c);
}

Quando você compilar e executar o programa, sua tela exibirá o seguinte:

			
a contém 100 b contém 2 c contém 3
a contém 1 b contém 2 c contém 3

Quando nomes de variáveis globais e nomes de variáveis locais estão em conflito, C sempre usará a variável local. Como você pode ver, as alterações que a função conflito_a fez na variável a somente aparecem dentro da função.


Nota: Embora o propósito deste programa seja ilustrar como C soluciona os conflitos de nomes, ele também ilustra a confusão que pode ocorrer quando você usa variáveis globais. Neste caso, um programador que esteja lendo seu código precisa prestar muita atenção para determinar que a função não altere a variável global a, mas, sim, uma variável local. Como a função combina o uso de variáveis globais e locais, o código pode tornar-se difícil de entender.

Definindo melhor o escopo de uma variável global

Dependendo de onde você define uma variável global, é possível controlar quais funções são na realidade capazes de referenciar a variável. Em outras palavras, você pode controlar o escopo da variável global. Quando seu programa declara uma variável global, quaisquer funções que seguem a declaração da variável podem referenciar essa variável, até o final do arquivo fonte. As funções que têm definições que aparecem antes da definição da variável global não podem acessar a variável global. Como um exemplo, considere o programa a seguir, que define a variável global título:

			
#include <stdio.h>
void titulo_desconhecido(void)
{
   printf(“o título do livro é %s\n”, titulo);
}

char titulo[] = “Biblia do Programador C/C++, do Jamsa!”;
void main(void)
{
   printf(“Titulo: %s\n”, titulo);
}

Como você pode ver, a função titulo_desconhecido tentará exibir a variável titulo. No entanto, como a declaração da variável global ocorre após a definição, a variável global é desconhecida dentro da função. Quando você tentar compilar este programa, seu compilador gerará um erro. Para corrigir o erro, coloque a declaração da variável global antes da função.

Compreendendo a chamada por valor

Já sabemos que seus programas passam informações para as funções usando parâmetros. Quando você passa um parâmetro para uma função, C usa uma técnica conhecida como chamada por valor para fornecer à função uma cópia dos valores dos parâmetros. Usando a chamada por valor, quaisquer modificações que a função fizer nos parâmetros existem somente dentro da própria função. Quando a função termina, o valor das variáveis que a função chamadora passou para a função não é modificado dentro da função chamadora. Por exemplo, o programa a seguir, passa três parâmetros (as variáveis a, b e c) para a função exibe_e_altera. A função, por usa vez, exibirá os valores, somará 100 aos valores e depois exibirá o resultado. Quando a função terminar, o programa exibirá os valores das variáveis. Como C usa chamada por valor, a função não altera os valores das variáveis dentro do chamador, como mostra aqui:

			
#include <stdio.h>
void exibe_e_altera (int primeiro, int segundo, int terceiro)
{
  printf(“Valores originais da função %d %d %d\n”,primeiro,segundo,terceiro);
  primeiro += 100;    // o mesmo que   primeiro = primeiro + 100;
  segundo += 100;
  terceiro += 100;
  printf(“Valores finais da função %d %d %d\n”,primeiro,segundo,terceiro);
}

void main(void)
{
   int a = 1, b = 2, c = 3;
   exibe_e_altera(a,b,c);
   printf(“Valores finais em main %d %d %d\n”, a, b, c);
}

Quando você compilar e executar o programa, sua tela exibirá o seguinte:

Valores originais da função 1 2 3
Valores finais da função 101 102 103
Valores finais em main 1 2 3

Como você pode ver, as alterações que a função faz nas variáveis somente são visíveis dentro da própria função. Quando a função termina, as variáveis dentro de main estão inalteradas. Nota: Quando você usa chamada por referência, a função pode modificar o valor de um parâmetro para que a modificação seja visível fora da função.

Evitando a alteração no valor do parâmetro com a chamada por valor

Você aprendeu que, por padrão, C usa chamada por valor para passar parâmetros para as funções. Consequentemente, quaisquer alterações nos valores dos parâmetros ocorrem apenas dentro da própria função. Quando a função termina, os valores das variáveis que o programa passou para a função estão inalterados. Uma variável é basicamente um nome atribuído a uma posição de memória. Toda variável tem dois atributos de interesse – seu valor atual e seu endereço de memória. No caso do programa apresentado na dica anterior, as variáveis a, b e c poderiam usar os endereços de memória mostrados na figura abaixo:

As variáveis armazenam um valor e residem em uma posição de memória específica. Quando você passa parâmetros para uma função, C coloca os valores correspondentes na pilha. No caso das variáveis a, b e c, a pilha contém os valores 1, 2 e 3. Quando a função acessa os valores da variável, a função referencia as posições da pilha, como mostrado na figura abaixo.

As funções referenciam valores armazenados na pilha. Quaisquer modificações que a função fizer nos valores dos parâmetros realmente alteram os valores da pilha, como mostrado na Figura abaixo.

As modificações que as funções fazem nos valores dos parâmetros afetam apenas os valores que estão na pilha. Quando a função termina, C descarta os valores na pilha bem como as alterações que a função fez nos conteúdos da pilha. A função nunca referencia as posições de memória que contém o valor de cada variável, de modo que suas funções não podem fazer alterações que existam após a função terminar em qualquer parâmetro que a função recebe usando a chamada por valor.



Um exemplo usando Vetor

Supondo que eu tenha dois vetores. O primeiro com 5 elementos e o segundo com 7 elementos. Preciso preencher estes vetores com valores aleatórios entre 0 e 100 e depois imprimir os 2 vetores, mas para isso quero criar um programa bem "light", com poucas linhas. Um solução possível seria criar uma função, a qual preencheria os dois vetores, independente do tamanho dos mesmos.

#include "stdio.h"
#include "conio.h"
#include "stdlib.h"

// prototipando a funcao preencher()
void preencher(int, int vet[]);

// **** funcao main ****
void main()
{
   int vet1[5], vet2[7],tam1=5, tam2=7, i;
   clrscr();

   // chamando a funcao p/ preencher o vetor 1
      preencher(tam1, vet1);

   //imprimindo vetor 1
   for(i=0;i<tam1;i++)
      printf("%d \t",vet1[i]);
   printf("\n\n");

   // chamando a funcao p/ preencher o vetor 2
   preencher(tam2, vet2);

   //imprimindo vetor 2
   for(i=0;i<tam2;i++)
      printf("%d \t",vet2[i]);

   getch();
}

// *** funcao preencher ****
void preencher(int tam, int vet[])
{
   int j;
   //preenchendo o vetor
   for(j=0;j<tam;j++)
   {
      vet[j]=j;
   }
}


Exercício

1) Modifique o exemplo acima, criando uma função para imprimir os 2 vetores.