Manipulando ponteiros: ponteiros duplos, aritmética e vetores
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:
Veja no código:
A grande novidade está na linha 08, onde a gente declara o nosso ponteiro duplo , que é iniciado com o endereço de memória do ponteiro .
Utilizando a operação podemos acessar o valor contido na variável . E usando a operação acessamos o valor contido na variável .
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 aponta para uma variável do tipo inteiro que ocupa . Em seguida somamos o ponteiro com uma unidade, e o resultado aumentou 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 o avanço seria de . Se fosse do tipo char o avanço seria de .
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:
É a multiplicação entre o valor contido no ponteiro vezes o valor contido na variável .
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
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 , , e ?
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: .
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 em (pois são do tipo ). Mas como já falamos, é 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:
É 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:
E para criar um ponteiro que aponta para um elemento qualquer do vetor fazemos da seguinte forma:
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
Adaptado de UFJF – Lista de Exercício Ponteiros – Exercício 10
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 inicializada com o , segundo um ponteiro , e terceiro é declarado um ponteiro duplo chamado
Na linha 07 fazemos o ponteiro apontar para o endereço de memória da variável .
E na linha 08 utilizamos o ponteiro duplo para apontar para o endereço de memória de do ponteiro .
Por fim, vamos pra linha 10: onde utilizamos o comando para imprimir o resultado de . E basta você lembrar que quando usamos em um ponteiro duplo nós acessamos o valor contido na variável .
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
Adaptado de UFU – Exercícios: Ponteiros – Exercício 03
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 e .
Perceba o seguinte, estamos declarando e 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 que aponta pro endereço de , e um ponteiro que aponta para o endereço de .
Passo 2
Certo! Agora vamos usar o comando para pedir que o usuário insira o valor das variáveis e com o 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 e de e imprimir o valor contido no maior endereço. Para isso vai ser necessário utilizar uma expressão .
Lembre-se, devido a forma como declaramos as variáveis e é 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
UFJF – Lista de Exercícios Ponteiros – Questão 03
Suponha que os elementos do vetor são do tipo e cada ocupa no seu computador. Se o endereço de é , qual o valor da expressão
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 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 ao ponteiro é equivalente a somar 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 pode ser obtido simplesmente chamando o vetor sem os colchetes assim: . 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: .
De forma análoga, para obter o endereço de memória de podemos fazer . Porém, ao somar unidade ao ponteiro, nós adicionamos ao endereço de memória:
E podemos ampliar este raciocínio para todas as outras posições do vetor. Ao adicionar unidades ao ponteiro, é equivalente a adicionar ao endereço de memória
E por fim, adicionando unidades ao ponteiro, o resultado é adicionar no endereço de memória.
Resposta
O endereço de é
Exercício Resolvido #4
Elaboração Própria
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 podemos considera-lo como um ponteiro para .
Já é um ponteiro para a primeira posição do vetor. Ou seja, o ponteiro aponta para .
Por fim, o ponteiro é um ponteiro duplo que aponta para o ponteiro .
Passo 3
Com tudo estabelecido, vamos resolver a expressão. Pra começar vamos escrever a expressão e analisa-la.
Vamos olhar primeiro pras expressões dentro dos parêntesis destacados abaixo:
Tanto quanto 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 , o resultado aponta para a quarta posição do vetor. A expressão é uma referência à . O operador acessa o valor contido neste endereço que é igual à
Na expressão em vermelho, é um ponteiro para a primeira posição do vetor. Ao adicionar somos enviados para a próxima posição. é uma referência a segunda posição do vetor. O operador acessa o valor contido neste endereço que é igual à
Por sua vez, é um ponteiro duplo que aponta para , que aponta para . Quando utilizamos dois operadores de desreferência em nós recebemos o valor contido em como resposta. Ou seja: , então a expressão em azul é:
Assim, a expressão é reduzida à:
Efetuando as somas temos:
Passo 4
Para garantir que nossa resposta está correta vamos executar o programa:
O que mostra que chegamos na resposta correta.
Resposta
Exercício Resolvido #5
UFU - Exercícios: Ponteiros - Exercício 21
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 com elementos inteiros. Você pode iniciar esse vetor com qualquer valor, eu vou usar múltiplos de .
Em seguida vamos declarar um ponteiro chamado que aponta para . 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 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 de um em um até .
Agora vamos atualizar a nossa array. Para cada iteração precisamos acessar a posição , ou seja, quando acessaremos a posição , quando vamos acessar a posição 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 ao valor vamos usar o operador .
Em código fazemos:
Em seguida vamos utilizar o para imprimir o valor atualizado da posição:
E é isso, a questão está concluída. Até logo, pessoal.
Resposta
Exercício Resolvido #6
Adaptado UFU – DAS5102 – Fundamentos da Estrutura da Informação – Ponteiros e Arrays – Questão 2
Seja o seguinte trecho de programa:
Qual será o valor das seguintes expressões:
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 e .
Em seguida são declarados dois ponteiros para inteiro e
E fazemos esses ponteiros apontarem para as variáveis:
Assim o conteúdo do ponteiro é o endereço de memória de
Passo 2
Agora vamos analisar a expressão: . Aqui temos uma comparação “o conteúdo armazenado em é igual ao endereço de memória de ”.
Lembre-se que o ponteiro armazena o endereço de , então a comparação é verdadeira!
No C++ quando uma comparação é verdadeira o resultado é que representa o valor lógico
- 1
Resposta
Exercício Resolvido #7
Adaptado UFU – DAS5102 – Fundamentos da Estrutura da Informação – Ponteiros e Arrays – Questão 2
Seja o seguinte trecho de programa:
Qual será o valor da seguinte expressão:
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 e .
Em seguida são declarados dois ponteiros para inteiro e
E fazemos esses ponteiros apontarem para as variáveis:
Assim o conteúdo do ponteiro é o endereço de memória de
Passo 2
A expressão que vamos analisar é 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:
Aqui podemos ver duas operações de ponteiros:
Um ao lado um ponteiro retorna o valor contido no endereço apontado. O ponteiro aponta para , então . Já aponta para , então a operação é igual a :
E agora vamos resolver a multiplicação:
Agora temos essa divisão entre dois inteiros. Lembre-se que no C++ divisão entre dois inteiros ignora a parte decimal, então é igual a , e não ! Assim temos:
Logo o resultado final é:
Resposta
Exercício Resolvido #8
Elaboração própria
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...
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 vamos declarar o vetor com dimensão
Para preencher esse array vamos usar um loop 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 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 para realizar uma iteração sobre esse vetor.
Dentro do loop, primeiro vamos imprimir a posição .
Em seguida, vamos imprimir o endereço de memória. O endereço de memória da posição pode ser obtido usando .
E também vamos exibir o conteúdo do endereço . Para isso é só a gente usar o operador de desreferência , junto com os parêntesis: 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!