Capítulo 3

Parte 1

 

3. Variáveis Apontador (Ponteiros) e Alocação Dinâmica de Memória

.Definição: Um ponteiro é uma variável cujo conteúdo é um endereço de memória.

Esse endereço normalmente é a posição de uma outra variável na memória.

Se uma variável contém o endereço de uma outra, então a primeira variável é dita apontar para a segunda. 3.1. Declaração de Ponteiros A declaração de uma variável do tipo ponteiro (ou apontador) consiste do tipo base (aquele para o qual o ponteiro vai apontar), um * e o nome da variável.

A forma geral é:

tipo *nome; ou tipo* nome;

Exemplos:

int *contador; ponteiro para um inteiro

char *meuString; ponteiro para caracteres

float *raizQuadrada; ponteiro para real.

Caso especial:

void *simplesPonteiro; ponteiro genérico.
 

Declarações que também devolvem ponteiros:

char nome[30]; nome sozinho é também um ponteiro para caracter,
que aponta para o primeiro elemento do nome.

Exemplo:

main ()
        {
                char    *apontaPraNome;
                int     *numero;
                .......
        }


3.2. Operadores de Ponteiros Existem dois operadores especiais para ponteiros:

Exemplo:
main ()
    {
        int *aponta;
        int      valor1, valor2;

        valor1 = 5;                     // inicializa valor1 com 5
        aponta = &valor1;               // aponta recebe o endereço 
                                        // de valor1, ou seja:
                                        // passa a apontar para valor1
        valor2 = *aponta;               // valor2 recebe o valor 
                                        // apontado por aponta, nesse
                                        // caso 5, pois aponta possui
                                        // como valor o endereço de 
                                        // valor1
     }
Precedência: Tanto o & como o * possuem precedência maior do que todos os outros operadores, com exceção do menos unário, que possue a mesma.
int valor; int *aponta;       valor = *aponta++               


3.3. Aritmética de Ponteiros: Expressões envolvendo Ponteiros

A linguagem "C" permite que se faça uma série de operações utilizando ponteiros, inclusive várias operações aritméticas, como soma e subtração, além de comparações entre ponteiros.

Isto é muito útil, pode porém, ser também muito perigoso, pois permite ao programador uma liberdade que em nenhuma outra linguagem de programação (exceto os assemblers) é possível.

3.3.1. Atribuição

A maior parte dos aspectos da atribuição vimos no capítulo anterior.
A título de reforço, observe-se que:
 
           int *p1, *p2, x;
        x = 4;
        p1 = &x;                        /* p1 passa a apontar para x       */
        p2 = p1;                        /* p2 recebeu o valor de p1, que é */
                                        /* o endereço de x, ou seja: p2    */
                                        /* também aponta para x.           */
        printf ("%p", p2 );             /* imprime o endereço de x         */
        printf ("%i", *p2 );            /* imprime o valor apontado por    */
                                        /* p2, seja: o valor de x.         */

3.3.2. Aritmética de Ponteiros

A expressão abaixo é válida em "C":
         int      *p1, *p2, *p3, *p4, x=0;
        p1 = &x;
        p2 = p1++;
        p3 = p2 + 4;
        p4 = p3 - 5;              /* p4 acaba tendo o mesmo valor que p1 */
                                  /* no começo.                          */
                                  /* Note que p1 foi incrementado e      */
                                  /* agora tem o valor (&x + 1).         */

No exemplo acima, se o endereço de x é 1000:

3.3.3. Comparações entre ponteiros

Você pode comparar ponteiros, para saber se um ponteiro aponta para um endereço de memória mais alto do que outro. Exemplo:
int *p, *q;
....
if (p < q) 
        printf("p aponta para um endereço menor que o de q");
é um trecho de programa perfeitamente válido em "C".


3.3.4 Exercício: Reimplemente o seu programinha de pilha com vetor de nºs inteiros usando como TOPO um ponteiro para inteiro, que você incrementa, decrementa e testa, para saber se a pilha está cheia, vazia, etc.

Para resolver:

            constantes Maxpilha = 100;

            tipo Pilha {
                  inteiro dados[Maxpilha];
                  inteiro *topo;
            };
 



