Manipulando ponteiros: ponteiros duplos, aritmética e vetores

Ver Todos os Exercícios

Teoria

E aí, aqui é o Juan do Responde Aí. E hoje vamos avançar nosso conhecimento de ponteiros.

Ponteiros duplos: apontando para outros ponteiros

Como já dissemos diversas vezes, ponteiros nada mais são do que variáveis que guardam endereços de memória.

Mas, se ponteiros são variáveis como qualquer outra, nada impede que a gente use um ponteiro para apontar para outro ponteiro. Esses são os chamados ponteiros duplos.

Tá, eu sei que a ideia é um pouco confusa, mas olha só como fica mais tranquilo na prática. Pra criar um ponteiro duplo basta a gente usar o operador * duas vezes:

i n t   V a r = 10 ; i n t * p n t _ A = & V a r ; i n t * * p n t _ B = & p n t _ A ;

Veja no código:

A grande novidade está na linha 08, onde a gente declara o nosso ponteiro duplo p n t B , que é iniciado com o endereço de memória do ponteiro p n t A .

Utilizando a operação * p n t B podemos acessar o valor contido na variável p n t A . E usando a operação * * p n t B acessamos o valor contido na variável V a r .

Aritmética de ponteiros

Você pode pensar nos endereços de memória como números hexadecimais e por isso podemos efetuar algumas operações matemáticas com números inteiros ou com outros ponteiros:

Operações entre dois ponteiros: Entre dois ponteiros podemos realizar comparações ( < ,     > ,     > = ,     < = ,     = = ,     ! = ). Veja o exemplo:

Operações entre ponteiro e um inteiro: Um valor inteiro pode ser somado ( + e + = ) e subtraído de um ponteiro ( - e - = ), e por extensão, os ponteiros podem ser incrementados e decrementados ( + + e - - ).

Mas tem um detalhe, o tipo de variável tem influência sobre as operações com ponteiros. Veja o exemplo.

O ponteiro p n t _ A aponta para uma variável do tipo inteiro que ocupa 4   b y t e s . Em seguida somamos o ponteiro com uma unidade, e o resultado aumentou 4 unidades. Isso não é coincidência, nem um erro.

Quando um ponteiro é somado ou subtraído o resultado é proporcional ao tipo de dado que o ponteiro aponta. Se fosse um ponteiro do tipo d o u b l e o avanço seria de 8   b y t e s . Se fosse do tipo char o avanço seria de 1   b y t e .

O último detalhe importante é a precedência das operações. A precedência e associatividade das operações é a mesma que já estudamos. Mas como estamos tratando de ponteiros, também devemos ficar atentos aos operadores de referência & e desreferência * .

Tenha cuidado para não confundir o operador de multiplicação com o operador de desreferência. Por exemplo, a expressão:

* p n t * v a r

É a multiplicação entre o valor contido no ponteiro p n t vezes o valor contido na variável v a r .

Relacionamento entre vetores e ponteiros

Pode não parecer, mas na verdade, os vetores nada mais são do que tipos especiais de ponteiro! Não acredita? Então que tal você tentar colocar um vetor direto no c o u t

Ora, ora, parece que temos um endereço de memória aqui. Será se esse é o endereço de memória do vetor? Pra tirar a dúvida vamos usar o operador & no vetor:

Exatamente o mesmo valor! Acontece que quando usamos apenas o nome do vetor (sem os colchetes), o vetor se comporta como um ponteiro!

Mas então qual será o local de memória de cada parte do vetor? Qual o local de memória de V e c [ 0 ] , V e c [ 1 ] , e V e c [ 2 ] ?

E olha que legal, o endereço de memória do vetor é igual ao endereço de memória do primeiro elemento! E os outros elementos do vetor ficam nos endereços de memória seguintes.

Uma array sem colchetes pode ser considerada como um ponteiro para o primeiro elemento!

Isso acontece porque por baixo do capô, um vetor é um tipo especial de ponteiro onde todos os elementos são organizados na memória em sequência, e o endereço do vetor é igual ao endereço do primeiro elemento.

Indexando vetores através da aritmética de ponteiros

Podemos acessar um valor específico em um vetor através do seu índice dessa forma: V e c   [   i n d i c e   ] .

