capitulos - el puntero personajes
Puntero a la aclaraciĆ³n del puntero (16)
Mi pregunta es: ¿Por qué en la segunda imagen, ipp aún apunta a ip1 pero no a ip2?
colocaste lindas imágenes, voy a intentar hacer un buen arte ascii:
Like @ Robert-S-Barnes dijo en su respuesta: olvídese de los indicadores , y qué apunta a qué, pero piense en términos de memoria. Básicamente, un int*
significa que contiene la dirección de una variable y un int**
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
significa la address of foo
, y *foo
significa el value of the address contained in foo
.
Entonces, como los punteros tratan sobre la memoria, la mejor manera de hacerlo realmente "tangible" es mostrar lo que el álgebra de punteros hace a la memoria.
Entonces, aquí está la memoria de su programa (simplificada a los fines del ejemplo):
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ | | | | ]
cuando haces tu código inicial:
int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;
así es como se ve tu memoria:
name: i j ip1 ip2
addr: 0 1 2 3
mem : [ 5| 6| 0| 1]
allí puede ver que ip1
e ip2
obtienen las direcciones de i
y j
y ipp
todavía no existe. No olvide que las direcciones son simplemente enteros almacenados con un tipo especial.
Luego declaras y ipp
como:
int **ipp = &ip1;
así que aquí está tu memoria:
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ 5| 6| 0| 1| 2]
y luego, está cambiando el valor apuntado por la dirección almacenada en ipp
, que es la dirección almacenada en ip1
:
*ipp = ip2;
la memoria del programa es
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ 5| 6| 1| 1| 2]
NB: como int*
es un tipo especial, prefiero evitar declarar varios punteros en la misma línea, ya que creo que int *x;
o int *x, *y;
la notación puede ser engañosa. Prefiero escribir int* x; int* y;
int* x; int* y;
HTH
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;
e
ipp
apunta aip1
que apunta ai
.*ipp
esip1
, y**ipp
esi
, o 5. Podemos ilustrar la situación, con nuestra conocida notación de cuadro y flecha, como esta:Si entonces decimos
*ipp = ip2;
hemos cambiado el puntero apuntado por
ipp
(es decir,ip1
) para que contenga una copia deip2
, de modo que (ip1
)ip1
ahora aj
:
Mi pregunta es: ¿Por qué en la segunda imagen, ipp
todavía apunta a ip1
pero no a ip2
?
Al igual que la mayoría de las preguntas para principiantes en la etiqueta C, esta pregunta puede responderse volviendo a los primeros principios:
- Un puntero es un tipo de valor.
- Una variable contiene un valor.
- El operador
&
convierte una variable en un puntero. - El 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 ubicaciones de almacenamiento mutables como "variables").
Entonces tenemos variables:
int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;
La variable ip1
contiene un puntero. El operador &
convierte i
en un puntero y ese valor del puntero se asigna a ip1
. Entonces ip1
contiene un puntero a i
.
La variable ip2
contiene un puntero. El operador &
convierte j
en un puntero y ese puntero se asigna a ip2
. Entonces ip2
contiene un puntero a j
.
int **ipp = &ip1;
La variable ipp
contiene un puntero. El operador &
convierte la variable ip1
en un puntero y ese valor del puntero se asigna a ipp
. Entonces ipp
contiene un puntero a ip1
.
Vamos a resumir la historia hasta ahora:
-
i
contengo 5 -
j
contiene 6 -
ip1
contiene "puntero ai
" -
ip2
contiene "puntero aj
" -
ipp
contiene "puntero aip1
"
Ahora decimos
*ipp = ip2;
El operador *
convierte un puntero en una variable. Buscamos el valor de ipp
, que es "puntero a ip1
y lo convierte en una variable. ¿Qué variable? ip1
por supuesto!
Por lo tanto, esta es simplemente otra forma de decir
ip1 = ip2;
Entonces buscamos el valor de ip2
. ¿Qué es? "puntero a j
". ip1
ese valor de puntero a ip1
, por lo que ip1
ahora es "puntero a j
"
Solo cambiamos una cosa: el valor de ip1
:
-
i
contengo 5 -
j
contiene 6 -
ip1
contiene "puntero aj
" -
ip2
contiene "puntero aj
" -
ipp
contiene "puntero aip1
"
¿Por qué
ipp
aún apunta aip1
y no aip2
?
Una variable cambia cuando se le asigna. Cuente las asignaciones; ¡no puede haber más cambios en las variables que asignaciones! Empiezas asignando a i
, j
, ip1
, ip2
e ipp
. A continuación, asigna a *ipp
, que como hemos visto significa lo mismo que "asignar a ip1
". ¡Ya que no asignó a ipp
una segunda vez, no cambió!
Si desea cambiar ipp
, tendrá que asignarlo a ipp
:
ipp = &ip2;
por ejemplo.
Considere cada variable representada así:
type : (name, adress, value)
entonces tus variables deberían ser representadas así
int : ( i , &i , 5 ); ( j , &j , 6); ( k , &k , 5 )
int* : (ip1, &ip1, &i); (ip1, &ip1, &j)
int** : (ipp, &ipp, &ip1)
Como el valor de ipp
es &ip1
entonces la incisión:
*ipp = ip2;
cambia el valor en addess &ip1
al valor de ip2
, lo que significa que ip1
ha cambiado:
(ip1, &ip1, &i) -> (ip1, &ip1, &j)
Pero ipp
todavía:
(ipp, &ipp, &ip1)
Entonces el valor de ipp
still &ip1
que significa que aún apunta a ip1
.
Debido a que cambió el valor apuntado por ipp
no el valor de ipp
. Entonces, ipp
aún apunta a ip1
(el valor de ipp
), el valor de ip1
ahora es el mismo que el valor de ip2
, por lo que ambos apuntan a j
.
Esta:
*ipp = ip2;
es lo mismo que:
ip1 = ip2;
Espero que este pedazo de código pueda ayudar.
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
int i = 5, j = 6, k = 7;
int *ip1 = &i, *ip2 = &j;
int** ipp = &ip1;
printf("address of value i: %p/n", &i);
printf("address of value j: %p/n", &j);
printf("value ip1: %p/n", ip1);
printf("value ip2: %p/n", ip2);
printf("value ipp: %p/n", ipp);
printf("address value of ipp: %p/n", *ipp);
printf("value of address value of ipp: %d/n", **ipp);
*ipp = ip2;
printf("value ipp: %p/n", ipp);
printf("address value of ipp: %p/n", *ipp);
printf("value of address value of ipp: %d/n", **ipp);
}
produce:
Mi opinión muy personal es que las imágenes con flechas que señalan de esta manera o que hacen que los indicadores sean más difíciles de entender. Los hace parecer como algunas entidades abstractas y misteriosas. Ellos no son.
Como todo lo demás en su computadora, los indicadores son números . El nombre "puntero" es simplemente una forma elegante de decir "una variable que contiene una dirección".
Por lo tanto, déjenme agitar las cosas explicando cómo funciona realmente una computadora.
Tenemos un int
, tiene el nombre i
el valor 5. Esto se almacena en la memoria. Como todo lo almacenado en la memoria, necesita una dirección, o no podríamos encontrarlo. Digamos que termina en la dirección 0x12345678 y su amigo j
con el valor 6 termina justo después de él. 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:
Address Data Meaning
0x12345678 00 00 00 05 // The variable i
0x1234567C 00 00 00 06 // The variable j
Ahora queremos señalar estas variables. Creamos un puntero a int, int* ip1
y one int* 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és de j
. Configuramos los punteros para que contengan las direcciones de las variables previamente asignadas: ip1=&i;
("copie la dirección de i en ip1") e ip2=&j
. Lo que sucede entre líneas es:
Address Data Meaning
0x12345680 12 34 56 78 // The variable ip1(equal to address of i)
0x12345684 12 34 56 7C // The variable ip2(equal to address of j)
Entonces, lo que obtuvimos ahora son algunos trozos de memoria de 4 bytes que contienen números. No hay flechas místicas o mágicas a la vista.
De hecho, con solo mirar un volcado de memoria, no podemos decir si la dirección 0x12345680 contiene un int
o int*
. La diferencia es cómo nuestro programa elige usar los contenidos almacenados en esta dirección. (La tarea de nuestro programa es simplemente decirle al CPU qué hacer con estos números).
Luego agregamos otro nivel de int** ipp = &ip1;
indirecto con int** ipp = &ip1;
. Nuevamente, obtenemos un pedazo de memoria:
Address Data Meaning
0x12345688 12 34 56 80 // The variable ipp
El patrón parece familiar. Sin embargo, otro pedazo de 4 bytes contiene un número.
Ahora, si tuviéramos un volcado de memoria de la RAM poco ficticia anterior, podríamos verificar manualmente dónde apuntan estos punteros. Echamos un vistazo a lo que está almacenado en la dirección de la variable ipp
y encontramos el contenido 0x12345680. Que es, por supuesto, la dirección donde se almacena ip1
. Podemos ir a esa dirección, verificar los contenidos allí y encontrar la dirección de i
, y finalmente podemos ir a esa dirección y encontrar el número 5.
Entonces, si tomamos los contenidos de ipp, *ipp
, obtendremos la dirección de la variable de puntero ip1
. Al escribir *ipp=ip2
copiamos ip2 en ip1, es equivalente a ip1=ip2
. En cualquier caso, obtendríamos
Address Data Meaning
0x12345680 12 34 56 7C // The variable ip1
0x12345684 12 34 56 7C // The variable ip2
(Estos ejemplos fueron dados para una gran CPU endian)
Muy empezando a configurar,
ipp = &ip1;
Ahora desreferenciarlo como,
*ipp = *&ip1 // Here *& becomes 1
*ipp = ip1 // Hence proved
Observe las asignaciones:
ipp = &ip1;
resultados ipp
para apuntar a ip1
.
ip2
, para que ipp
apunte a ip2
, debemos cambiar de la misma manera,
ipp = &ip2;
lo que claramente no estamos haciendo. En cambio, estamos cambiando el valor en la dirección señalada por ipp
.
Haciendo el siguiente
*ipp = ip2;
solo estamos reemplazando el valor almacenado en ip1
.
ipp = &ip1
, significa *ipp = ip1 = &i
,
Ahora, *ipp = ip2 = &j
.
Entonces, *ipp = ip2
es esencialmente lo mismo que ip1 = ip2
.
Olvide por un segundo sobre la analogía señalando. 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 haces *ipp = ip2
, lo que estás haciendo es *ipp
obtener el objeto en la dirección contenida en ipp
que es ip1
y luego asignarle a ip1
el valor almacenado en ip2
, que es la dirección de j
.
Simplemente
&
-> Dirección de
*
-> Valor en
Porque cuando dices
*ipp = ip2
usted está diciendo el ''objeto apuntado por ipp
'' para señalar la dirección de la memoria que ip2
.
No estás diciendo que ipp
apunte a ip2
.
Porque está cambiando el puntero de *ipp
. Significa
-
ipp
(nombre variable) ---- vaya adentro. - dentro de
ipp
está la dirección deip1
. - ahora
*ipp
así que vaya a (dirección del interior)ip1
.
Ahora estamos en ip1
. *ipp
(es decir, ip1
) = ip
2.
ip2
contiene la dirección de j
ip1
contenido ip1
será reemplazado por el contenido de ip2 (es decir, la dirección de j), NO ESTAMOS CAMBIANDO CONTENIDO ipp
. ESO ES.
Si agrega el operador de desreferencia *
al puntero, lo redirecciona desde el puntero al objeto apuntado.
Ejemplos:
int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
// it''s not the dereference operator in this context
*p; // <-- this expression uses the pointed-to object, that is `i`
p; // <-- this expression uses the pointer object itself, that is `p`
Por lo tanto:
*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
// therefore, `ipp` still points to `ip1` afterwards.
Si quiere que ipp
apunte a ip2
, debería decir ipp = &ip2;
. Sin embargo, esto dejaría que ip1
siga apuntando a i
.
*ipp = ip2;
implica:
Asigna ip2
a la variable apuntada por ipp
. Entonces esto es equivalente a:
ip1 = ip2;
Si desea que la dirección de ip2
se almacene en ipp
, simplemente haga lo siguiente:
ipp = &ip2;
Ahora ipp
apunta a ip2
.
ipp
puede contener un valor de (es decir, señalar) un puntero al objeto de tipo puntero . Cuando tu lo hagas
ipp = &ip2;
entonces el ipp
contiene la dirección de la variable (puntero) ip2
, que es ( &ip2
) de tipo puntero a puntero . Ahora la flecha de ipp
en la segunda foto apuntará a ip2
.
Wiki dice:
El operador *
es un operador de desreferencia que opera en la variable del puntero y devuelve un l-value (variable) equivalente al valor en la dirección del puntero. Esto se llama desreferenciación del puntero.
Al aplicar *
operador en ipp
desfasa a un valor l de puntero a tipo int
. El l-value *ipp
desreferenciado es de tipo puntero a int
, puede contener la dirección de un tipo de datos int
. Después de la declaración
ipp = &ip1;
ipp
está sosteniendo la dirección de ip1
y *ipp
está sosteniendo la dirección de (señalando) i
. Puedes decir que *ipp
es un alias de ip1
. Ambos **ipp
y *ip1
son alias para i
.
Haciendo
*ipp = ip2;
*ipp
e ip2
apuntan a la misma ubicación, pero ipp
todavía apunta a ip1
.
Qué *ipp = ip2;
en realidad es que copia el contenido de ip2
(la dirección de j
) a ip1
(como *ipp
es un alias para ip1
), haciendo que ambos punteros ip1
e ip2
apunten al mismo objeto ( j
).
Entonces, en la segunda figura, la flecha de ip1
e ip2
apuntan a j
mientras que ipp
aún apunta a ip1
ya que no se realiza ninguna modificación para cambiar el valor de ipp
.
ipp = &ip1;
Ninguna asignación posterior ha cambiado el valor de ipp
. Es por eso que todavía apunta a ip1
.
Lo que haces con *ipp
, es decir, con ip1
, no cambia el hecho de que ipp
apunta a ip1
.