 |
Aula
do dia 10 de Fevereiro de 2003 |
|
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.
|