Mas vamos conectar algumas coisas que a gente já aprendeu: 1) Um vetor é apenas uma sequência de endereços de memória. 2) Um vetor sem os colchetes é um ponteiro para o primeiro endereço. 3) Com aritmética de ponteiros podemos incrementar um endereço de memória.

Viu onde eu quero chegar? A gente pode usar a aritmética de ponteiros para acessar os endereços seguintes, assim:

Vemos que os elementos vão crescendo de 4 em 4 (pois são do tipo i n t ). Mas como já falamos, V e c é um ponteiro, então podemos usar o operador de desreferência ( * ) para obter o valor contido no endereço:

Sacou! Ah, não esqueça de colocar os parêntesis para garantir a precedência correta:

V e c   i n d e x                     e q u i v a l e n t e ⇔                   * ( V e c + i n d e x )

É importante você compreender essa equivalência, pois ela vai ser útil quando a gente falar de alocação dinâmica de memória! E no futuro, essa forma vai ser útil para desenvolver estruturas de dados e algoritmos de organização.

Criando um ponteiro para um vetor

Como dissemos, quando não usamos os colchetes o vetor se comporta como um ponteiro para a primeira posição. Mas apesar disso, as vezes pode ser útil criar um ponteiro que guarde o endereço de memória de um vetor, ou de algum elemento específico do vetor.

Para criar um ponteiro que aponte para um vetor fazemos da seguinte forma:

t i p o * n o m e _ p o n t e i r o = n o m e _ v e t o r

E para criar um ponteiro que aponta para um elemento qualquer do vetor fazemos da seguinte forma:

t i p o * n o m e _ p o n t e i r o = & n o m e _ v e t o r [ i n d i c e ]

Agora você está craque em ponteiros. Vamos ver uns exercícios de fixação.

Aritmética de ponteiros

Relacionamento entre vetores e ponteiros

Indexando vetores através da aritmética de ponteiros

Criando um ponteiro para um vetor

Exercícios Resolvidos

Exercício Resolvido #1

Qual o resultado do código abaixo? Explique cada linha.

Passo 1

Vamos lá galera!

Essa é uma questão pra gente interpretar o código! Então vamos entende-lo linha por linha.

Pra começar, da linha 01 a 05 é apenas a estrutura básica do C++, então vamos nos concentrar no restante do código.

Na linha 06 temos três declarações: primeiro uma variável inteira x inicializada com o 100 , segundo um ponteiro p , e terceiro é declarado um ponteiro duplo chamado p p

Na linha 07 fazemos o ponteiro p apontar para o endereço de memória da variável x .

E na linha 08 utilizamos o ponteiro duplo p p para apontar para o endereço de memória de do ponteiro p .

Por fim, vamos pra linha 10: onde utilizamos o comando c o u t para imprimir o resultado de * * p p . E basta você lembrar que quando usamos * * em um ponteiro duplo nós acessamos o valor contido na variável x .

Assim, concluímos que o resultado desse código é imprimir a expressão “Valor de pp: 100” na tela.

Resposta

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

Exercício Resolvido #2

Escreva um programa que contenha duas variáveis inteiras, e crie um ponteiro para cada variável. Leia essas variáveis do teclado. Em seguida, compare seus endereços e exiba o conteúdo do maior endereço.

Passo 1

E aí pessoal. Vamos lá resolver essa questão. Esse é um exercício bem direto, então vamos começar sem demora.

Primeiro de tudo vamos declarar as duas variáveis inteiras, vamos chama-las de A e B .

Perceba o seguinte, estamos declarando A e B como variáveis distintas, então elas sempre terão endereços de memória diferentes!

Agora vamos criar um ponteiro para cada uma das variáveis. Um ponteiro p A que aponta pro endereço de A , e um ponteiro p B que aponta para o endereço de B .

Passo 2

Certo! Agora vamos usar o comando c o u t para pedir que o usuário insira o valor das variáveis e com o c i n guardamos cada valor na variável adequada.

Passo 3

Agora vamos entrar no centro da questão: vamos comparar os endereços de memória.

