Introdução aos ponteiros

Ver Todos os Exercícios

Teoria

Fala galera, aqui é o Juan do Responde Aí. E hoje vamos começar um assunto que assusta muita gente, os Ponteiros.

Endereços de memória.

Você provavelmente já sabe que um computador possui diversos componentes: processador, HD, placa mãe, etc. E um dos principais componentes é a Memória RAM. Quando abrimos um aplicativo no celular, ou um programa no computador, as variáveis, funções, vetores, e todo o resto do programa é carregado na Memória RAM. Pra organizar todas essas coisas na RAM são usados os endereços de memória.

Enquanto eu e você pensamos um programa como uma coleção de variáveis, vetores, funções, objetos, e outras coisas, pro computador tudo isso não passa de uma coleção de endereços de memória. Depois de compilado, cada parte do nosso programa é guardado em um endereço de memória.

Uma das coisas que torna a Linguagem C++ tão poderosa, é que ela nos permite acessar os endereços de memória e deixa que a gente trabalhe diretamente nos endereços, sem nos preocupar com a variável ou o valor que “mora” nesse endereço.

Mas aí surge a dúvida: “Como podemos saber o endereço de memória de uma variável?” Pra isso vamos usar o operador de endereço & (também chamado de operador de referência). Quando colocamos este operador na frente de uma variável nós recebemos o endereço de memória.

Vamos ver um exemplo:

Nesse programa nós criamos uma variável inteira A e inicializamos com o valor 10 . Na linha 08 imprimimos o valor contido na variável, e obtemos a resposta: 10 .

Na linha 09 a gente usa o operador & para descobrir o endereço de memória onde a variável A está armazenada, e a resposta é 0 x 60 f e 14 . No C++ os endereços de memória são representados por um número hexadecimal.

Note que: se você reproduzir esse código no seu computador, você provavelmente irá receber um endereço diferente, afinal de contas, nossos computadores são diferentes!

Mas o que acontece se a gente mudar o valor de A ? O endereço vai mudar? Vamos testar:

Adicionamos algumas linhas ao código. Na linha 12 mudamos o valor guardado na variável A . Em seguida imprimimos o valor na linha 14 e imprimimos o endereço na linha 15.

Sacou?! O valor contido na variável A pode mudar, mas o endereço da variável não muda! O que você precisa entender é: quando guardamos um valor em uma variável, na prática, estamos guardando um valor em um endereço de memória. Os valores podem mudar à vontade, mas os endereços são fixos!

Enfim, os Ponteiros.

Mas agora que você viu toda essa explicação sobre os endereços de memória, tá na hora de entrar no assunto: mas o que são esses tais ponteiros afinal?

Ponteiros são variáveis que armazenam endereços de memória! Exatamente, da mesma forma que uma variável i n t armazena um inteiro, e uma variável c h a r armazena um caractere, o ponteiro é uma variável que armazena endereços de memória de outras variáveis.

Esse processo de “armazenar um endereço de memória” é chamado de “apontar”, e é daí que vem o nome: ponteiros (alguns professores chamam de apontadores ou de pointers)

Para declarar um ponteiro fazemos da seguinte forma:

t i p o   *   n o m e _ d o _ p o n t e i r o ;

O asterisco * (sim, o mesmo símbolo usado para multiplicar) indica que a variável é um ponteiro. E claro, o n o m e _ d o _ p o n t e i r o é o nome que identifica o ponteiro. O t i p o indica pra qual tipo de variável aquele ponteiro pode apontar.

Vamos ver um exemplo simples:

Na linha 06 criamos uma variável N U M do tipo d o u b l e , em seguida, na linha 07 criamos um ponteiro chamado p n t _ N U M , note que o ponteiro também deve ser do tipo d o u b l e ! Na linha 10 fazemos que p n t _ N U M aponte pro endereço de N U M . Nas linhas 13 a 16 imprimimos os valores, e você pode ver que o ponteiro p n t _ N U M guarda o valor do endereço de N U M .

