Estaba siguiendo este tutorial sobre cómo funciona un puntero a un puntero .
Permítanme citar el pasaje relevante:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
Ahora podemos establecer
int **ipp = &ip1;
y
ipp
puntos a losip1
que apuntai
.*ipp
esip1
, y**ipp
esi
, o 5. Podemos ilustrar la situación, con nuestra notación familiar de caja y flecha, así:Si entonces decimos
*ipp = ip2;
hemos cambiado el puntero señalado por
ipp
(es decir,ip1
) para que contenga una copiaip2
, de modo que (ip1
) ahora apunte aj
:
Mi pregunta es: ¿Por qué en la segunda imagen, ipp
todavía está apuntando ip1
pero no ip2
?
ipp
cuando lo defina, para que su pregunta esté completa ;-)ipp
que apunta aip1
lo que*ipp = ip2
es igual aip1 = ip2
int **ipp
es mucho menos intuitivo queint** ipp
, especialmente cuando**ipp
significa algo completamente diferente fuera de las declaraciones de tipo.int **ipp
parece bastante intuitivo. Significa que estoy haciendo**ipp
unint
. Cual es verdad.ipp
esint**
, así que simplemente escriba enint**
lugar de mágico "laimp
desreferenciación es una comprensión int" .Respuestas:
Olvídese por un segundo sobre la analogía de señalar. Lo que realmente contiene un puntero es una dirección de memoria. El
&
operador es la "dirección de", es decir, devuelve la dirección en la memoria de un objeto. El*
operador le proporciona el objeto al que se refiere un puntero, es decir, dado un puntero que contiene una dirección, devuelve el objeto en esa dirección de memoria. Entonces, cuando lo hace*ipp = ip2
, lo que está haciendo es*ipp
obtener el objeto en la dirección en laipp
que se encuentraip1
y luego asignarlo alip1
valor almacenadoip2
, que es la dirección dej
.Simplemente
&
-> Dirección de*
-> Valor enfuente
Porque cambiaste el valor señalado por
ipp
no el valor deipp
. Entonces,ipp
todavía apunta aip1
(el valor deipp
),ip1
el valor de ahora es el mismo queip2
el valor de, por lo que ambos apuntan aj
.Esta:
es lo mismo que:
fuente
int *ip1 = &i
y*ipp = ip2;
, es decir, si elimina elint
de la primera instrucción, las asignaciones se ven muy similares, pero*
está haciendo algo muy diferente en los dos casos.Como la mayoría de las preguntas para principiantes en la etiqueta C, esta pregunta se puede responder volviendo a los primeros principios:
&
operador convierte una variable en un puntero.*
operador convierte un puntero en una variable.(Técnicamente, debería decir "lvalue" en lugar de "variable", pero creo que es más claro describir las ubicaciones de almacenamiento mutable como "variables").
Entonces tenemos variables:
La variable
ip1
contiene un puntero. El&
operador se conviertei
en un puntero y se le asigna ese valor de punteroip1
. Por lo tanto,ip1
contiene un puntero ai
.La variable
ip2
contiene un puntero. El&
operador se conviertej
en un puntero y se le asigna ese punteroip2
. Por lo tanto,ip2
contiene un puntero aj
.La variable
ipp
contiene un puntero. El&
operador convierte la variableip1
en un puntero y se le asigna ese valor de punteroipp
. Por lo tanto,ipp
contiene un puntero aip1
.Resumamos la historia hasta ahora:
i
contiene 5j
contiene 6ip1
contiene "puntero ai
"ip2
contiene "puntero aj
"ipp
contiene "puntero aip1
"Ahora decimos
El
*
operador vuelve a convertir un puntero en una variable. Buscamos el valor deipp
, que es "puntero aip1
y lo convertimos en una variable. ¿Qué variable? Porip1
supuesto!Por lo tanto, esta es simplemente otra forma de decir
Entonces buscamos el valor de
ip2
. ¿Qué es? "puntero aj
". Asignamos ese valor de puntero aip1
, porip1
lo que ahora es "puntero aj
"Solo cambiamos una cosa: el valor de
ip1
:i
contiene 5j
contiene 6ip1
contiene "puntero aj
"ip2
contiene "puntero aj
"ipp
contiene "puntero aip1
"Una variable cambia cuando se le asigna. Cuenta las tareas; ¡no puede haber más cambios en las variables que asignaciones! Se empieza por asignar a
i
,j
,ip1
,ip2
yipp
. Luego asigna a*ipp
, que como hemos visto significa lo mismo que "asignar aip1
". Como no lo asignó poripp
segunda vez, ¡no cambió!Si desea cambiar,
ipp
entonces tendrá que asignar aipp
:por ejemplo.
fuente
Espero que este código pueda ayudar.
produce:
fuente
Mi opinión muy personal es que las imágenes con flechas apuntando hacia este lado o que hacen que los punteros sean más difíciles de entender. Los hace parecer algunas entidades abstractas y misteriosas. Ellos no son.
Como todo lo demás en su computadora, los punteros son números . El nombre "puntero" es solo una forma elegante de decir "una variable que contiene una dirección".
Por lo tanto, déjenme remover las cosas explicando cómo funciona realmente una computadora.
Tenemos un
int
, tiene el nombrei
y el valor 5. Esto se almacena en la memoria. Como todo lo que está almacenado en la memoria, necesita una dirección, o no podríamos encontrarla. Digamos quei
termina en la dirección 0x12345678 y su amigoj
con valor 6 termina justo después. Suponiendo una CPU de 32 bits donde int es de 4 bytes y los punteros son de 4 bytes, las variables se almacenan en la memoria física de esta manera:Ahora queremos señalar estas variables. Creamos un puntero a int
int* ip1
, y oneint* ip2
. Como todo en la computadora, estas variables de puntero también se asignan en algún lugar de la memoria. Supongamos que terminan en las siguientes direcciones adyacentes en la memoria, inmediatamente despuésj
. Configuramos los punteros para que contengan las direcciones de las variables previamente asignadas:ip1=&i;
("copie la dirección de i en ip1") yip2=&j
. Lo que sucede entre líneas es:Entonces, lo que obtuvimos fueron aún algunos fragmentos de memoria de 4 bytes que contenían números. No hay flechas místicas o mágicas a la vista.
De hecho, con solo mirar un volcado de memoria, no podemos saber si la dirección 0x12345680 contiene un
int
oint*
. La diferencia es cómo nuestro programa elige usar los contenidos almacenados en esta dirección. (La tarea de nuestro programa es en realidad decirle a la CPU qué hacer con estos números).Luego agregamos otro nivel de indirección con
int** ipp = &ip1;
. De nuevo, solo tenemos un trozo de memoria:El patrón parece familiar. Otro trozo de 4 bytes que contiene un número.
Ahora, si tuviéramos un volcado de memoria de la pequeña RAM ficticia anterior, podríamos comprobar manualmente dónde apuntan estos punteros. Echamos un vistazo a lo que está almacenado en la dirección de la
ipp
variable y encontramos los contenidos 0x12345680. Cuál es, por supuesto, la dirección dondeip1
se almacena. Podemos ir a esa dirección, verificar el contenido allí y encontrar la dirección dei
, y finalmente podemos ir a esa dirección y encontrar el número 5.Entonces, si tomamos el contenido de ipp,
*ipp
obtendremos la dirección de la variable de punteroip1
. Al escribir*ipp=ip2
, copiamos ip2 en ip1, es equivalente aip1=ip2
. En cualquier caso obtendríamos(Estos ejemplos se dieron para una gran CPU endian)
fuente
location, value, variable
ubicación1,2,3,4,5
y el valorA,1,B,C,3
, la idea correspondiente de los punteros podría explicarse fácilmente sin el uso de flechas, que son intrínsecamente confusas. Con cualquier implementación que elija, existe un valor en algún lugar, y esta es la pieza del rompecabezas que se ofusca al modelar con flechas.&
operador en una variable le da una moneda que representa esa variable. El*
operador de esa moneda le devuelve la variable. ¡No se requieren flechas!Observe las asignaciones:
resultados
ipp
a señalarip1
.así que para
ipp
señalarip2
, debemos cambiar de manera similar,que claramente no estamos haciendo. En cambio, estamos cambiando el valor en la dirección señalada por
ipp
.Al hacer lo siguiente
solo estamos reemplazando el valor almacenado en
ip1
.ipp = &ip1
, Medios*ipp = ip1 = &i
,ahora,
*ipp = ip2 = &j
.Entonces,
*ipp = ip2
es esencialmente igual queip1 = ip2
.fuente
Ninguna asignación posterior ha cambiado el valor de
ipp
. Es por eso que todavía apunta aip1
.Lo que haces con
*ipp
, es decir, conip1
, no cambia el hecho queipp
señalaip1
.fuente
pusiste lindas fotos, voy a tratar de hacer un bonito arte ascii:
Como @ Robert-S-Barnes dijo en su respuesta: olvídate de los punteros y de lo que apunta a qué, pero piensa en términos de memoria. Básicamente,
int*
significa que contiene la dirección de una variable yint**
contiene la dirección de una variable que contiene la dirección de una variable. Luego puede usar el álgebra del puntero para acceder a los valores o las direcciones:&foo
mediasaddress of foo
y*foo
mediasvalue of the address contained in foo
.Por lo tanto, como los punteros se trata de lidiar con la memoria, la mejor manera de hacer que eso sea "tangible" es mostrar lo que el álgebra de los punteros le hace a la memoria.
Entonces, aquí está la memoria de su programa (simplificado para el propósito del ejemplo):
cuando haces tu código inicial:
así es como se ve tu memoria:
Allí se puede ver
ip1
yip2
obtiene las direcciones dei
ej
yipp
todavía no existe. No olvide que las direcciones son simplemente enteros almacenados con un tipo especial.Luego declara y define
ipp
como:así que aquí está tu memoria:
y luego, está cambiando el valor señalado por la dirección almacenada
ipp
, que es la dirección almacenada enip1
:la memoria del programa es
NB: como
int*
es un tipo especial, prefiero evitar siempre declarando múltiples punteros en la misma línea, ya que creo que elint *x;
oint *x, *y;
la notación puede ser engañoso. Prefiero escribirint* x; int* y;
HTH
fuente
ip2
debe ser3
no4
.Porque cuando dices
estás diciendo el "objeto señalado por
ipp
" para señalar la dirección de la memoria queip2
está apuntando.No estás diciendo
ipp
que apuntesip2
.fuente
Si agrega el operador de desreferencia
*
al puntero, redirige desde el puntero al objeto señalado.Ejemplos:
Por lo tanto:
fuente
Si desea
ipp
señalarip2
, tendría que deciripp = &ip2;
. Sin embargo, esto dejaríaip1
todavía apuntando ai
.fuente
Desde el principio te pones
Ahora desreferenciarlo como,
fuente
Considere cada variable representada así:
entonces tus variables deberían ser representadas así
Como el valor de
ipp
es&ip1
así, la inctrucción:cambia el valor en la dirección
&ip1
al valor deip2
, lo que significa queip1
se cambia:Pero
ipp
aun así:Entonces, el valor de
ipp
still, lo&ip1
que significa que todavía apuntaip1
.fuente
Porque estás cambiando el puntero de
*ipp
. Significaipp
(nombre variable) ---- entra.ipp
es la dirección deip1
.*ipp
ve a (dirección del interior)ip1
.Ahora nos encontramos en
ip1
.*ipp
(es decirip1
) =ip
2.ip2
contener la dirección dej
.so elip1
contenido será reemplazado por contener de ip2 (es decir, la dirección de j), NO ESTAMOS CAMBIANDO ELipp
CONTENIDO. ESO ES.fuente
*ipp = ip2;
implica:Asignar
ip2
a la variable apuntada poripp
. Entonces esto es equivalente a:Si desea
ip2
que se guarde la dirección deipp
, simplemente haga lo siguiente:Ahora
ipp
apunta aip2
.fuente
ipp
puede contener un valor de (es decir, señalar) un puntero al objeto de tipo puntero . Cuando tu lo hagasentonces
ipp
contiene la dirección de la variable (puntero)ip2
, que es (&ip2
) de tipo puntero a puntero . Ahora la flecha deipp
en la segunda foto apuntaráip2
.Wiki dice:
El
*
operador es un operador de desreferencia que opera en la variable del puntero y devuelve un valor l (variable) equivalente al valor en la dirección del puntero. Esto se llama desreferenciar el puntero.Aplicando el
*
operador alipp
desreferenciarlo a un valor l de puntero paraint
escribir. El valor l desreferenciado*ipp
es de tipo puntero aint
, puede contener la dirección de unint
tipo de datos. Después de la declaraciónipp
mantiene la dirección deip1
y*ipp
mantiene la dirección de (apuntando a)i
. Se puede decir que*ipp
es un alias deip1
. Ambos**ipp
y*ip1
son alias parai
.Haciendo
*ipp
yip2
ambos apuntan a la misma ubicación peroipp
todavía apuntan aip1
.Lo
*ipp = ip2;
que sí sucede es que copia el contenido deip2
(la dirección dej
) enip1
(como*ipp
es un alias paraip1
), en efecto haciendo punterosip1
yip2
apuntando al mismo objeto (j
).Entonces, en la segunda figura, la flecha de
ip1
yip2
apunta aj
mientrasipp
todavía apunta aip1
que no se realiza ninguna modificación para cambiar el valor deipp
.fuente