Ponteiros

Ponteiros

Nesse post você vai encontrar algumas explicações relembrando a teoria de ponteiros e também exercícios práticos e teóricos sobre ponteiros e como eles são usados na computação.

Configure seu ambiente

Antes de mais nada preciso avisar que você precisará ter um ambiente instalado e funcional para trabalhar. Você poderá resolver esses exercícios usando linguagens que dão acesso a manipulação dos ponteiros de memória (por exemplo C ou C++). O ambiente inclui um compilador (no windows geralmente é o MinGw) e para outros sistemas operacionais você pode escolher outras opções. Além disso, eu recomendo fortemente que você utilize uma IDE de programação como o DEVcpp ou então o Codeblocks.

Se você está no windows:

Esses exercícios fazem parte de um conjunto de exercícios disponibilizados gratuitamente pelo nosso website. Acesse aqui os demais exercícios.

Vamos dar aquela revisada?

Se você está procurando exercícios sobre ponteiros para treinar seus conhecimentos posso assumir que você de certa forma já deve ter estudado um pouco sobre eles. Se você já sabe como eles funcionam exatamente, pule essa revisão e vá direto para os exercícios que estão na próxima seção. Porém, se você ainda tem algumas dúvidas seria legal você revisar comigo.

Primeiramente, precisamos entender: o que é um ponteiro? um ponteiro é uma referência para um endereço de memória do seu computador. Antes mesmo de você estudar computação você já teve contato com referências e você nem percebeu. Imagine a seguinte situação, você é entregador e vai entregar o pedido em uma casa e encontra a seguinte situação:

motoqueiro encontra uma placa informando um novo endereço (ponteiro)
Entregador encontrou essa situação… e ai?

Parece um exemplo bobo, mas ilustra exatamente como os ponteiros funcionam. Os ponteiros são referências para um outro endereço que fica situado em um outro lugar (físico).

Para entender como isso funciona dentro do contexto da computação precisamos compreender um pouquinho do funcionamento da memória principal, veja esse exemplo:

Memória RAM vazia
Representação simplificada de memória RAM

Cada um dos quadradinhos brancos são “espaços” que podem abrigar valores, esses valores na verdade são apenas sinais energéticos (0 ou 1). A memória ram funciona então como um grande “armazém” de informações que permite que você armazene seu nome por exemplo:

Uma memória RAM parcialmente preenchida

Observando a memória, você pode perceber que os pequenos espaços conseguem apenas guardar uma única informação, ou seja, para guardar um nome completo um conjunto de espaços é necessário (mais conhecido como vetor). Além disso, o armazenamento da memória não é sequencial, por isso, você consegue ver que existem valores vazios e os valores preenchidos estão espalhados pela memória.

O último conceito que você precisa compreender são os endereços de memória. Eles são simplesmente uma forma de você dizer ao seu computador qual espaço da memória você quer acessar, veja um exemplo:

Exemplo de memória RAM com Endereços
Exemplo de memória RAM com endereços

Veja que na imagem acima você pode referenciar um endereço de memória usando suas linhas e colunas: A1 = V; A2 = a; etc. Portanto, o endereço com o ponto de interrogação poderia ser representado pelo endereço de memória B3.

Certo, agora sabendo disso, você pode começar a compreender o que são ponteiros. Os ponteiros são tipos de dados especiais que apontam para um espaço específico da sua memória. Então, na verdade é como se você armazenasse em sua memória RAM uma referência para outro endereço:

Exemplo de referência de memória (ponteiro)
Exemplo de referenciação (ponteiro)

Então, todas as vezes que você pensar em ponteiros você deve pensar em uma referência. Na linguagem de programação seria algo mais ou menos assim:

void main(){
    // aqui eu declaro uma nova variável, ou seja, um dado armazenado na memória RAM
    int a = 10;
    // aqui eu declaro um ponteiro que aponta para um inteiro.
    int *p;
    // aqui eu extraio da variável 'a' o seu endereço (em hexadecimal) e armazeno em p
    p = &a;
}

Quando você armazena algo em um ponteiro ele precisa necessariamente dizer pra que tipo de dados ele aponta (int, char, float, double). Além disso você pode acessar o valor apontado quando você quiser usando o operador *.

void main(){
    int a = 10;
    int *p;
    p = &a;
    //esse código imprime o valor que está em "a", ou seja, "10"
    printf("%d", *p);
}

Agora que já refrescamos sua memória está na hora de você praticar e pensar um pouco sobre a relevância desse conceito para computação.

Exercícios Teóricos

  1. Faça um paralelo sobre o conceito de ponteiros e os vetores (estáticos)
  2. Explique porquê os ponteiros ajudam o desenvolvimento de aplicações que otimizam o espaço da memória
  3. Qual a relação entre os ponteiros e uma String? Explique e dê exemplos.
  4. “Um vetor é essencialmente um ponteiro” – Você concorda com essa afirmação? explique
  5. Imagine que você definiu um vetor chamado “vetor” e seus elementos são inteiros e cada um ocupa 8 bytes da sua memória. Se o endereço de vetor[0] é 85800, qual o valor da expressão vetor + 6?  
  6. Suponha que v é um vetor. Descreva o porquê existe uma diferença entre escrever v[3] e v + 3