Acessando endereços de memória através de ponteiros

Certo, mas e daí?! O que dá pra fazer com esses endereços?

Acontece que utilizando os ponteiros podemos acessar diretamente os endereços de memória e alterar os valores de uma variável direto da fonte!

Uma vez que você armazenou um endereço de memória em um ponteiro, podemos usar o operador * para acessar o valor contido no endereço de memória. Esse processo é chamado de desreferência. E no contexto de ponteiros o operador * é chamado operador de desreferência.

Veja esse exemplo:

Primeiro criamos uma variável chamada N U M = 1.5 , em seguida criamos um ponteiro p n t _ N U M que guarda o endereço de memória de N U M . Na linha 14 usamos o operador * para acessar o valor contido em N U M através do ponteiro e imprimimos usando c o u t .

Na linha 17 a gente faz a operação * p n t _ N U M   e guardamos o valor 753 no endereço de memória. O resultado é que o valor contido na variável NUM foi modificado. Então, quando a gente imprimir a variável N U M , o valor impresso será 753 .

À primeira vista pode parecer ilógico acessar e alterar variáveis dessa forma. Mas logo você vai perceber que os ponteiros são um dos recursos mais poderosos e práticos do C++!

Evitando erros com ponteiros.

Ponteiros são ferramentas poderosas. Quando bem usados deixam o código mais simples e mais rápido. Mas quando mal utilizados podem gerar bugs e problemas sérios no código. Pra facilitar sua vida com ponteiros o ideal é seguir algumas dicas:

  1. Sempre inicialize seus ponteiros: Uma boa prática é sempre inicializar os seus ponteiros na mesma hora que você criar. Mas se por acaso você precisar iniciar um ponteiro sem valor, o ideal é iniciar esse ponteiro com valor 0 ou então usando a palavra-chave N U L L (que significa “nulo” ou “vazio”)
  2. Utilize uma convenção de nomes para indicar ponteiros: Pra deixar claro quais variáveis são ponteiros você pode utilizar um prefixo. Por exemplo: p o n t _ A ou p V a r ou p n t _ B . Usar uma convenção de nomes ajuda a deixar o código mais legível e evita que você se confunda!
  3. Use um asterisco para cada ponteiro: Se você quiser declarar vários ponteiros numa mesma linha, use um * para cada ponteiro.

E por hoje é isso pessoal! Fiquem atentos aos ponteiros de vocês!

Agora que você está manjando dos ponteiros, bora ver uns exercícios de fixação.

Acessando endereços de memória através de ponteiros

Evitando erros com ponteiros.

Exercícios Resolvidos

Exercício Resolvido #1

Explique o que é um endereço de memória e sua relação com os ponteiros

Passo 1

Sempre que a gente cria uma variável no C++ o que estamos fazendo é dizer pro computador que vamos guardar um valor na memória.

Para organizar todas as variáveis na memoria RAM, o computador organiza a memória em blocos, e cada um desses blocos tem um endereço que determina sua localização exata. O endereço de memória é a localização exata de um dado na memória ram. Cada endereço representa um byte da mémoria.

Já um ponteiro é uma variável especial que é capaz de guardar um endereço de memória. Ele permite que a gente acesse e modifique diretamente os valores armazenados na memória RAM do computador.

Por exemplo, se você tiver uma variável que contém um valor inteiro, você pode criar um ponteiro para ela assim:

i n t   v a r = 1 ; i n t * p n t _ v a r   = & v a r

A variável v a r armazena o valor 1 , e o ponteiro p n t _ v a r armazena o endereço de v a r :

Resposta

Ei, a resposta está no passo a passo :)

Exercício Resolvido #2

Cris estuda engenharia, e pra uma aula de C++ fez o seguinte código.

O código não funciona como deveria. Ajude-o a encontrar os problemas.

Passo 1