Para comparar dois ponteiros não tem segredo, basta usar o operador de comparação adequado, que é o próprio “ > ”. Então, vamos comparar os endereços de A e de B e imprimir o valor contido no maior endereço. Para isso vai ser necessário utilizar uma expressão i f - e l s e .

S E   e n d e r e ç o   d e   A > e n d e r e ç o   d e   B   E N T Ã O   i m p r i m a   o   v a l o r S E   N A O   i m p r i m a   o   v a l o r   d e   B

Lembre-se, devido a forma como declaramos as variáveis A e B é impossível que elas tenham o mesmo local de memória, então não precisamos nos preocupar com esse caso.

O código fica da seguinte forma:

E assim finalizamos a questão.

Resposta

Exercício Resolvido #3

Suponha que os elementos do vetor v são do tipo i n t e cada i n t ocupa 8   b y t e s no seu computador. Se o endereço de v [ 0 ] é 55000 , qual o valor da expressão v   +   3

Passo 1

E aí pessoal, tudo bem? Vamos para mais uma questão sobre manipulação de ponteiros.

Pra resolver essa questão a gente precisa ter em mente duas coisas: a relação entre ponteiros e vetores e também o funcionamento da aritmética de ponteiros.

Precisamos lembrar que um vetor pode ser tratado como um ponteiro! Quando utilizamos um vetor sem os colchetes nós temos um ponteiro para a primeira posição do vetor. Seguindo essa lógica, quando fazemos uma soma do tipo v + 3 podemos tratar isso como uma aritmética de ponteiros.

Sobre a aritmética de ponteiros precisamos lembrar que: quando somamos um ponteiro com um inteiro, a soma é proporcional a quantidade de bytes do tipo do ponteiro. Então, no nosso caso, somar 1 ao ponteiro é equivalente a somar 8 no endereço de memória.

Passo 2

Certo, então agora vamos efetuar os nossos cálculos.

O endereço de memória de v [ 0 ] pode ser obtido simplesmente chamando o vetor sem os colchetes assim: v → 5500 . Porém, se a gente quiser obter este endereço de memória através de aritmética de ponteiros podemos expressar da seguinte maneira: v + 0 → 5500 .

De forma análoga, para obter o endereço de memória de v [ 1 ] podemos fazer v + 1 . Porém, ao somar 1 unidade ao ponteiro, nós adicionamos 8   b y t e s ao endereço de memória:

v + 1 → 55000 + 8 v + 1 → 55008

E podemos ampliar este raciocínio para todas as outras posições do vetor. Ao adicionar 2 unidades ao ponteiro, é equivalente a adicionar 8   b y t e s ao endereço de memória

v + 2 → 55000 + 16 v + 2 → 550 16

E por fim, adicionando 3 unidades ao ponteiro, o resultado é adicionar 24   b y t e s no endereço de memória.

v + 3 → 55000 + 24 v + 3 → 550 24

Resposta

O endereço de v + 3 é 55024

Exercício Resolvido #4

Observe a seguinte expressão de aritmética de ponteiros. Qual o resultado?

Passo 1

Vamos lá pessoal.

Basicamente, a questão quer que a gente analise a expressão da linha 10. Essa é uma expressão envolvendo ponteiros, ponteiros duplos e vetores. Então vamos tomar cuidado para não cometer nenhum erro.

Antes de começar, vamos dar uma revisada nas regras de precedência:

Note que os operadores de ponteiro * e & tem precedência bem alta. E vamos tomar cuidado para não confundir um operador de ponteiro com uma multiplicação.

Passo 2

Agora vamos pensar em pra onde cada ponteiro está apontando.

Sempre que a gente encontrar o vetor sem os colchetes v e c podemos considera-lo como um ponteiro para v e c [ 0 ] .

p é um ponteiro para a primeira posição do vetor. Ou seja, o ponteiro p aponta para v e c [ 0 ] .

Por fim, o ponteiro p p é um ponteiro duplo que aponta para o ponteiro p .

Passo 3

Com tudo estabelecido, vamos resolver a expressão. Pra começar vamos escrever a expressão e analisa-la.

* p + +   +   2   +   15   +   * v e c   +   1   +   * * p p   +   2

Vamos olhar primeiro pras expressões dentro dos parêntesis destacados abaixo:

Tanto p quanto v e c são ponteiros para a primeira posição do vetor. Na expressão laranja o ponteiro é incrementado com o operador + + passa a apontar para a segunda posição do vetor, em seguida é somado + 2 , o resultado aponta para a quarta posição do vetor. A expressão p + + + 2 é uma referência à v [ 3 ] . O operador * acessa o valor contido neste endereço que é igual à 12

Na expressão em vermelho, v e c é um ponteiro para a primeira posição do vetor. Ao adicionar + 1 somos enviados para a próxima posição. v e c   +   1 é uma referência a segunda posição do vetor. O operador * acessa o valor contido neste endereço que é igual à 6

Por sua vez, p p é um ponteiro duplo que aponta para p , que aponta para v [ 0 ] . Quando utilizamos dois operadores de desreferência em p p nós recebemos o valor contido em v 0 como resposta. Ou seja: * * p p ⇒ 3 , então a expressão em azul é:

Assim, a expressão é reduzida à:

Efetuando as somas temos:

38

Passo 4

Para garantir que nossa resposta está correta vamos executar o programa:

O que mostra que chegamos na resposta correta.

Resposta

38

Exercício Resolvido #5

Escreva um programa que declare um array de inteiros. Associe um ponteiro ao array. Agora, some mais um (+1) a cada posição do array usando o ponteiro (use *). Exiba os novos valores da array

Passo 1

Vamos nessa, galerinha. Essa questão quer que a gente exercite a indexação de vetores através de ponteiros. Então vamos precisar criar um ponteiro, um vetor e, para efetuar o loop, vai ser necessário uma variável de iteração.

Então vamos começar criando um vetor chamado v e c com 5 elementos inteiros. Você pode iniciar esse vetor com qualquer valor, eu vou usar múltiplos de 5 .

Em seguida vamos declarar um ponteiro chamado p V e c que aponta para v e c . Lembre-se que para declarar o ponteiro de um vetor o operador de referência (&) é opcional

Para criar o nosso loop precisamos de uma variável i inicializada com o valor 0.

Passo 2

Vamos criar um loop para o nosso vetor.

Para poder indexar um vetor vamos começar a contar a partir do i = 0 de um em um até i < 5 .

Agora vamos atualizar a nossa array. Para cada iteração precisamos acessar a posição p V e c + i , ou seja, quando i = 0 acessaremos a posição v e c [ 0 ] , quando i = 1 vamos acessar a posição v e c [ 1 ] e assim por diante. Para acessar o valor contido em cada endereço de memória vamos utilizar o operador de desreferenciação ( * ). E para adicionar 1 ao valor vamos usar o operador + = .

* p V e c + i + = 1

Em código fazemos:

Em seguida vamos utilizar o c o u t para imprimir o valor atualizado da posição:

E é isso, a questão está concluída. Até logo, pessoal.

Resposta

Exercício Resolvido #6

Seja o seguinte trecho de programa:

Qual será o valor das seguintes expressões:

  1. p = = & i ;
  2. 3 *   * p   /     * q + 7

Passo 1

E aí, pessoal!

Vamos começar a questão analisando o código do enunciado.

Na primeira linha são declaras duas variáveis inteiras i e j .

Em seguida são declarados dois ponteiros para inteiro p e q

E fazemos esses ponteiros apontarem para as variáveis:

Assim o conteúdo do ponteiro p é o endereço de memória de i

Passo 2

Agora vamos analisar a expressão: p = = & i ; . Aqui temos uma comparação “o conteúdo armazenado em p é igual ao endereço de memória de i ”.

Lembre-se que o ponteiro p armazena o endereço de i , então a comparação é verdadeira!

No C++ quando uma comparação é verdadeira o resultado é 1 que representa o valor lógico t r u e

  1. 1

Resposta

1

Exercício Resolvido #7

Seja o seguinte trecho de programa:

Qual será o valor da seguinte expressão:

3 *   * p   /     ( * q ) + 7

Passo 1

E aí, pessoal!

Vamos começar a questão analisando o código do enunciado.

Na primeira linha são declaras duas variáveis inteiras i e j .

Em seguida são declarados dois ponteiros para inteiro p e q

