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 25

Aula 26

Aula 27



Passagem de Parâmetros por Valor (uma breve revisão)




Passagem de Parâmetros por Referência (Ponteiros)




Ponteiros x Matrizes




Recapitulando....

Utilização de Parâmetros

O uso de parâmetros em uma função tem por finalidade efetuar uma comunicação entre rotinas de programa. Muito útil quando se trabalha apenas com variáveis locais, que são mais vantajosas por ocuparem um espaço menor em memória do que as variáveis globais, pois as variáveis locais só ocupam espaço em memória enquanto estão sendo utilizadas. Quando se trabalha com funções em linguagem C, é possível passar valores de uma função para outra de duas formas: Passagem de parâmetros por valor e passagem de parâmetros por referência.

Passagem de Parâmetros por Valor

Caracteriza-se por funcionar apenas como um mecanismo de entrada de valor para uma determinada função. Desta forma, o processamento é executado somente dentro da função chamada, ficando o resultado obtido "preso" dentro da função. O exemplo abaixo calcula o fatorial, passando o valor da variável limite para a função.

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

int fatorial(int);

void main()
{
   int limite;
   clrscr();
   printf("\nQual fatorial: ");
   scanf("%d", &limite);

   //chamando a funcao/passagem de parametros por valor
   int fat=fatorial(limite);

   printf("\nO fatorial de %d ‚ = %d",limite,fat);
   getche();
}

int fatorial(n)
{
   int i,fat;
   fat=1;
   for(i=1;i<=n;i++)
      fat=fat*i;
   return(fat);
}

Por exemplo, se o usuário solicitar o fatorial de 4, a saída na tela será a seguinte:

			
O fatorial de 4 = 24

Observe, que a variável N da função fatorial recebe o valor fornecido pela variável limite. Este valor estabelece o número de vezes que o looping deve ser executado. O uso da instrução return na função fatorial envia o valor da variável fat para a função chamadora (main). O comando return poderá ser escrito das seguintes formas: return (); retorna o valor da expressão return(); não retorna valor return; não retorna valor

Passagem de Parâmetro por Referência

Caracteriza-se por funcionar como um mecanismo de entrada e saída de valor para uma determinada função. Desta forma, a alteração efetuada é devolvida para a rotina chamadora. A linguagem C permite que em uma variável seja armazenado o endereço de uma outra variável. Para que isso seja possível, é necessário trabalhar com uma variável como sendo um ponteiro, um apontador. A declaração de um ponteiro ocorre de forma semelhante à de uma variável simples, tendo como diferencial o asterisco antes da variável.

Compreendendo a Chamada por Referência

Como você aprendeu, C passa parâmetros para as funções usando chamada por valor por padrão. Usando a chamada por valor, as funções não podem modificar o valor de uma variável passada para uma função. No entanto, na maioria dos programas, suas funções modificarão as variáveis de um modo ou de outro. Por exemplo, uma função que lê informações de um arquivo precisa colocar as informações em uma matriz de strings de caracteres. Da mesma forma, uma função tal como strupr precisa converter as letras em uma string de caractere para maiúsculas. Quando suas funções alteram o valor de um parâmetro, seus programas precisam passar o parâmetro para a função usando chamada por referência. A diferença entre chamada por valor e chamada por referência é que, usando a chamada por valor, as funções recebem uma cópia do valor de um parâmetro. Por outro lado, com a chamada por referência, as funções recebem o endereço de memória da variável. Portanto, as funções podem alterar o valor armazenado na posição de memória específica (em outras palavras, o valor da variável); alterações essas que permanecem após a função terminar. Para usar a chamada por referência, seu programa precisa usar ponteiros. No entanto, pense em um ponteiro simplesmente como um endereço de memória. Para atribuir o endereço de uma variável a um ponteiro, use o operador de endereço de C (&). Para acessar posteriormente o valor na posição de memória para a qual o ponteiro aponta, use o operador de redireção (*).

Obtendo um endereço

Uma variável é essencialmente um nome atribuído a uma ou mais posições de memória. Quando seu programa roda, cada variável reside em seu próprio endereço de memória. Seu programa localiza as variáveis na memória usando o endereço de memória da variável. Para determinar o endereço de uma variável, use o operador de endereço para exibir o endereço (em hexadecimal) das variáveis a, b e c:

#include <stdio.h>
void main(void)
{
   int a = 1, b = 2, c = 3;
	 
   printf(“O endereço de a é %x o valor de a é %d\n”, &a, a);
   printf(“O endereço de b é %x o valor de b é %d\n”, &b, b); 
   printf(“O endereço de c é %x o valor de c é %d\n”, &c, c);
}

Quando você compilar e executar este programa, ele exibirá uma saída similar a esta (os valores dos endereços reais serão diferentes):

			
O endereço de a é fff4 o valor de a é 1
O endereço de b é fff2 o valor de b é 2
O endereço de c é fff0 o valor de c é 3

Quando seus programas mais tarde passarem parâmetros para funções cujos valores a função precisa alterar, seus programas passarão as variáveis por referência (endereço de memória), usando o operador de endereço, como mostrado aqui:

			
alguma_funçao (&a, &b, &c);

Usando um Endereço de Memória

Quando você passa um endereço para uma função, precisa dizer ao compilador C que a função estará usando um ponteiro (o endereço de memória) de uma variável, e não o valor da variável. Para fazer isso, você precisa declarar uma variável ponteiro. Declarar uma variável ponteiro é muito similar à declaração de uma variável padrão, em que você especifica um tipo e o nome da variável. A diferença, entretanto, é que um asterisco (*) precede os nomes das variáveis ponteiro. As declarações a seguir criam variáveis ponteiro do tipo int, float e char:

			
int *i_ponteiro;
float *f_ponteiro;
char *c_ponteiro;

Após você declarar uma variável ponteiro, precisa atribuir um endereço de memória a ela. Por exemplo, o comando a seguir, atribui o endereço da variável inteira a à variável ponteiro i_ponteiro:

			
i_ponteiro = &a;

Em seguida, para usar o valor apontado pela variável ponteiro, seus programas precisam usar o operador de redireção de C – o asterisco (*). Por exemplo, o comando a seguir atribui o valor 5 à variável a (cujo endereço está contido na variável i_ponteiro):

			
*i_ponteiro = 5;

De um modo similar, o comando a seguir atribui à variável b o valor ao qual a variável i_ponteiro aponta atualmente:

			
b = *i_ponteiro;

Quando você quiser usar o valor apontado por uma variável ponteiro, use o operador de redireção (*). Quando quiser atribuir o endereço de uma variável a uma variável ponteiro, use o operador de endereço (&). O programa a seguir, ilustra o uso de uma variável ponteiro. O programa atribui à variável ponteiro i_ponteiro o endereço da variável a. O programa então usa a variável ponteiro para alterar, exibir e atribuir o valor da variável:

			
#include "stdio.h"
void main(void)
{

   int a=1, b=2;
   int *i_ponteiro;
   i_ponteiro = &a; // atribui um endereço
   *i_ponteiro = 5; // altera o valor apontado por i_ponteiro para 5
   
	 // exibe valor
   printf(“O valor apontado por i_ponteiro é %d a variável a é %d\n”,
	       *i_ponteiro, a);
   b = *i_ponteiro; // atribui o valor
   printf(“O valor de b é %d\n”, b);
   printf(“Valor de i_ponteiro %x\n”, i_ponteiro);
}

Lembre-se de que um ponteiro não é nada mais que um endereço de memória. Seu programa precisa atribuir o valor que o ponteiro (o endereço) contém. No programa acima, o programa atribui ao ponteiro o endereço da variável a. O programa poderia ter também atribuído o endereço da variável b.

Nota: Quando você usa ponteiros, precisa ainda ter em mente os tipos de valores, tais como int, float e char. Seus programas somente devem atribuir o endereço de valores inteiros às variáveis ponteiro, e assim por diante.

Alterando o Valor de um Parâmetro

Para alterar o valor de um parâmetro dentro de uma função, seus programas precisam usar a chamada por referência, passando o endereço da variável. Dentro da função, você precisa usar ponteiros. O programa a seguir, usa ponteiros e endereços (chamada por referência) para exibir e, depois, alterar os parâmetros que o programa passa para a função exibe_e_altera:

#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;
   *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);
}