Nessa questão precisamos encontrar os erros no código. Uma forma de fazer isso é rodar o código acima e encontrar os erros com a ajuda do compilador.

Mas ao invés disso, vamos ler o código linha por linha e encontrar os problemas. Vamos lá!

Até a linha 05 temos a estrutura básica de um código C++. Nenhum erro aqui. Na linha 06 é declarada uma variável inteira, e até aqui tudo bem.

Na linha 07 Cris queria declarar um ponteiro, mas esqueceu de utilizar o operador * . Sem este operador não é possível criar um ponteiro.

Passo 2

Seguindo em frente.

Na linha 09 é declarada uma variável do tipo d o u b l e para armazenar o valor de P I .  

Na linha 10, Cris quer declarar um ponteiro para P I , mas note que a variável P I é do tipo d o u b l e , e o ponteiro criado aponta para tipo i n t . O tipo do ponteiro e da variável devem ser os mesmos!

Passo 3

Agora vamos analisar as impressões. O c o u t da linha 12 está tudo certo!

Mas se você notar, na linha 13 Cris quer imprimir o endereço de memória, mas acaba imprimindo o valor contido na variável. Para imprimir corretamente o endereço de memória, a solução é adicionar o operador & antes da variável.

Resposta

Exercício Resolvido #3

Observe os códigos abaixo. Na linha 11 de ambos os códigos tem um c o u t . Existe alguma diferença no que é impresso?

Passo 1

Vamos lá pessoal.

Primeiro de tudo, note que o ponteiro p t r _ P aponta para a variável P .

E agora, vamos comparar os códigos. A primeira diferença está na linha 09. No código à esquerda, a variável P é acessada e seu valor é alterada para 7 .

Já no código à direita, usamos o ponteiro p t r _ P . Perceba que o operador * foi usado, e isso quer dizer que estamos acessando o valor contido no endereço. Então, quando fazemos * p t r _ P = 7 estamos guardando o valor 7 dentro da variável P

Ou seja, em ambos os códigos o valor da variável P é alterado para 7 .

Passo 2

Na linha 11 usa-se o c o u t .

No código à esquerda temos a expressão

Veja que usamos o ponteiro junto com o operador * , isso quer dizer que vamos receber o valor contido no endereço. Em outras palavras será impresso:

P = 7

Já no código à direita temos a simples impressão do conteúdo da variável P

Ou seja, o resultado será P = 7 .

Passo 3

Concluindo: Os dois códigos são equivalentes, pois nos dois casos utilizamos o ponteiro p n t _ P para acessar o endereço de memória e pegar o valor contido dentro do endereço.

Resposta

Ei, a resposta está no passo a passo :)

Exercício Resolvido #4

Crie um programa que receba um valor inteiro do usuário. Em seguida, imprima na tela o valor digitado e diga qual o endereço de memória em que o valor foi armazenado.

Passo 1

Vamos lá! Essa questão pede pra gente mostrar um endereço de memória. Então certamente vamos usar o operador & .

Mas antes, vamos começar o nosso programa declarando uma variável inteira chamada V a l o r

Passo 2

Agora vamos pedir pro usuário inserir um valor. Com o c o u t pedimos pra ele inserir o valor, e com o c i n guardamos o valor na variável.

Passo 3

Então, vamos imprimir o valor que o usuário imprimiu, usando o c o u t

E para imprimir um endereço de memória basta a gente usar o operador & antes da variável. Usando o c o u t podemos imprimir o endereço:

E é isso. Questão concluída.

Resposta

Exercício Resolvido #5

Faça um código que tenha uma variável N u m do tipo inteiro, iniciada com o valor 10 . Crie um ponteiro chamado p t r _ N u m , e use o ponteiro para:

  1. Imprimir o endereço de memória da variável N u m
  2. Imprimir o valor contido dentro da variável N u m
  3. Alterar o valor de N u m para 35.

Passo 1

Vamos lá galera. A questão tá pedindo pra gente aplicar os conceitos básicos sobre ponteiros.