E fazemos esses ponteiros apontarem para as variáveis:

Assim o conteúdo do ponteiro p é o endereço de memória de i

Passo 2

A expressão que vamos analisar é 3 * * p /   ( * q ) + 7 .   Essa é uma expressão aritmética envolvendo ponteiros.

A maior dificuldade está na presença do símbolo *   asterisco várias vezes ao longo da expressão, em alguns momentos ele representa o operador de desreferência, e em outros momentos representa o operador de multiplicação.

A dica pra não confundir é lembrar que a precedência do operador de desreferência é maior do que a precedência do operador de multiplicação. Então, se o * estiver ao lado de um ponteiro ele vai ser considerado como um operador de desreferência.

Vamos dar uma olhada nas regras de precedência.

Analisando a tabela de precedência, primeiro vamos calcular as operações de ponteiro, depois as operações aritméticas

Agora vamos analisar a expressão:

3 *   * p / ( * q ) + 7

Aqui podemos ver duas operações de ponteiros:

Um * ao lado um ponteiro retorna o valor contido no endereço apontado. O ponteiro p aponta para i = 3 , então * p = 3 . Já q aponta para j = 5 , então a operação * q é igual a 5 :

3 *   3 / ( 5 ) + 7

E agora vamos resolver a multiplicação:

9 / ( 5 ) + 7

Agora temos essa divisão entre dois inteiros. Lembre-se que no C++ divisão entre dois inteiros ignora a parte decimal, então 9 / 5 é igual a 1 , e não 1.5 ! Assim temos:

1 + 7

Logo o resultado final é:

8

Resposta

8

Exercício Resolvido #8

Faça um programa que declare uma array de 10 elementos inteiros, e receba os valores do usuário. Depois exiba a posição, endereço de memória e valor contido em cada elemento da array.

Passo 1

E aí, galerinha!

Nessa questão vamos precisar criar uma array, receber os valores da array e imprimi-los. O detalhe é que também vamos imprimir os endereços de memória de cada posição da array.

Para acessar o endereço de memória de uma array, é só a gente usar o vetor sem os colchetes. Um vetor sem colchete funciona como endereço de memória para a primeira posição. E se a gente adicionar um, apontaremos para a segunda posição. Se adicionar dois, vamos apontar para a segunda posição, e assim por diante...

v   +   0 → e n d e r e ç o   p a r a   p r i m e i r a   a   p o s i ç ã o   v 0 v + 1 → e n d e r e ç o   p a r a   s e g u n d a   a   p o s i ç ã o   v 1 v + 2 → e n d e r e ç o   p a r a   t e r c e i r a   a   p o s i ç ã o   v 2                                                                         ⋮ v + n → a r r a y   p a r a   a   p o s i ç ã o   v [ n ]

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

Passo 2

A primeira coisa a fazer é a gente criar uma array de inteiros. Então dentro da m a i n ( ) vamos declarar o vetor v com dimensão 10

Para preencher esse array vamos usar um loop f o r que vai executar dez iterações, começando em 0 e indo até 10 de um em um.

Em cada rodada vamos pedir pro usuário inserir um valor, e armazenamos o valor inserido na posição i do vetor.

Passo 3

Show de bola. Agora vamos pra parte mais importante da questão.

O que precisamos fazer é imprimir o índice do vetor, o endereço daquela posição, e o valor contido nessa posição. Então, pra deixar nosso programa bonitão, vamos imprimir um cabeçalho:

Beleza, agora vamos ao que interessa.

A gente precisa imprimir o índice, endereço e valor de várias posições de um vetor, então vamos usar um loop f o r para realizar uma iteração sobre esse vetor.

Dentro do loop, primeiro vamos imprimir a posição i .

Em seguida, vamos imprimir o endereço de memória. O endereço de memória da posição i pode ser obtido usando v + i .

E também vamos exibir o conteúdo do endereço v + i . Para isso é só a gente usar o operador de desreferência * , junto com os parêntesis: * ( v + 1 ) e obteremos o conteúdo armazenado no endereço.

E prontinho! Podemos terminar o programa.

Passo 4

E agora, vamos rodar e ver se funciona.

E deu tudo certo! Até mais!

Resposta