Já vimos que, quando o programa chama a função, ele passa como parâmetros os endereços das variáveis a, b e c. Dentro de exibe_e_altera, a função usa variáveis ponteiro e o operador de redireção para alterar e exibir os valores dos parâmetros. Quando você compilar e executar o programa acima sua tela exibirá a seguinte saída:

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

Alterando Somente Parâmetros Específicos

Como você aprendeu, suas funções podem modificar o valor de um parâmetro usando a chamada por referência. Por exemplo, a dica anterior, apresentou a função exibe_e_altera, que usou chamada por referência para alterar o valor de cada um de seus parâmetros. No entanto, em muitos casos, suas funções podem alterar o valor de um parâmetro e ao mesmo tempo deixar o valor de um segundo parâmetro inalterado. Por exemplo, o programa a seguir, usa a função muda_primeiro para atribuir ao parâmetro primeiro o valor do parâmetro segundo:

#include <stdio.h>

void muda_primeiro(int *primeiro, int segundo)
{
*primeiro = segundo; // Atribui o valor de segundo a primeiro
}

void main(void)
{
   int a = 0, b = 5;
   muda_primeiro(& a, b);
   printf(“Valor de a %d valor de b %d\n”, a, b);
}

Como você pode ver, a função muda_primeiro usa a chamada por referência para alterar o valor do parâmetro primeiro, e chamada por valor para o parâmetro segundo. Quando seus programas usam ambas as técnicas – e eles usarão –, você precisa ter em mente quando usar ponteiros e quando referenciar diretamente a variável. Como regra, os parâmetros cujos valores você quer alterar irão requerer chamada por referência. Para compreender melhor o impacto da chamada por referência versus chamada por valor, modifique a função muda_primeiro,como mostrado aqui:

			
void muda_primeiro(int *primeiro, int segundo)
{
   *primeiro = segundo; // atribui o valor de segundo a primeiro
   segundo = 100;
}

A seguir é apresentado o programa de cálculo do fatorial, mas agora usando passagem de parâmetro por referência

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

void fatorial(int *,int);

void main()
{
   int limite;
   int fat=1;
   clrscr();

   printf("\nQual fatorial: ");
   scanf("%d", &limite);

   //chamando a funcao/passagem de parametros por referencia
   fatorial(&fat,limite);

   printf("\nO fatorial de %d ‚ = %d",limite,fat);
   getche();
}

void fatorial(int *fat2,int n)
{
   int i;
   for(i=1;i<=n;i++)
     *fat2 =*fat2 *i;
}

Por exemplo, se o usuário solicitar o fatorial de 4, a saída na tela será a seguinte:

			
O fatorial de 4 = 24

Observe que o endereço da variável fat é passado, por referência, para a variável fat2 da função fatorial. Com isso todas as alterações feitas em fat2, alterarão o valor da variável fat. Já o valor da variável limite é passada por valor para a variável n da função fat. Qualuqer mudança na variável n, não irá refletir na variável limite, pois elas pertencem a funções diferentes, as quais utilizam pilhas diferentes.

Um Exemplo com Vetor

Como demonstração, considere a leitura de 10 elementos do tipo inteiro em um vetor e a colocação dos mesmos em ordem crescente.

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

void ordena(int *, int *);

void main()
{
   int vetor[10], i, j;
   clrscr();

   //entrada de dados
   for(i=0;i<10;i++)
   {
      printf("Entre o %2d. elemento: ",i+1);
      scanf("%d",&vetor[i]);
   }

   //ordenacao do vetor
   for (i=0;i<=8;i++)
     for(j=i+1;j<=9;j++)
	ordena(&vetor[i], &vetor[j]);

   //apresentacao do vetor ordenado
   printf("\n");
   for (i=0;i<=9;i++)
      printf("Agora o %2do. elemento ‚: %d\n",i+1, vetor[i]);

   getch();
}

void ordena(int *a, int *b)
{
   int x;

   if(*a > *b)
   {
      x= *a;
      *a = *b;
      *b = x;
   }
}

Comprendendo Como C Trata Matrizes Como Ponteiros

O compilador C trata as matrizes como ponteiros. Por exemplo, quando seu programa passa uma matriz para uma função, o compilador passa o endereço inicial da matriz. O programa a seguir, exibe o endereço inicial de várias matrizes diferentes:

   
#include <stdio.h>
void main (void)
{
   int conta[10];
   float salarios[5];
   printf("O endereço da matriz conta é %x\n", conta);
   printf("O endereço da matriz salários é %x\n",salarios);
}