Pra começar, vamos seguir as instruções do enunciado. Vamos declarar uma variável inteira chamada N u m e iniciá-la com valor 10 .

Em seguida, vamos criar um ponteiro chamado p t r _ N u m que aponte para n u m . Não se esqueça que o ponteiro deve ser declarado com o tipo i n t e o operador * .

Passo 2

Item A)

O primeiro item da questão pede pra gente usar o ponteiro para imprimir o valor do endereço de memória da variável N u m .

Isso é tranquilo de fazer. Lembre-se que um ponteiro é uma variável que armazena um endereço de memória! Então é só a gente usar o c o u t para imprimir o valor contido na variável p t r _ N u m

Passo 3

Item B)

O segundo item pede pra gente imprimir o valor contido na variável, mas usando o ponteiro para isso.

Pra fazer isso, basta a gente utilizar o operador * antes do ponteiro!

Passo 4

Item C)

O último item pede pra gente alterar o valor da variável N u m , mas utilizando o ponteiro para isso.

Para efetuar isso, basta usar o operador * antes do ponteiro, pois dessa forma vamos acessar o valor contido no endereço. Em seguida, usamos o operador = para guardar o valor 35 no endereço de memória.

E prontinho! Questão feita!

Resposta

Exercício Resolvido #6

Escreva um programa que declare um inteiro, um real e um char, e ponteiros para inteiro, real, e char. Associe as variáveis aos ponteiros (use &). Modifique os valores de cada variável usando os ponteiros. Imprima os valores das variáveis antes e após a modificação.

Passo 1

Lets go everybody!

Nesse exercício vamos trabalhar todos os conceitos básicos sobre ponteiros. A gente vai criar variáveis, associar essas variáveis a ponteiros, e utilizar estes ponteiros para acessar e modificar os valores.

Mas antes, vamos começar com o cabeçalho do código:

Passo 2

Primeiro de tudo, vamos criar três variáveis dos tipos inteiro, real e caractere. Vamos declarar a variável A do tipo i n t incializada com o valor 0 ; a variável B do tipo f l o a t inicializada com o valor 0 ; e por último a variável chamada C do tipo c h a r inicializada com o caractere ‘ 0 ’ .

Passo 3

Agora vamos declarar os nossos ponteiros.

Primeiro a gente precisa lembrar que o ponteiro precisa ser do mesmo tipo que a variável. Então precisaremos de ponteiros do tipo i n t , do tipo f l o a t e do tipo c h a r

Para declarar um ponteiro indicamos o tipo, depois usamos o operador * e inserimos o nome do ponteiro:

t i p o * n o m e P o n t e i r o

Para dar nome aos nossos ponteiros vamos usar o prefixo pnt_ pra que a gente identifique facilmente quais são os ponteiros do programa e pra qual variável eles apontam.

Uma boa prática de programação é associar os ponteiros e as variáveis no momento em que os ponteiros são criados. Mas uma alternativa é inicializar os ponteiros com o valor N U L L .

Passo 4

Show de bola. Variáveis e ponteiros criados.

Agora vamos associar os ponteiros e as variáveis. Pra isso, a gente vai usar o operador de endereço & para extrair o endereço de cada variável, e com o operador = vamos atribuir o valor do endereço no ponteiro adequado

p n t _ V a r i a v e l = & V a r i a v e l

Lembre-se, que o tipo do ponteiro e da variável devem ser os mesmos

Passo 5

Agora vamos alterar os valores das variáveis através dos ponteiros.

Para fazer isso, devemos utilizar o operador de desreferenciação ( * ) para acessar o valor contido no endereço. Em seguida, com o operador de atribuição ( = ) vamos alterar o conteúdo contido no endereço.

* p n t _ V a r i a v e l = n o v o V a l o r

Nessa etapa eu utilizei valores aleatórios. Mas você pode escolher quaisquer valores.

Passo 6

