Manipulando ponteiros
Teoria
Introdução
Fala aí, aqui é o Guga do Responde Aí! Tudo bem com você?
No tópico anterior nós vimos o conceito de apontadores e aprendemos como declará-los na linguagem C, agora nós iremos conversar um pouco sobre a manipulação de ponteiros em alguns casos comuns em que eles são utilizados.
Obtendo o dado de um ponteiro
O caso mais básico e, que iremos ver primeiro, é como obter o dado de um ponteiro, mas esse dado não é o endereço em sí que ele armazena e sim o dado alocado nesse endereço de memória.
A imagem a seguir ilustra a declaração de um ponteiro ptrX:
O que nós faríamos se quiséssemos imprimir o dado alocado no endereço de memória para o qual o ponteiro ptrX aponta?
A solução é muito simples, basta observarmos essa imagem:
Nela podemos ver que só precisamos utilizar o operador * (ele é utilizado para muitas coisas mesmo) antes do ponteiro. Isso fará com que o valor que é acessado seja o dado armazenado no endereço de memória que é apontado pelo endereço que está armazenado no ponteiro. Ou seja, nesse caso ao invés de ser impresso o endereço na memória em que X está armazenado, será impresso o valor 3.1416 que é o dado armazenado na memória exatamente no endereço para o qual ptrX aponta.
Ponteiros como parâmetro de funções
Ponteiros como parâmetros de funções são muito comuns, pois como se trata de endereços, criamos uma forma de alterar o dado presente nesse endereço mesmo quando a alteração é feita dentro do escopo de uma função.
Mas por que isso acontece?
Pois a variável de cópia criada pela função em seu escopo local terá o mesmo endereço de memória que a variável original. Ou seja, alterações tanto no programa principal, quanto em uma função estão alterando o mesmo endereço de memória, consequentemente a mesma variável.
Estranho, né? Olha como isso funciona em um código de verdade:
Nesse código, o endereço da variável X foi passado como parâmetro da função incrementa (linha 09), essa por sua vez incrementou o valor armazenado nesse endereço de memória em uma unidade. Porém, apesar de tal alteração ter sido feita em escopo local (dentro da função incrementa), por se tratar de uma manipulação por endereço de memória, a impressão feita na linha 10 (após a função ter sido invocada) teve o seguinte resultado:
Sendo assim, podemos concluir que o valor da variável original também foi alterado.
Então lembre-se, sempre que se uma manipulação é feita através de um endereço de memória, todas as instâncias que compartilham essa memória são alteradas. Nesse exemplo, tanto a variável no escopo principal, quanto a variável no escopo local tiveram seus valores alterados.
Presta muita atenção porque isso cai como pegadinha sempre nas provas de programação, hein!?
Ponteiros duplos
Ponteiros duplos costumam confundir muito a cabeça, basicamente são ponteiros que apontam para outros ponteiros.
Para entendermos melhor, vamos observar essa imagem:
Nela nós temos um ponteiro pt2 que aponta para o endereço de outro ponteiro, o pt1, que por sua vez aponta para o endereço de uma variável V, essa que armazena o valor 100.
Em termos de código, isso acontece da seguinte forma:
A grande novidade está na declaração de um ponteiro duplo (linha 08 do código acima), em que basicamente o operador * é utilizado 2 vezes para sinalizar que aquela variável irá apontar para um endereço de memória que contém um ponteiro que aponta para outro endereço de memória. Além disso, esse ponteiro duplo é inicializado com o endereço de memória de pt1 (ponteiro que aponta para a variável V).
E se nós quiséssemos imprimir o valor da variável V a partir do ponteiro pt2? A lógica é a mesma, nós só precisamos nos atentar a sintaxe dos acessos (primeira coisa vista nesse tópico).
Para entendermos melhor, vamos observar a imagem a seguir:
Basicamente essa sintaxe acessa o valor apontado por pt2 (que é pt1), em seguida acessa o valor apontado por pt2 (que é a variável V). Para isso, é necessário a utilização do operador * duas vezes, que corresponde aos dois acessos descritos anteriormente.
Agora que nós já sabemos o básico sobre manipulação de vetores, está na hora de aprendermos ainda mais com os exercícios de fixação!
Ponteiros como parâmetro de funções
Ponteiros duplos
Exercícios Resolvidos
Exercício Resolvido #1
Elaboração própria.
Dado o seguinte trecho de código:
Crie um ponteiro chamado e inicialize o mesmo com o endereço da variável . Após isso, faça a impressão do valor armazenado por usando somente o ponteiro .
Passo 1
A primeira coisa que nós temos que fazer é adicionar a esse código a linha responsável por declarar e inicializar o ponteiro .
Passo 2
Após isso nós simplesmente temos que realizar a impressão do valor armazenado por através do ponteiro anteriormente criado.
Para isso nós devemos utilizar a função printf quase da mesma forma com que utilizamos ela para a impressão de variáveis comuns a grande diferença está na troca da variável pelo ponteiro antecedido do operador *. Essa troca sinaliza para o programa que queremos imprimir o dado armazenado na variável “apontada” pelo endereço armazenado por esse ponteiro (que nesse caso é ).
Resposta
Dessa forma, teríamos a seguinte impressão na tela: “O valor de é: 10” seguido de uma quebra de linha.
Exercício Resolvido #2
Universidade Federal de Juiz de Fora – UFJF, Lista de exercícios sobre Ponteiros, 2012, Jairo Francisco de Souza, pp. 01 – Exercício 01.
Observe o seguinte trecho de código:
Quais serão os valores de , e ao final de sua execução?
Passo 1
A melhor forma de resolvermos esse exercício é executarmos o trecho de código dado passo a passo.
Começando pela linha 03, conseguimos ver facilmente a declaração de duas variáveis inteiras e e de um ponteiro, também inteiro, chamado .
Passo 2
A partir da linha 05 temos diversas manipulações entre essas declarações, a primeira delas é a atribuição do endereço da variável ao ponteiro (linha 05), em seguida temos a atribuição do valor apontado por a (linha 06) e a atribuição do valor 4 novamente a (linha 07) que sobrescreve a atribuição realizada na linha 06.
Logo, até agora temos armazenando o valor 4 e apontando para a variável .
Passo 3
Na linha 08 nós conseguimos ver o incremento de uma unidade na variável apontada por , como essa variável é (que foi inicializada com 0 e não foi manipulada até essa linha) temos que agora ela armazena o valor 1.
Passo 4
Em seguida (linha 09) temos o decremento de uma unidade do valor armazenado por , que agora passa a armazenar o valor 3.
Passo 5
Por fim, temos o incremento de na variável apontada por , que é (linha 10). Como armazena o valor 3, (que armazena 1) agora passa a armazenar 4.
Resposta
Sendo assim, terminamos esse trecho de código com a variável armazenando o valor 3, o valor 4 e o endereço de memória de .
Exercício Resolvido #3
Universidade de São Paulo – USP, Linguagens de Programação e Aplicações, 2009/2, Paulo Sérgio Souza, pp. 01 – Exercício 05.
Observe o seguinte programa:
Qual será a saída impressa no final da execução desse código?
Passo 1
Vamos começar do começo, na linha 05 do programa nós conseguimos identificar a declaração de duas variáveis inteiras e inicializadas com o valor 0, além de dois ponteiros para inteiro e .
Passo 2
Sabendo quais elementos serão manipulados pelo programa podemos agora analisar os comandos que se iniciam na linha 07.
Começando pelas atribuições, nós podemos ver a variável recebendo o valor 4 e a variável o valor 3 (linhas 07 e 08).
Passo 3
Em relação aos ponteiros, nós conseguimos identificar a atribuição do endereço da variável ao ponteiro (linha 09) e em seguida a atribuição do endereço armazenado pelo ponteiro ao ponteiro (linha 10). Sendo assim, podemos concluir que temos e apontando para a mesma variável, que é .
Passo 4
Na linha 11, nós temos uma atribuição da soma entre o valor armazenado pela variável apontada por e 3 a variável apontada por . Reduzindo um pouco isso, podemos dizer que a variável (variável apontada por ) está recebendo a soma entre o seu valor atual (já que o ponteiro também aponta para ) e 3.
Dessa forma, podemos concluir que a variável agora armazena o valor 7.
Passo 5
Já na linha seguinte (linha 12), temos a atribuição de 3 vezes o valor da variável apontada por a . Como a variável apontada por é e armazena o valor 7, podemos concluir que passa agora a armazenar o valor 21.
Passo 6
Em seguida (linha 13) temos o incremento de uma unidade sendo feito na variável apontada por seguida da atribuição do endereço de ao ponteiro . Como aponta para que armazena o valor 7, temos agora armazenando o valor 8 e agora apontando para a variável .
Resposta
Depois de toda essa análise podemos afirmar que a impressão realizada na linha 16 será dos valores 21 e 8 (já que aponta para a variável e para a variável ) seguida de uma quebra de linha e da impressão dos valores 8 e 21 que são os valores armazenados pelas variáveis e respectivamente.
Exercício Resolvido #4
Universidade de São Paulo – USP, Linguagens de Programação e Aplicações, 2009/2, Paulo Sérgio Souza, pp. 01 – Exercício 04.
Observe o seguinte programa:
E a seguinte definição da função :
Quais serão os valores de e impressos ao fim da execução desse programa dado que os valores inseridos pelo usuário como entrada desse programa serão 3 e 4 respectivamente?
Passo 1
Para entendermos o que será impresso por esse programa temos primeiro que entender o funcionamento da função .
Analisando os seus parâmetros conseguimos identificar que ela espera como entrada dois ponteiros para inteiro e . Em seguida, atribui a o endereço armazenado por (linha 03). Ou seja, logo no início da função temos tanto quanto apontando para o mesmo endereço.
Como o enunciado nos diz que os dados inserido pelo usuário são 3 e 4 e esses são passados como parâmetros dessa função através dos endereços de e , podemos dizer que tanto quanto apontam para a variável .
Passo 2
Na linha seguinte (linha 04 da função) temos a atribuição da multiplicação entre o valor armazenado pela variável apontada por e o valor da variável apontada por a variável apontada por . Como ambos os ponteiros apontam para mesma variável, que é que armazena o valor 4, isso não passa de uma atribuição a de .
Passo 3
Continuando, na linha 5 da função, temos a atribuição da soma entre o valor da variável apontada por e 2 a variável apontada por . Como o ponteiro aponta para a variável (que armazena o valor 16), isso não passa de uma atribuição a de .
Resposta
Por fim, na linha 14 do programa, temos a impressão dos valores de e . Como dentro da função ambos os ponteiros passaram a apontar para , não houve manipulações em que por esse motivo manteve o seu valor original de 3. Já em relação a , o resultado obtido após todas as manipulações dentro da função foi 18 e esse que será impresso na tela.
Sendo assim, a impressão será , .
Exercício Resolvido #5
Elaboração própria.
Observe o seguinte programa:
Defina a função de impressão para que o programa funcione corretamente e seja impresso na tela o número 5 armazenado pela variável .
Passo 1
Analisando o cabeçalho do programa (mais precisamente a linha 03) nós conseguimos ver com clareza o escopo da função :
Onde conseguimos perceber que se trata de uma função sem retorno (por isso o tipo void) e que espera como parâmetro um ponteiro triplo para inteiro.
Passo 2
Tendo essa visão geral sobre o programa, conseguimos perceber que a única coisa que nós temos que fazer é imprimir o conteúdo de uma variável a partir de um ponteiro triplo.
Mamão com açúcar! A única coisa que nós temos que fazer é colocar um operador * para cada “instância do ponteiro”. Então pensa comigo, um ponteiro triplo nada mais é do que um ponteiro que armazena o endereço de outro ponteiro, que por sua vez, armazena o endereço de outro ponteiro que armazena o endereço de uma variável.
Quando nós colocamos três operadores * antes de um ponteiro triplo em uma impressão estamos falando para o programa o seguinte: eu quero o conteúdo desse ponteiro triplo (que é um ponteiro duplo), o conteúdo desse ponteiro duplo (que é um ponteiro normal) e o conteúdo desse ponteiro normal (que é o dado de uma variável).
Resposta
Dessa forma, recebendo como parâmetro o endereço do ponteiro duplo (formando o ponteiro triplo dentro da função ) a função iria ser capaz de imprimir o valor 5 armazenado pela variável .
Exercício Resolvido #6
Elaboração própria.
Observe a seguinte função:
Explique o seu funcionamento passo a passo e identifique a sua funcionalidade.
Passo 1
A primeira coisa que nós temos que fazer para resolvermos esse exercício é analisarmos o escopo dessa função. Sendo assim, se trata de uma função sem retorno de valor que espera como parâmetros dois ponteiros para inteiro e .
Passo 2
A sua execução é bastante simples, consiste em um comando condicional que verifica se o valor armazenado na variável apontada por é maior que o valor armazenado na variável apontada por . Caso seja, é impresso na tela o valor da variável apontada por , caso contrário o valor da variável apontada por .
Resposta
Sendo assim, a função se trata de uma função que recebe como parâmetros dois ponteiros para inteiros e imprime o maior valor entre eles.
Exercício Resolvido #7
Elaboração própria.
Observe a seguinte função:
Explique o seu funcionamento passo a passo e identifique a sua funcionalidade.
Passo 1
A primeira coisa que nós iremos fazer para resolvermos esse exercício é analisarmos o escopo da função . Sendo assim, podemos concluir que ela se trata de uma função sem retorno de valor (do tipo void) que espera como parâmetros dois ponteiros para inteiros e .
Passo 2
Sobre o seu funcionamento, a primeira ação tomada pela função é declarar uma variável inteira chamada e atribuir a ela o valor armazenado na variável apontada pelo ponteiro . Em seguida, a variável apontada por recebe o valor armazenado na variável apontada pelo ponteiro e a função é finalizada com a variável apontada por recebendo o valor armazenado em .
Resposta
Com base nisso podemos concluir que a função se trata de uma função que realiza a troca dos valores entre duas variáveis, isso se dá através da entrada de dois ponteiros como parâmetros da função que apontam para tais variáveis.
Exercício Resolvido #8
Elaboração própria.
Observe o seguinte programa:
Ao final de sua execução o que terá sido impresso na tela?
Passo 1
Para ficar tudo certinho, vamos separar cada um dos 3 parâmetros da impressão em diferentes passos.
Começando pelo primeiro, que é igualzinho a isso aqui:
Podemos dizer que se trata da impressão da soma entre o valor armazenado pela variável apontada pelo ponteiro e 2. Como essa variável é , que armazena o valor 5, o que será impresso primeiro na tela é o número 7.
Passo 2
O segundo parâmetro de impressão é igual a isso aqui:
Aqui nós temos que prestar muita atenção com o operador & antes do , isso representa o endereço de (que já é um ponteiro), ou seja, temos um ponteiro duplo. Sendo assim, quando colocamos os dois operadores antes do operador & estamos dizendo para o programa imprimir o conteúdo do endereço que aponta para (que é o próprio ), a variável que aponta (que é a variável ). No fim, nós teremos apenas a impressão do número 5.
Passo 3
Por fim, o terceiro e último parâmetro é idêntico a isso aqui:
Que simplesmente é a multiplicação entre 3 e o conteúdo armazenado na variável apontada por . Como essa variável é , que armazena o valor 5, o que será impresso na tela é o número 15.
Resposta
Ei, a resposta está no passo a passo :)