Eu estava seguindo este tutorial sobre como funciona um ponteiro para um ponteiro .
Deixe-me citar a passagem relevante:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
Agora podemos definir
int **ipp = &ip1;
e
ipp
pontos para osip1
quais apontai
.*ipp
éip1
, e**ipp
éi
ou 5. Podemos ilustrar a situação, com nossa notação familiar de caixa e flecha, assim:Se então dizemos
*ipp = ip2;
alteramos o ponteiro apontado por
ipp
(isto é,ip1
) para conter uma cópia deip2
, para que (ip1
) agora aponte paraj
:
Minha pergunta é: por que, na segunda foto, ipp
ainda está apontando, ip1
mas não ip2
?
ipp
quando defini-lo, assim que sua pergunta é completo ;-)ipp
apontar paraip1
isso*ipp = ip2
é o mesmo queip1 = ip2
int **ipp
é muito menos intuitivo do queint** ipp
, especialmente quando**ipp
significa algo completamente diferente fora das declarações de tipo.int **ipp
parece bastante intuitivo para mim. Significa, estou fazendo**ipp
umint
. Que é verdade.ipp
éint**
, por isso basta escreverint**
em vez de mágica "imp
dereference é um int" entendimentos .Respostas:
Esqueça por um segundo a analogia do apontador. O que um ponteiro realmente contém é um endereço de memória. O
&
é o operador "endereço de" - isto é, retorna o endereço na memória de um objeto. O*
operador fornece o objeto ao qual um ponteiro se refere, ou seja, se um ponteiro contém um endereço, ele retorna o objeto nesse endereço de memória. Portanto, quando você faz*ipp = ip2
, o que você está fazendo é*ipp
obter o objeto no endereço emipp
que estáip1
e depois atribuir aoip1
valor armazenado emip2
, que é o endereço dej
.Simplesmente
&
-> Endereço de*
-> Valor emfonte
Porque você alterou o valor apontado por
ipp
não o valor deipp
. Portanto,ipp
ainda aponta paraip1
(o valor deipp
),ip1
o valor de agora é igual aoip2
valor de, portanto, ambos apontam paraj
.Este:
é o mesmo que:
fonte
int *ip1 = &i
e*ipp = ip2;
, ou seja, se você remover oint
da primeira instrução, as atribuições parecerão muito semelhantes, mas isso*
está fazendo algo muito diferente nos dois casos.Como a maioria das perguntas para iniciantes na tag C, essa pergunta pode ser respondida voltando aos primeiros princípios:
&
operador transforma uma variável em um ponteiro.*
operador transforma um ponteiro em uma variável.(Tecnicamente, devo dizer "lvalue" em vez de "variável", mas acho que é mais claro descrever locais de armazenamento mutáveis como "variáveis".)
Então, temos variáveis:
Variável
ip1
contém um ponteiro. O&
operador se transformai
em um ponteiro e esse valor de ponteiro é atribuído aip1
. Entãoip1
contém um ponteiro parai
.Variável
ip2
contém um ponteiro. O&
operador se transformaj
em um ponteiro e esse ponteiro é atribuído aip2
. Entãoip2
contém um ponteiro paraj
.Variável
ipp
contém um ponteiro. O&
operador transforma variávelip1
em um ponteiro e esse valor de ponteiro é atribuído aipp
. Entãoipp
contém um ponteiro paraip1
.Vamos resumir a história até agora:
i
contém 5j
contém 6ip1
contém "ponteiro parai
"ip2
contém "ponteiro paraj
"ipp
contém "ponteiro paraip1
"Agora dizemos
O
*
operador transforma um ponteiro de volta em uma variável. Nós buscamos o valor deipp
, que é "ponteiroip1
e o transformamos em uma variável. Qual variável? Éip1
claro!Portanto, esta é simplesmente outra maneira de dizer
Então, buscamos o valor de
ip2
. O que é isso? "ponteiro paraj
". Atribuímos esse valor ao ponteiroip1
,ip1
agora é "ponteiro paraj
"Mudamos apenas uma coisa: o valor de
ip1
:i
contém 5j
contém 6ip1
contém "ponteiro paraj
"ip2
contém "ponteiro paraj
"ipp
contém "ponteiro paraip1
"Uma variável muda quando você atribui a ela. Conte as tarefas; não pode haver mais alterações nas variáveis do que atribuições! Você começa por atribuir a
i
,j
,ip1
,ip2
eipp
. Você então atribui a*ipp
, o que, como vimos, significa o mesmo que "atribuir aip1
". Como você não atribuiuipp
uma segunda vez, isso não mudou!Se você quiser mudar
ipp
, precisará atribuir aipp
:por exemplo.
fonte
espero que este pedaço de código possa ajudar.
produz:
fonte
Minha opinião pessoal é que as figuras com setas apontando dessa maneira ou dificultam a compreensão dos ponteiros. Faz com que pareçam algumas entidades abstratas e misteriosas. Eles não são.
Como tudo no seu computador, ponteiros são números . O nome "ponteiro" é apenas uma maneira elegante de dizer "uma variável que contém um endereço".
Portanto, deixe-me agitar as coisas explicando como um computador realmente funciona.
Temos um
int
, ele tem o nomei
e o valor 5. Isso é armazenado na memória. Como tudo armazenado na memória, ele precisa de um endereço ou não poderíamos encontrá-lo. Digamos quei
termina no endereço 0x12345678 e seu amigoj
com o valor 6 acaba logo após ele. Supondo que uma CPU de 32 bits em que int seja 4 bytes e ponteiros sejam 4 bytes, as variáveis são armazenadas na memória física como esta:Agora queremos apontar para essas variáveis. Criamos um ponteiro para int
int* ip1
, e umint* ip2
. Como tudo no computador, essas variáveis de ponteiro também são alocadas em algum lugar da memória. Vamos supor que eles acabem nos próximos endereços adjacentes na memória, imediatamente apósj
. Definimos os ponteiros para conter os endereços das variáveis alocadas anteriormente:ip1=&i;
("copie o endereço de i para ip1") eip2=&j
. O que acontece nas entrelinhas é:Então, o que obtivemos foram apenas alguns pedaços de 4 bytes de memória contendo números. Não há flechas místicas ou mágicas em nenhum lugar à vista.
De fato, apenas olhando para um despejo de memória, não podemos dizer se o endereço 0x12345680 contém um
int
ouint*
. A diferença é como o nosso programa escolhe usar o conteúdo armazenado neste endereço. (A tarefa do nosso programa é realmente apenas dizer à CPU o que fazer com esses números.)Então adicionamos mais um nível de indireção com
int** ipp = &ip1;
. Novamente, temos apenas um pedaço de memória:O padrão parece familiar. Ainda outro pedaço de 4 bytes contendo um número.
Agora, se tivéssemos um despejo de memória da pequena RAM fictícia acima, poderíamos verificar manualmente onde esses ponteiros apontam. Examinamos o que está armazenado no endereço da
ipp
variável e encontramos o conteúdo 0x12345680. Qual é, obviamente, o endereço ondeip1
está armazenado. Podemos ir para esse endereço, verificar o conteúdo e encontrar o endereço dei
; finalmente, podemos ir para esse endereço e encontrar o número 5.Portanto, se pegarmos o conteúdo de ipp,
*ipp
obteremos o endereço da variável ponteiroip1
. Ao escrever*ipp=ip2
copiamos ip2 para ip1, é equivalente aip1=ip2
. Em ambos os casos, teríamos(Estes exemplos foram dados para uma grande CPU endian)
fonte
location, value, variable
onde estava o local1,2,3,4,5
e o valorA,1,B,C,3
, a ideia correspondente de ponteiros poderia ser explicada facilmente sem o uso de setas, que são inerentemente confusas. Qualquer que seja a implementação escolhida, existe um valor em algum local, e isso é parte do quebra-cabeça que fica ofuscado ao modelar com setas.&
operador de uma variável fornece uma moeda que representa essa variável. O*
operador nessa moeda devolve a variável. Não são necessárias flechas!Observe as atribuições:
resultados
ipp
para apontarip1
.Portanto, para
ipp
apontarip2
, devemos mudar da mesma maneira,o que claramente não estamos fazendo. Em vez disso, estamos alterando o valor no endereço indicado por
ipp
.Fazendo o seguinte
estamos apenas substituindo o valor armazenado
ip1
.ipp = &ip1
, Meios*ipp = ip1 = &i
,agora,
*ipp = ip2 = &j
.Então,
*ipp = ip2
é essencialmente o mesmo queip1 = ip2
.fonte
Nenhuma atribuição posterior alterou o valor de
ipp
. É por isso que ainda apontaip1
.O que você faz com
*ipp
, isto é, comip1
, não muda o fato de queipp
aponta paraip1
.fonte
você colocou boas fotos, vou tentar fazer uma bela arte ascii:
Como @ Robert-S-Barnes disse em sua resposta: esqueça os ponteiros e o que aponta para o que, mas pense em termos de memória. Basicamente, um
int*
significa que ele contém o endereço de uma variável e umint**
contém o endereço de uma variável que contém o endereço de uma variável. Em seguida, você pode usar a álgebra do ponteiro para acessar os valores ou os endereços:&foo
meiosaddress of foo
e*foo
meiosvalue of the address contained in foo
.Portanto, como os ponteiros tratam da memória, a melhor maneira de tornar isso "tangível" é mostrar o que a álgebra dos ponteiros faz com a memória.
Então, aqui está a memória do seu programa (simplificada para o propósito do exemplo):
quando você faz seu código inicial:
aqui está como sua memória se parece:
lá você pode ver
ip1
eip2
obter os endereços dei
ej
eipp
ainda não existe. Não esqueça que endereços são simplesmente números inteiros armazenados com um tipo especial.Então você declara e define
ipp
como:então aqui está sua memória:
e então, você está alterando o valor apontado pelo endereço armazenado
ipp
, que é o endereço armazenado emip1
:a memória do programa é
NB: como
int*
é um tipo especial, prefiro sempre declarar vários ponteiros na mesma linha, pois acho que a notaçãoint *x;
ouint *x, *y;
pode ser enganosa. Eu prefiro escreverint* x; int* y;
HTH
fonte
ip2
deve ser .3
4
Porque quando você diz
você está dizendo o 'objeto apontado por
ipp
' para apontar a direção da memória queip2
está apontando.Você não está dizendo
ipp
para apontarip2
.fonte
Se você adicionar o operador de desreferência
*
ao ponteiro, será redirecionado do ponteiro para o objeto apontado.Exemplos:
Portanto:
fonte
Se você gostaria
ipp
de apontarip2
, você teria que dizeripp = &ip2;
. No entanto, isso deixariaip1
ainda apontandoi
.fonte
Bem começando você definir,
Agora desreferencie-o como,
fonte
Considere cada variável representada assim:
então suas variáveis devem ser representadas assim
Como o valor de
ipp
é&ip1
assim a introdução:altera o valor no endereço
&ip1
para o valor deip2
, o que significa queip1
é alterado:Mas
ipp
ainda:Portanto, o valor de
ipp
still, o&ip1
que significa que ainda aponta paraip1
.fonte
Porque você está mudando o ponteiro de
*ipp
. Isso significaipp
(nome identificável) ---- entre.ipp
é o endereço deip1
.*ipp
então vá para (endereço de dentro)ip1
.Agora estamos em
ip1
.*ipp
(ieip1
) =ip
2.ip2
contenha endereço dej
., então oip1
conteúdo será substituído por contido de ip2 (ou seja, endereço de j), NÃO ESTAMOS ALTERANDOipp
CONTEÚDO. É ISSO AÍ.fonte
*ipp = ip2;
implica:Atribua
ip2
à variável apontada poripp
. Portanto, isso é equivalente a:Se você deseja que o endereço
ip2
seja armazenadoipp
, basta:Agora
ipp
aponta paraip2
.fonte
ipp
pode conter um valor de (ou seja, apontar para) um ponteiro para o objeto do tipo ponteiro . Quando você fazentão o
ipp
contém o endereço da variável (ponteiro)ip2
, que é (&ip2
) do tipo ponteiro para ponteiro . Agora a seta daipp
segunda foto apontará paraip2
.O Wiki diz:
O
*
operador é um operador de desreferência que opera na variável ponteiro e retorna um valor l (variável) equivalente ao valor no endereço do ponteiro. Isso é chamado de desreferenciar o ponteiro.Aplicando o
*
operador naipp
referência a um valor l do ponteiro paraint
digitar. O valor l não referenciado*ipp
é do tipo ponteiro paraint
, pode conter o endereço de umint
tipo de dados. Após a declaraçãoipp
está mantendo o endereço deip1
e*ipp
está mantendo o endereço de (apontando para)i
. Você pode dizer que*ipp
é um apelido deip1
. Ambos**ipp
e*ip1
são alias parai
.Fazendo
*ipp
eip2
ambos apontam para o mesmo local, masipp
ainda estão apontando paraip1
.O
*ipp = ip2;
que realmente é é que ele copia o conteúdo deip2
(o endereço dej
) paraip1
(como*ipp
é um apelido paraip1
), fazendo com que ambos os ponteirosip1
eip2
apontando para o mesmo objeto (j
).Portanto, na segunda figura, a seta de
ip1
eip2
está apontando paraj
enquantoipp
ainda está apontandoip1
, já que nenhuma modificação é feita para alterar o valor deipp
.fonte