E pra finalizar a questão, vamos imprimir os novos valores de cada uma das variáveis. Pra isso, é só utilizar o c o u t

E é isso, galerinha! Até mais.

Resposta

Exercício Resolvido #7

Seja a seguinte sequência de instruções em um programa C++:

Qual afirmativa é falsa?

  1. p t i armazena o endereço de i
  2. * p t i é igual a 10
  3. ao se executar * p t i   =   20 ; a variável i passará a ter o valor 20
  4. ao se alterar o valor de i , o valor da expressão * p t i será modificado
  5. p t i é igual a 10

Passo 1

Bora lá, pessoal.

Questões de marcar são mais fáceis quando a gente analisa o código antes de ir para as alternativas.

Na linha 01 é criado um ponteiro chamado p t i

Na linha 02 é criada uma variável chamada i com valor inicial 10

E na linha 03 fazemos p t i apontar para i , ou seja, a gente armazena o endereço da variável i no ponteiro p t i .

Passo 2

Agora vamos analisar as alternativas.

  1. p t i armazena o endereço de i
  2. Verdadeiro. Como vimos, na linha 03 o código faz o ponteiro p t i armazenar o valor da variável i

  3. * p t i é igual a 10
  4. Verdadeiro.

    Quando a gente usa o operador de desreferenciação * a gente acessa o valor armazenado na variável apontada pelo ponteiro.

    O ponteiro p t i aponta para i , e o valor de i é 10 . Logo, o valor de * p t i é 10

  5. ao se executar * p t i   =   20 ; a variável i passará a ter o valor 20
  6. Verdadeiro.

    Quando a gente usa o operador de desreferenciação * a gente acessa o valor armazenado na variável apontada pelo ponteiro.

    Então, alterar o valor utilizando a expressão * p t i   =   20 ; é a mesma coisa que usar a expressão i = 20 ;

  7. ao se alterar o valor de i , o valor da expressão * p t i será modificado
  8. Verdadeiro.

    A expressão * p t i acessa o valor contido na variável i . Então se o valor de i for alterado, então o valor de * p t i também será alterado.

  9. p t i é igual a 10

Falso.

p t i é um ponteiro que armazena o endereço de i

Resposta

O item e) é falso.

Exercício Resolvido #8

Crie um programa que declare duas variáveis (inteiro e real) atribua valores para elas e crie dois ponteiros, cada um apontando para uma destas variáveis. Mostre na tela: O valor e o endereço de memória das variáveis, o valor do ponteiro, o endereço do ponteiro e o valor apontado pelo ponteiro

Passo 1

Ok, pessoal, vamos lá!

A questão quer que a gente crie duas variáveis e dois ponteiros, e depois imprima os endereços de memória envolvidos.

Antes de começar, vamos escrever o cabeçalho do código:

Passo 2

Primeiro, vamos criar duas variáveis: v a r 1 do tipo inteiro inicializada com valor 10 , e v a r 2 do tipo f l o a t inicializado com o valor 20 . Esses valores eu escolhi aleatoriamente

Não vá esquecer dos ponto-e-virgula.

Agora vamos criar os ponteiros que apontam para estas variáveis. Pra criar um ponteiro a gente usa a estrutura:

t i p o * p o n t e i r o = & v a r r i a v e l

O importante é que o tipo do ponteiro deve ser o mesmo tipo da variável!

Passo 3

Agora vamos imprimir os resultados que obtivemos.

Primeiro de tudo, vamos visualizar os endereços de memória das variáveis. Para isso, vamos usar o operador de endereço ( & ) para imprimir o local de memória de cada variável.

Em seguida vamos imprimir o conteúdo armazenado dentro de cada um dos ponteiros. Cada um deles deve conter um endereço de memória igual ao que imprimimos nas linhas anteriores:

E pra terminar, vamos imprimir os endereços de memória de cada um dos ponteiros. Afinal de contas, eles são variáveis também, e por isso, também possuem endereços de memória únicos.

O resultado é:

Resposta