Exercícios práticos

  1. O código abaixo contém a declaração de algumas variáveis e ponteiros. Quando você executa esse código os valores x, y e Pserão os valores de x, y e p ao final do trecho de código abaixo?

void main() {
 int x, y, *p; y = 0;
 p = &y;
 x = *p;
 x = 4;
 (*p)++;
 --x;
 (*p) += x;
}

2. Uma das atividades mais desafiadoras aos programadores é entender o código feito por outras pessoas. Isso fica ainda pior quando existem erros ou “bugs” nos códigos. Então, para você praticar um pouco isso os trexos de código abaixo possuem alguns erros. Identifique-os e reescreva indicando como deveriam ser?  

a)

void main() {
   int x, *p;
   x = 100;
   p = x;
   printf(“Valor de p: %d.n”, *p);
}

b)

void troca (int *i, int *j) {
   int *temp;
   *temp = *i;
   *i = *j;
   *j = *temp;
}

c) 

main(){
   char *a, *b;
   a = "abacate";
   b = "uva”;
   if (a < b)
      printf ("%s vem antes de %s no dicionário", a, b);
   else
      printf ("%s vem depois de %s no dicionário", a, b);
}

 

3) Crie uma função que receba por parâmetro um vetor de números inteiros e os endereços de duas variáveis inteiras (que podemos chamar de min e max). Ao passar essas variáveis para a função seu programa deverá analisar qual é o maior e o menor elemento do vetor e depositar esses elementos nas variáveis do parâmetro. É claro que para testar tudo isso você vai precisar criar uma função main que consuma a função que você definiu.

Use o seguinte protótipo para sua função:

void maiorMenor(int vetor[], int* maximo, int* minimo); 

  4) Nesse exercício abandone seu computador por alguns minutos e analise o conteúdo do vetor “a” depois dos seguintes comandos.  

main(){
     int i, a[99];

     for (i = 0; i < 99; ++i) 
          a[i] = 98 - i;

     for (i = 0; i < 99; ++i) 
          a[i] = a[a[i]];
}

5) Agora vamos manipular um vetor inteiro dentro de uma função. Para isso, você precisa definir a assinatura da função :

void troca(float *a, float *b);   

Agora você precisa escrever a função que troca o valor que está em “a” pelo valor que está em “b”.

Veja o exemplo da função main:

void main(){
    float a,b;
    a = 1.2;
    b = 2.4;
    troca (&a, &b);
    // por fim imprimindo:
    printf("%f,%f", a, b);
}

O resultado esperado é:

//output
2.4,1.2

6) Agora vamos criar uma função que copia um vetor de caracteres para outro vetor (cria uma cópia). A assinatura da função deve ser:

char *strcopy(char *str, int tamanho);   

7) Você sabia que é possível criar um ponteiro que aponta para um ponteiro? Como explicamos anteriormente um ponteiro é um tipo de dados que aponta para um endereço de memória de uma variável (que possui um tipo), ou seja, quando você cria um int *a; você cria um ponteiro para um inteiro.

No entanto, os ponteiros podem apontar para outros ponteiros. Para isso você deverá colocar dois sinais de asteriscos, seria algo assim:

int **p

Agora analise o código abaixo e explique cada uma das linhas usando comentários.

main (){
   int x = 100, *p, **pp;
   p = &x;
   pp = &p;
   printf("Valor de pp: %dn", **pp);
} 

8) Vamos criar uma função agora que localiza uma letra em um vetor e retorna um outro vetor com suas posições onde a letra foi encontrada. Por exemplo:

 0  1  2  3  4
[v][a][n][i][a]

// output procurando a letra "a"
[1][4]     

9) Vamos treinar um pouco a identificar o que significa os operadores asterisco. Para relembrar eles podem assumir duas funções:

  1. quando você quer definir um ponteiro
  2. quando você quer desreferenciar um ponteiro (acessar o valor da variável que o ponteiro aponta)

O que significa o operador asterisco em cada um dos seguintes casos:

a) int *p;
b) printf("%d",*p);
c) *p = x*5;
d) printf("%d",*(p+1)); 

 10) Os ponteiros são excelentes exercícios de lógica, a seguir temos uma função main com alguns ponteiros e variáveis. Identifique o que será impresso na tela.  

void main(){ 
   int i=5, *p; 
   p = &i; 
   printf("%d, %d, %d ,%d, %d", p,(*p+2),**&p, (3**p),(**&p+4); 
}

Respostas

Vinicius dos Santos

Apenas um apaixonado por Ciência da Computação e a forma com que ela pode transformar vidas!

Deixe um comentário

13 − três =