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
ipppuntos a losip1que apuntai.*ippesip1, y**ippesi, 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, ipptodavía está apuntando ip1pero no ip2?



ippcuando lo defina, para que su pregunta esté completa ;-)ippque apunta aip1lo que*ipp = ip2es igual aip1 = ip2int **ippes mucho menos intuitivo queint** ipp, especialmente cuando**ippsignifica algo completamente diferente fuera de las declaraciones de tipo.int **ippparece bastante intuitivo. Significa que estoy haciendo**ippunint. Cual es verdad.ippesint**, así que simplemente escriba enint**lugar de mágico "laimpdesreferenciació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*ippobtener el objeto en la dirección en laippque se encuentraip1y luego asignarlo alip1valor almacenadoip2, que es la dirección dej.Simplemente
&-> Dirección de*-> Valor enfuente
Porque cambiaste el valor señalado por
ippno el valor deipp. Entonces,ipptodavía apunta aip1(el valor deipp),ip1el valor de ahora es el mismo queip2el valor de, por lo que ambos apuntan aj.Esta:
es lo mismo que:
fuente
int *ip1 = &iy*ipp = ip2;, es decir, si elimina elintde 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
ip1contiene un puntero. El&operador se convierteien un puntero y se le asigna ese valor de punteroip1. Por lo tanto,ip1contiene un puntero ai.La variable
ip2contiene un puntero. El&operador se conviertejen un puntero y se le asigna ese punteroip2. Por lo tanto,ip2contiene un puntero aj.La variable
ippcontiene un puntero. El&operador convierte la variableip1en un puntero y se le asigna ese valor de punteroipp. Por lo tanto,ippcontiene un puntero aip1.Resumamos la historia hasta ahora:
icontiene 5jcontiene 6ip1contiene "puntero ai"ip2contiene "puntero aj"ippcontiene "puntero aip1"Ahora decimos
El
*operador vuelve a convertir un puntero en una variable. Buscamos el valor deipp, que es "puntero aip1y lo convertimos en una variable. ¿Qué variable? Porip1supuesto!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, porip1lo que ahora es "puntero aj"Solo cambiamos una cosa: el valor de
ip1:icontiene 5jcontiene 6ip1contiene "puntero aj"ip2contiene "puntero aj"ippcontiene "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,ip2yipp. Luego asigna a*ipp, que como hemos visto significa lo mismo que "asignar aip1". Como no lo asignó porippsegunda vez, ¡no cambió!Si desea cambiar,
ippentonces 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 nombreiy 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 queitermina en la dirección 0x12345678 y su amigojcon 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
intoint*. 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
ippvariable y encontramos los contenidos 0x12345680. Cuál es, por supuesto, la dirección dondeip1se 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,
*ippobtendremos 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, variableubicación1,2,3,4,5y 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
ippa señalarip1.así que para
ippseñ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 = ip2es 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 queippseñ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:&foomediasaddress of fooy*foomediasvalue 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
ip1yip2obtiene las direcciones deiejyipptodavía no existe. No olvide que las direcciones son simplemente enteros almacenados con un tipo especial.Luego declara y define
ippcomo: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
ip2debe ser3no4.Porque cuando dices
estás diciendo el "objeto señalado por
ipp" para señalar la dirección de la memoria queip2está apuntando.No estás diciendo
ippque 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
ippseñalarip2, tendría que deciripp = &ip2;. Sin embargo, esto dejaríaip1todaví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
ippes&ip1así, la inctrucción:cambia el valor en la dirección
&ip1al valor deip2, lo que significa queip1se cambia:Pero
ippaun así:Entonces, el valor de
ippstill, lo&ip1que significa que todavía apuntaip1.fuente
Porque estás cambiando el puntero de
*ipp. Significaipp(nombre variable) ---- entra.ippes la dirección deip1.*ippve a (dirección del interior)ip1.Ahora nos encontramos en
ip1.*ipp(es decirip1) =ip2.ip2contener la dirección dej.so elip1contenido será reemplazado por contener de ip2 (es decir, la dirección de j), NO ESTAMOS CAMBIANDO ELippCONTENIDO. ESO ES.fuente
*ipp = ip2;implica:Asignar
ip2a la variable apuntada poripp. Entonces esto es equivalente a:Si desea
ip2que se guarde la dirección deipp, simplemente haga lo siguiente:Ahora
ippapunta aip2.fuente
ipppuede contener un valor de (es decir, señalar) un puntero al objeto de tipo puntero . Cuando tu lo hagasentonces
ippcontiene la dirección de la variable (puntero)ip2, que es (&ip2) de tipo puntero a puntero . Ahora la flecha deippen 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 alippdesreferenciarlo a un valor l de puntero paraintescribir. El valor l desreferenciado*ippes de tipo puntero aint, puede contener la dirección de uninttipo de datos. Después de la declaraciónippmantiene la dirección deip1y*ippmantiene la dirección de (apuntando a)i. Se puede decir que*ippes un alias deip1. Ambos**ippy*ip1son alias parai.Haciendo
*ippyip2ambos apuntan a la misma ubicación peroipptodavía apuntan aip1.Lo
*ipp = ip2;que sí sucede es que copia el contenido deip2(la dirección dej) enip1(como*ippes un alias paraip1), en efecto haciendo punterosip1yip2apuntando al mismo objeto (j).Entonces, en la segunda figura, la flecha de
ip1yip2apunta ajmientrasipptodavía apunta aip1que no se realiza ninguna modificación para cambiar el valor deipp.fuente