Quando você compilar e executar o programa, sua tela exibirá a seguinte saída:

O endereço da matriz conta é ffe2
O endereço da matriz salarios é ffce

Aplicando o Operador de Endreço & a uma Matriz

O compilador C trata uma matriz como um ponteiro para o primeiro elemento da matriz. C usa o operador de endereço (&) para retornar o endereço de uma variável. Se você aplicar o operador de endereço a uma matriz, C retornará o endereço inicial da matriz. Portanto, aplicar o operador de endereço a uma matriz é redundante. O programa a seguir, exibe o endereço inicial de uma matriz, seguido pelo ponteiro que o operador de endereço de C retorna:

#include <stdio.h>
void main (void)
{
   int conta[10];
   float salarios[5];
   printf("O endereço da matriz conta é %x &conta é %x\n", 
         conta, &conta);
   printf("O endereço da matriz salários é %x &salários é %x\n", 
         salários, &salarios);
}

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

O endereço da matriz conta é ffe2 &conta é ffe2
O endereço da matriz salarios é ffce &salarios é ffce

Compreendendo a Aritmética de Ponteiros

Um ponteiro é um endereço que aponta para um valor de um determinado tipo de memória. Nos termos mais simples possíveis, um ponteiro é um valor que aponta para uma posição de memória especifica. Se você somar o valor 1 a um ponteiro, o ponteiro apontará para a próxima posição de memória. Se você somar 5 ao valor de um ponteiro, o ponteiro apontará para a posição de memória cinco posições adiante do endereço atual. No entanto, a aritmética de ponteiro não é tão simples quanto você pode imaginar. Por exemplo, assuma que um ponteiro contenha o endereço 1000. Se você somasse 1 ao ponteiro, poderia esperar que o resultado fosse 1001. No entanto, o endereço resultante depende do tipo de ponteiro. Por exemplo, se você somar 1 a uma variável ponteiro do tipo char (que contém 1000), o endereço será 1001. Se você somar 1 a um ponteiro do tipo int (que requer dois bytes na memória), o endereço resultante será 1002. Quando você efetuar aritmética de ponteiro, tenha em mente o tipo do ponteiro. Além de somar valores aos ponteiros, seus programas poderão subtrair valores ou somar e subtrair dois ponteiros.

Incrementando e Decrementando um Ponteiro

À medida que seus programas trabalhem com ponteiros, uma das operações mais comuns que eles efetuarão será incrementar e decrementar o valor de um ponteiro para apontar para a próxima posição ou para posição anterior na memória. O programa a seguir, atribui o endereço inicial de uma matriz de valores inteiros ao ponteiro iptr. O programa depois incrementa o valor do ponteiro para exibir os cinco elementos que a matriz contém:

#include <stdio.h>
void main (void)
{
   int valores[5] = {1, 2, 3, 4, 5};
   int contador;
   int *iptr;
   iptr = valores;
   for (contador = 0; contador<5; contador++)
   {
      printf("%d\n", *iptr);
      iptr++;
   }
}

Quando você compilar e executar o programa sua tela exibirá os valores de 1 a 5. O programa inicialmente atribui o endereço inicial da matriz ao ponteiro. Em seguida, o programa incrementa o ponteiro para apontar para cada elemento.

Combinando Uma Referência e Incremento de Ponteiro

Você usou o ponteiro iptr para exibir o conteúdo de uma matriz. Para exibir o conteúdo da matriz, o ponteiro usou um laço for, como mostrado aqui:

for (contador = 0; contador<5; contador++)
{
   printf("%d\n", *iptr);
   iptr++;
}

Como você pode ver, o laço for acessa o valor do ponteiro em uma linha, e, depois, incrementa o ponteiro na próxima linha. Você já aprendeu que pode usar o operador de incremento de sufixo de C para usar o valor de uma variável, e, depois, incrementar o valor.

O laço for a seguir usa o operador de incremento de sufixo para referenciar o valor apontado pela variável ponteiro e depois incrementa o valor do ponteiro, fazendo a mesma coisa que o exemplo acima, mas usando menos linhas de programação.:

for (contador = 0; contador<5; contador++)
   printf("%d\n", *iptr);