3.4. Ponteiros e Matrizes

Ponteiros, Vetores e Matrizes possuem uma realção muito estreita em "C".

Exemplo:

char            nome[30] = "José da Silva";
char            *p1, *p2;
char            car;
int             i;
p1 = nome;                                      // nome sozinho é um ponteiro
                                                // para o 1º elemento de nome[].
car = nome[3];                                  // Atribui 'é' a car.
car = p1[0];                                    // Atribui 'J' a car. Válido.

p2 = &nome[5];                                  //Atribui a p2 o endereço da 6ª
                                                // posição de nome, no caso 'd'.
printf( "%s", p2);                              // Imprime "da Silva"...

p2 = p1;                                        // Evidentemente válido.
p2 = p1 + 5;                                    // Equivalente a p2 = &nome[5]
printf( "%s",(p1 + 5));                         // Imprime "da Silva"...
printf( "%s",(p1 + 20));                        // Cuidado: Imprime lixo!!

for (i=0; strlen(nome)- 1; i++)
        {
                printf ("%c", nome[i]);         // Imprime 'J','o','s',etc
                p2 = p1 + i;
                printf ("%c", *p2);             // Imprime 'J','o','s',etc
        }

3.4.1. Matrizes de Ponteiros

Exemplo:
        int             *vetor[30];        /* Vetor de 30 ponteiros para    */
                                         /* números inteiros.             */

        int             a=1, b=2, c=3;
        
        vetor[0] = &a;                   /* vetor[0] passa a apontar p/a. */
        vetor[1] = &b;
        vetor[2] = &c;

        printf  ( "a: %i, b: %i", *vetor[0], *vetor[1] );
                                         /* Imprime "a: 1, b: 2"...       */

3.4.2. Ponteiros para Ponteiros e Indireção Múltipla

Suponhamos a seguinte função que exibe uma mensagem de erro com base em um código de erro:
char *mensagem[] = {                      /* vetor inicializado */
                "arquivo não encontrado",
                "erro de leitura",
                "erro de escrita",
                "impossível criar arquivo"
        };
void escreveMensagemDeErro (int num)
        {
         printf ("%s\n", mensagem[num]);
        }
main ()
        {
         escreveMensagemDeErro( 3 );
        }
No caso acima, nem parece que estamos usando ponteiros. Se quiséssemos fazer o mesmo com inteiros, por exemplo em uma rotina que imprime todos os valores apontados por um vetor de inteiros, já seria diferente:
int *vetor[40];
void imprimeTodos ()
        {       int i;
                for (i=0; i < 40; i++)  printf ("%i\n", *vetor[i]);
        }
Uma forma de declarar ponteiros para ponteiros é a forma implícita já vista acima. Outra forma que podemos utilizar, quando não sabemos de antemão o espaço em memória a ser utilizado, é de declarar um ponteiro explicitamente como sendo de indireção:
#include <stdio.h>

main ()
        {
                int x, *p, **q;                 // q é um ponteiro para um 
                                                // ponteiro a inteiro.
                x = 10;
                p = &x;                         // p aponta para x
                q = &p;                         // q aponta para p
                printf ("%i\n", **q);           // imprime 10...
        }


3.5. Passagem de Parâmetros usando Ponteiros

#include <stdio.h>

char tecla;
char *a                         = "Bananarama";
char b[80]                      = "uma coisa besta";
char *c[5];

void teste1 (char *d[] )
  {
        printf( "Teste1: d[0]:%s e d[1]:%s\n\n", d[0], d[1]);
  }

void teste2 (char **d )
  {
        printf( "Teste2: d[0]:%s e d[1]:%s\n", d[0], d[1]);
        printf( "Teste3: d[0]:%s e d[1]:%s\n", *d, *(d + 1));
  }

main ()
 {
        c[0] = a;
        c[1] = b;
        printf( "a: %s e b: %s\n\n", a, b);
        printf( "c[0]: %s e c[1]: %s\n\n", c[0], c[1]);
        teste1 ( c );
        teste2 ( c );

  }





[Capítulo 4]