sinopsis puntero personajes capitulos capitulo c pointers

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 a ip1 que apunta a i . *ipp es ip1 , y **ipp es i , 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 de ip2 , de modo que ( ip1 ) ip1 ahora a j :

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 a i "
  • ip2 contiene "puntero a j "
  • ipp contiene "puntero a ip1 "

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 a j "
  • ip2 contiene "puntero a j "
  • ipp contiene "puntero a ip1 "

¿Por qué ipp aún apunta a ip1 y no a ip2 ?

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

  1. ipp (nombre variable) ---- vaya adentro.
  2. dentro de ipp está la dirección de ip1 .
  3. 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 .