significado punteros perros meaning dogs array c++ c pointers

c++ - punteros - ¿Cuáles son las barreras para comprender los indicadores y qué se puede hacer para superarlos?



pointers significado (28)

¿Por qué los indicadores son un factor de confusión tan importante para muchos estudiantes universitarios nuevos, e incluso antiguos, en C o C ++? ¿Existen herramientas o procesos de pensamiento que lo ayudaron a comprender cómo funcionan los punteros a nivel de variable, función y nivel superior?

¿Cuáles son algunas de las buenas prácticas que se pueden hacer para llevar a alguien al nivel de "Ah, ja, lo tengo", sin que queden atrapados en el concepto general? Básicamente, perforar como escenarios.


¿Por qué los indicadores son un factor de confusión tan importante para muchos estudiantes nuevos, e incluso antiguos, de nivel universitario en el lenguaje C / C ++?

El concepto de un marcador de posición para un valor - variables - se mapea en algo que se nos enseña en la escuela - álgebra. No hay un paralelo existente que puedas dibujar sin entender cómo está físicamente la memoria dentro de una computadora, y nadie piensa en este tipo de cosas hasta que están lidiando con cosas de bajo nivel, en el nivel de comunicaciones C / C ++ / byte .

¿Existen herramientas o procesos de pensamiento que lo ayudaron a comprender cómo funcionan los punteros a nivel de variable, función y nivel superior?

Cajas de direcciones. Recuerdo que cuando estaba aprendiendo a programar BÁSICOS en microcomputadoras, había libros bonitos con juegos, y algunas veces tenías que meter valores en direcciones particulares. Tenían una imagen de un montón de cajas, rotuladas incrementalmente con 0, 1, 2 ... y se explicó que solo una pequeña cosa (un byte) cabía en estas cajas, y había muchas, algunas computadoras Tenía hasta 65535! Estaban uno junto al otro, y todos tenían una dirección.

¿Cuáles son algunas de las buenas prácticas que se pueden hacer para llevar a alguien al nivel de "Ah, ja, lo tengo", sin que queden atrapados en el concepto general? Básicamente, perforar como escenarios.

¿Para un taladro? Hacer una estructura:

struct { char a; char b; char c; char d; } mystruct; mystruct.a = ''r''; mystruct.b = ''s''; mystruct.c = ''t''; mystruct.d = ''u''; char* my_pointer; my_pointer = &mystruct.b; cout << ''Start: my_pointer = '' << *my_pointer << endl; my_pointer++; cout << ''After: my_pointer = '' << *my_pointer << endl; my_pointer = &mystruct.a; cout << ''Then: my_pointer = '' << *my_pointer << endl; my_pointer = my_pointer + 3; cout << ''End: my_pointer = '' << *my_pointer << endl;

Mismo ejemplo que el anterior, excepto en C:

// Same example as above, except in C: struct { char a; char b; char c; char d; } mystruct; mystruct.a = ''r''; mystruct.b = ''s''; mystruct.c = ''t''; mystruct.d = ''u''; char* my_pointer; my_pointer = &mystruct.b; printf("Start: my_pointer = %c/n", *my_pointer); my_pointer++; printf("After: my_pointer = %c/n", *my_pointer); my_pointer = &mystruct.a; printf("Then: my_pointer = %c/n", *my_pointer); my_pointer = my_pointer + 3; printf("End: my_pointer = %c/n", *my_pointer);

Salida:

Start: my_pointer = s After: my_pointer = t Then: my_pointer = r End: my_pointer = u

Tal vez eso explica algunos de los conceptos básicos a través del ejemplo?


Algunas de las respuestas anteriores han afirmado que "los punteros no son realmente difíciles", pero no se han dirigido directamente donde "¡los punteros son difíciles!" viene de. Hace algunos años enseñé a estudiantes de primer año de CS (solo por un año, ya que claramente lo apesté) y me quedó claro que la idea de apuntar no es difícil. Lo que es difícil es entender por qué y cuándo querría un puntero .

No creo que pueda divorciarse de esa pregunta (por qué y cuándo usar un puntero) de explicar los problemas de ingeniería de software más amplios. Por qué cada variable no debería ser una variable global, y por qué uno debería factorizar un código similar en funciones (que, con esto, usa punteros para especializar su comportamiento en su sitio de llamadas).


Creo que en realidad podría ser un problema de sintaxis. La sintaxis de C / C ++ para los punteros parece inconsistente y más compleja de lo que necesita ser.

Irónicamente, lo que realmente me ayudó a entender los punteros fue encontrar el concepto de iterador en la Biblioteca de plantillas estándar de c ++ . Es irónico porque solo puedo asumir que los iteradores fueron concebidos como una generalización del puntero.

A veces simplemente no puedes ver el bosque hasta que aprendes a ignorar los árboles.


Creo que la principal barrera para entender los punteros son los malos maestros.

A casi todos se les enseñan mentiras sobre punteros: que no son más que direcciones de memoria o que te permiten apuntar a ubicaciones arbitrarias .

Y por supuesto que son difíciles de entender, peligrosas y semi-mágicas.

Nada de lo cual es verdad. Los punteros son en realidad conceptos bastante simples, siempre y cuando se adhieran a lo que el lenguaje C ++ tiene que decir sobre ellos y no los incorpore con atributos que "generalmente" resultan funcionar en la práctica, pero no están garantizados por el lenguaje. , y por lo tanto no son parte del concepto real de un puntero.

Intenté escribir una explicación de esto hace unos meses en esta publicación del blog , con suerte ayudará a alguien.

(Tenga en cuenta que antes de que alguien se vuelva pedante con mí, sí, el estándar de C ++ dice que los punteros representan direcciones de memoria. Pero no dice que "los punteros son direcciones de memoria, y nada más que direcciones de memoria y pueden usarse o pensarse indistintamente con memoria" direcciones ". La distinción es importante)


Creo que la razón principal por la que la gente tiene problemas es porque generalmente no se enseña de manera interesante y atractiva. Me gustaría ver que un profesor obtenga 10 voluntarios de la multitud y les dé una regla de 1 metro cada uno, haga que se paren en una determinada configuración y utilicen las reglas para apuntarse entre sí. Luego muestre la aritmética de punteros moviendo a las personas (y hacia donde apuntan sus gobernantes). Sería una forma simple pero efectiva (y sobre todo memorable) de mostrar los conceptos sin atascarse en la mecánica.

Una vez que llegas a C y C ++, parece ser más difícil para algunas personas. No estoy seguro de si esto se debe a que finalmente están poniendo la teoría de que no comprenden adecuadamente la práctica o porque la manipulación de punteros es inherentemente más difícil en esos idiomas. No puedo recordar mi propia transición tan bien, pero conocí los punteros en Pascal y luego me moví a C y me perdí totalmente.


Creo que lo que hace que los punteros sean difíciles de aprender es que hasta que los punteros se sientan cómodos con la idea de que "en esta ubicación de memoria hay un conjunto de bits que representan un int, un doble, un carácter, lo que sea".

La primera vez que ve un puntero, realmente no obtiene lo que está en esa ubicación de memoria. "¿Qué quieres decir con que tiene una dirección ?"

No estoy de acuerdo con la idea de que "o los consigues o no".

Se vuelven más fáciles de entender cuando empiezas a encontrar usos reales para ellos (como no pasar estructuras grandes a funciones).


El problema con los punteros no es el concepto. Es la ejecución y el lenguaje involucrado. Se produce una confusión adicional cuando los maestros asumen que es el CONCEPTO de indicadores lo que es difícil, y no la jerga, o el desorden complicado C y C ++ hace del concepto. Tanto esfuerzo se ha concentrado en explicar el concepto (como en la respuesta aceptada para esta pregunta) y prácticamente se desperdicia en alguien como yo, porque ya entiendo todo eso. Es solo explicar la parte equivocada del problema.

Para darle una idea de dónde vengo, soy alguien que entiende perfectamente los punteros y puedo usarlos de manera competente en el lenguaje ensamblador. Porque en el lenguaje ensamblador no se les conoce como punteros. Se les conoce como direcciones. Cuando se trata de programar y usar punteros en C, cometo muchos errores y me confundo mucho. Todavía no he resuelto esto. Dejame darte un ejemplo.

Cuando una api dice:

int doIt(char *buffer ) //*buffer is a pointer to the buffer

que quiere

podría querer

un número que representa una dirección a un búfer

(Para darle eso, ¿digo doIt(mybuffer) , o doIt(*myBuffer) ?)

un número que representa la dirección de una dirección a un búfer

(es eso doIt(&mybuffer) o doIt(mybuffer) o doIt(*mybuffer) ?)

un número que representa la dirección a la dirección a la dirección al búfer

(tal vez eso es doIt(&mybuffer) o es doIt(&&mybuffer) ? o incluso doIt(&&&mybuffer) )

y así sucesivamente, y el lenguaje involucrado no lo deja tan claro porque incluye las palabras "puntero" y "referencia" que no tienen tanto significado y claridad para mí como "x tiene la dirección a y" y " esta función requiere una dirección a y ". Además, la respuesta depende de qué diablos es "mybuffer" para comenzar, y de lo que pretende hacer con ella. El lenguaje no admite los niveles de anidamiento que se encuentran en la práctica. Como cuando tengo que entregar un "puntero" a una función que crea un nuevo búfer y modifica el puntero para que apunte a la nueva ubicación del búfer. ¿Realmente quiere el puntero, o un puntero al puntero, para que sepa dónde ir para modificar el contenido del puntero?La mayoría de las veces solo tengo que adivinar qué se entiende por "puntero" y la mayoría de las veces me equivoco, independientemente de cuánta experiencia tenga al adivinar.

"Puntero" es demasiado sobrecargado. ¿Es un puntero una dirección a un valor? o es una variable que contiene una dirección a un valor. Cuando una función desea un puntero, ¿desea la dirección que contiene la variable del puntero o desea la dirección de la variable del puntero? Estoy confundido.


La confusión proviene de las múltiples capas de abstracción mezcladas en el concepto "puntero". Los programadores no se confunden con las referencias ordinarias en Java / Python, pero los punteros son diferentes en que exponen las características de la arquitectura de memoria subyacente.

Es un buen principio separar limpiamente las capas de abstracción, y los indicadores no lo hacen.


La forma en que me gustaba explicarlo fue en términos de matrices e índices: es posible que las personas no estén familiarizadas con los punteros, pero en general saben qué es un índice.

Así que digo que imagina que la RAM es una matriz (y que solo tienes 10 bytes de RAM):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

Entonces, un puntero a una variable es realmente solo el índice de (el primer byte de) esa variable en la RAM.

Entonces, si tiene un puntero / índice unsigned char index = 2 , entonces el valor es obviamente el tercer elemento, o el número 4. Un puntero a un puntero es donde toma ese número y lo usa como un índice en sí, como RAM[RAM[index]] .

Dibujaría una matriz en una lista de papel, y solo la usaría para mostrar cosas como muchos punteros que apuntan a la misma memoria, aritmética de punteros, puntero a puntero, etc.


La razón por la que es tan difícil de entender no es porque sea un concepto difícil, sino porque la sintaxis es inconsistente .

int *mypointer;

Primero aprendió que la parte más a la izquierda de la creación de una variable define el tipo de la variable. La declaración de puntero no funciona así en C y C ++. En su lugar, dicen que la variable está apuntando en el tipo a la izquierda. En este caso: * mypointer está apuntando en un int.

No entendí totalmente los punteros hasta que intenté usarlos en C # (con inseguro), funcionan exactamente de la misma manera pero con una sintaxis lógica y coherente. El puntero es un tipo en sí mismo. Aquí mypointer es un puntero a un int.

int* mypointer;

Ni siquiera me refiero a los punteros de función ...


Me gusta la analogía de la dirección de la casa, pero siempre he pensado que la dirección está en el buzón mismo. De esta manera puede visualizar el concepto de desreferenciación del puntero (abrir el buzón).

Por ejemplo, siguiendo una lista enlazada: 1) comience con su papel con la dirección 2) Vaya a la dirección en el papel 3) Abra el buzón para encontrar un nuevo papel con la siguiente dirección

En una lista enlazada lineal, el último buzón no tiene nada (final de la lista). En una lista enlazada circular, el último buzón tiene la dirección del primer buzón.

Tenga en cuenta que el paso 3 es donde se produce la desreferencia y donde se bloqueará o se equivocará cuando la dirección no sea válida. Suponiendo que puedas caminar hasta el buzón de una dirección no válida, imagina que hay un agujero negro o algo ahí que hace que el mundo se vuelva del revés :)


Número de apartado de correos.

Es una información que te permite acceder a otra cosa.

(Y si realiza cálculos aritméticos en los números de casillas de correos, es posible que tenga un problema, porque la letra va en el casillero equivocado. Y si alguien se muda a otro estado, sin dirección de reenvío, entonces tiene un puntero colgante. por otro lado, si la oficina de correos reenvía el correo, entonces tiene un puntero a un puntero.)


No creo que los punteros en sí mismos sean confusos. La mayoría de la gente puede entender el concepto. Ahora, ¿en cuántos punteros puede pensar o en cuántos niveles de direccionamiento se siente cómodo? No se necesitan demasiados para poner a la gente al límite. El hecho de que se puedan cambiar accidentalmente por errores en su programa también puede hacerles muy difíciles de depurar cuando las cosas salen mal en su código.


No es una mala manera de entenderlo, a través de los iteradores ... pero sigue buscando, verás que Alexandrescu comienza a quejarse de ellos.

Muchos desarrolladores ex-C ++ (que nunca entendieron que los iteradores son un puntero moderno antes de deshacerse del lenguaje) saltan a C # y aún creen que tienen iteradores decentes.

Hmm, el problema es que todo lo que los iteradores tienen es lo que las plataformas de tiempo de ejecución (Java / CLR) intentan lograr: nuevo, simple, uso de todos los usuarios es un dev. Lo que puede ser bueno, pero lo dijeron una vez en el libro púrpura y lo dijeron incluso antes y antes de C:

Indirección

Un concepto muy poderoso, pero nunca así si lo haces del todo. Los iteradores son útiles porque ayudan con la abstracción de algoritmos, otro ejemplo. Y el tiempo de compilación es el lugar para un algoritmo, muy simple. Usted sabe código + datos, o en ese otro idioma C #:

IEnumerable + LINQ + Massive Framework = 300MB penalización en tiempo de ejecución de aplicaciones maliciosas y de arrastre a través de montones de instancias de tipos de referencia.

"Le Pointer es barato".


No veo lo que es tan confuso acerca de los punteros. Señalan una ubicación en la memoria, es decir, almacena la dirección de la memoria. En C / C ++ puede especificar el tipo al que apunta el puntero. Por ejemplo:

int* my_int_pointer;

Dice que my_int_pointer contiene la dirección a una ubicación que contiene un int.

El problema con los punteros es que apuntan a una ubicación en la memoria, por lo que es fácil desviarse hacia alguna ubicación en la que no debería estar. más allá del límite asignado).


Podía trabajar con punteros cuando solo conocía C ++. Yo sabía qué hacer en algunos casos y qué no hacer después de una prueba / error. Pero lo que me dio una comprensión completa es el lenguaje ensamblador. Si realiza una depuración seria de nivel de instrucción con un programa de lenguaje ensamblador que ha escrito, debería poder entender muchas cosas.


Solo para confundir un poco más las cosas, a veces tienes que trabajar con manejadores en lugar de punteros. Las manijas son punteros a punteros, de modo que el extremo posterior puede mover cosas en la memoria para desfragmentar el montón. Si el puntero cambia a mitad de la rutina, los resultados son impredecibles, por lo que primero tiene que bloquear el manejador para asegurarse de que nada vaya a ninguna parte.

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 habla de esto un poco más coherentemente que yo. :-)


Todos los principiantes de C / C ++ tienen el mismo problema y ese problema se produce no porque "los indicadores son difíciles de aprender" sino "quién y cómo se explica". Algunos aprendices lo recopilan verbalmente, de forma visual y la mejor manera de explicarlo es usar el ejemplo "entrenar" (se adapta al ejemplo verbal y visual).

Donde "locomotora" es un puntero que no puede contener nada y "vagón" es lo que "locomotora" intenta tirar (o señalar). Después, puede clasificar el "vagón" en sí mismo, puede contener animales, plantas o personas (o una mezcla de ellos).


En mi primera clase de Comp Sci, hicimos el siguiente ejercicio. Concedido, esta era una sala de conferencias con aproximadamente 200 estudiantes en ella ...

El profesor escribe en la pizarra: int john;

John se levanta

El profesor escribe: int *sally = &john;

Sally se pone de pie, señala a John

Profesor: int *bill = sally;

Bill se pone de pie, apunta a John

Profesor: int sam;

Sam se pone de pie

Profesor: bill = &sam;

Bill ahora apunta a Sam.

Creo que entiendes la idea. Creo que pasamos aproximadamente una hora haciendo esto, hasta que repasamos los conceptos básicos de la asignación de punteros.


Encontré el "Tutorial sobre punteros y matrices en C" de Ted Jensen, un excelente recurso para aprender sobre los punteros. Se divide en 10 lecciones, comenzando con una explicación de qué son los punteros (y para qué sirven) y terminando con punteros de función. http://home.netcom.com/~tjensen/ptr/cpoint.htm

A partir de ahí, la Guía de programación de red de Beej enseña la API de sockets de Unix, a partir de la cual puede comenzar a hacer cosas realmente divertidas. http://beej.us/guide/bgnet/


La razón por la que los punteros parecen confundir a tanta gente es que, en su mayoría, tienen poca o ninguna experiencia en arquitectura de computadoras. Dado que muchos no parecen tener una idea de cómo se implementan realmente las computadoras (la máquina), trabajar en C / C ++ parece extraño.

Un ejercicio es pedirles que implementen una máquina virtual basada en un simple bytecode (en cualquier idioma que elijan, Python funciona muy bien para esto) con un conjunto de instrucciones enfocadas en operaciones de puntero (carga, almacenamiento, direccionamiento directo / indirecto). Luego pídales que escriban programas simples para ese conjunto de instrucciones.

Cualquier cosa que requiera algo más que una simple adición implicará punteros y seguramente lo obtendrán.


La razón por la que me costó entender los punteros, al principio, es que muchas explicaciones incluyen mucha mierda acerca de pasar por referencia. Todo lo que hace es confundir el tema. Cuando utiliza un parámetro de puntero, todavía está pasando por valor; pero el valor pasa a ser una dirección en lugar de, digamos, un int.

Alguien más ya se ha vinculado a este tutorial, pero puedo resaltar el momento en que comencé a entender los punteros:

Un tutorial sobre punteros y matrices en C: Capítulo 3 - Punteros y cadenas

int puts(const char *s);

Por el momento, ignora la const. El parámetro pasado a puts() es un puntero, que es el valor de un puntero (ya que todos los parámetros en C se pasan por valor), y el valor de un puntero es la dirección a la que apunta, o, simplemente, una dirección . Así cuando escribimos puts(strA); Como hemos visto, estamos pasando la dirección de strA [0].

En el momento en que leí estas palabras, las nubes se separaron y un rayo de sol me envolvió con un indicador de comprensión.

Incluso si eres un desarrollador de VB .NET o C # (como yo) y nunca usas código inseguro, vale la pena entender cómo funcionan los punteros, o no entenderás cómo funcionan las referencias a objetos. Luego tendrá la idea común pero errónea de que al pasar una referencia de objeto a un método se copia el objeto.


Las complejidades de los punteros van más allá de lo que podemos enseñar fácilmente. Hacer que los alumnos se señalen entre sí y usar trozos de papel con direcciones de casa son excelentes herramientas de aprendizaje. Hacen un gran trabajo introduciendo los conceptos básicos. De hecho, aprender los conceptos básicos es vital para usar con éxito los punteros. Sin embargo, en el código de producción, es común meterse en escenarios mucho más complejos de los que estas simples demostraciones pueden resumir.

He estado involucrado con sistemas donde teníamos estructuras que apuntan a otras estructuras que apuntan a otras estructuras. Algunas de esas estructuras también contenían estructuras integradas (en lugar de punteros a estructuras adicionales). Aquí es donde los punteros se vuelven realmente confusos. Si tiene varios niveles de direccionamiento indirecto y comienza a terminar con un código como este:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

se puede confundir muy rápidamente (imagine muchas más líneas y potencialmente más niveles). Agregue matrices de punteros, y punteros de nodo a nodo (árboles, listas enlazadas) y empeora aún más. He visto que algunos desarrolladores realmente buenos se pierden una vez que comenzaron a trabajar en tales sistemas, incluso los desarrolladores que entendieron los conceptos básicos realmente bien.

Las estructuras complejas de los punteros tampoco indican necesariamente una codificación deficiente (aunque pueden). La composición es una pieza vital de una buena programación orientada a objetos, y en lenguajes con punteros en bruto, inevitablemente conducirá a una indirección de múltiples capas. Además, los sistemas a menudo necesitan usar bibliotecas de terceros con estructuras que no coincidan entre sí en estilo o técnica. En situaciones como esa, la complejidad va a surgir naturalmente (aunque ciertamente, deberíamos combatirla tanto como sea posible).

Creo que lo mejor que pueden hacer las universidades para ayudar a los estudiantes a aprender punteros es usar buenas demostraciones, combinadas con proyectos que requieren el uso de indicadores. Un proyecto difícil hará más por la comprensión de punteros que mil demostraciones. Las demostraciones pueden proporcionarle una comprensión superficial, pero para comprender mejor los punteros, tiene que usarlos realmente.


Los punteros son un concepto que para muchos puede ser confuso al principio, en particular cuando se trata de copiar valores de punteros y seguir haciendo referencia al mismo bloque de memoria.

Descubrí que la mejor analogía es considerar el puntero como un pedazo de papel con una dirección de casa y el bloque de memoria al que hace referencia como la casa real. Todo tipo de operaciones se pueden explicar fácilmente.

He añadido algunos códigos Delphi a continuación, y algunos comentarios, según corresponda. Elegí Delphi ya que mi otro lenguaje de programación principal, C #, no muestra cosas como fugas de memoria de la misma manera.

Si solo desea aprender el concepto de alto nivel de los punteros, debe ignorar las partes etiquetadas como "Diseño de memoria" en la explicación a continuación. Tienen la intención de dar ejemplos de cómo podría verse la memoria después de las operaciones, pero son de un nivel más bajo. Sin embargo, para poder explicar con precisión cómo funcionan realmente los desbordamientos de búfer, fue importante que agregué estos diagramas.

Descargo de responsabilidad: Para todos los propósitos, esta explicación y los diseños de memoria de ejemplo se simplifican enormemente. Hay más gastos generales y muchos más detalles que necesitaría saber si necesita lidiar con la memoria a bajo nivel. Sin embargo, a los efectos de explicar la memoria y los punteros, es lo suficientemente preciso.

Asumamos que la clase THouse utilizada a continuación se ve así:

type THouse = class private FName : array[0..9] of Char; public constructor Create(name: PChar); end;

Cuando inicializa el objeto de casa, el nombre dado al constructor se copia en el campo privado FName. Hay una razón por la que se define como una matriz de tamaño fijo.

En la memoria, habrá algunos gastos generales asociados con la asignación de la casa, lo ilustraré a continuación de la siguiente manera:

---[ttttNNNNNNNNNN]--- ^ ^ | | | +- the FName array | +- overhead

El área "tttt" es una sobrecarga, normalmente habrá más de esto para varios tipos de tiempos de ejecución e idiomas, como 8 o 12 bytes. Es imperativo que cualquier valor que se almacene en esta área nunca se modifique por nada que no sea el asignador de memoria o las rutinas del sistema central, o corre el riesgo de bloquear el programa.

Asignar memoria

Pídale a un empresario que construya su casa y le dé la dirección de la casa. A diferencia del mundo real, la asignación de memoria no puede indicarse dónde asignar, pero encontrará un lugar adecuado con suficiente espacio e informará la dirección a la memoria asignada.

En otras palabras, el empresario elegirá el lugar.

THouse.Create(''My house'');

Diseño de la memoria:

---[ttttNNNNNNNNNN]--- 1234My house

Mantener una variable con la dirección.

Escriba la dirección de su nueva casa en un pedazo de papel. Este papel servirá de referencia para su casa. Sin este pedazo de papel, estás perdido y no puedes encontrar la casa, a menos que ya estés en ella.

var h: THouse; begin h := THouse.Create(''My house''); ...

Diseño de la memoria:

h v ---[ttttNNNNNNNNNN]--- 1234My house

Copiar valor de puntero

Solo escribe la dirección en una nueva hoja de papel. Ahora tiene dos hojas de papel que lo llevarán a la misma casa, no dos casas separadas. Cualquier intento de seguir la dirección de un papel y reorganizar los muebles en esa casa hará que parezca que la otra casa se ha modificado de la misma manera, a menos que pueda detectar explícitamente que en realidad es solo una casa.

Nota Este suele ser el concepto que más me cuesta explicar a las personas, dos punteros no significa dos objetos o bloques de memoria.

var h1, h2: THouse; begin h1 := THouse.Create(''My house''); h2 := h1; // copies the address, not the house ...

h1 v ---[ttttNNNNNNNNNN]--- 1234My house ^ h2

Liberando la memoria

Demoler la casa. Posteriormente, puede reutilizar el papel para una nueva dirección si lo desea, o borrarlo para olvidar la dirección de la casa que ya no existe.

var h: THouse; begin h := THouse.Create(''My house''); ... h.Free; h := nil;

Aquí primero construyo la casa, y tomo su dirección. Luego le hago algo a la casa (lo uso, el código ... dejado como ejercicio para el lector) y luego lo libero. Por último borro la dirección de mi variable.

Diseño de la memoria:

h <--+ v +- before free ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after free ---------------------- | (note, memory might still xx34My house <--+ contain some data)

Punteros colgando

Le dices a tu empresario que destruya la casa, pero olvidas borrar la dirección de tu pedazo de papel. Más tarde, cuando mira la hoja de papel, ha olvidado que la casa ya no está allí y va a visitarla, con resultados fallidos (consulte también la parte sobre una referencia no válida a continuación).

var h: THouse; begin h := THouse.Create(''My house''); ... h.Free; ... // forgot to clear h here h.OpenFrontDoor; // will most likely fail

Usar h después de la llamada a .Free podría funcionar, pero eso es pura suerte. Lo más probable es que falle, en el lugar de un cliente, en medio de una operación crítica.

h <--+ v +- before free ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h <--+ v +- after free ---------------------- | xx34My house <--+

Como puede ver, h todavía apunta a los remanentes de los datos en la memoria, pero como podría no estar completo, su uso como antes podría fallar.

Pérdida de memoria

Pierdes el pedazo de papel y no puedes encontrar la casa. Sin embargo, la casa sigue en pie en algún lugar, y cuando más adelante quiera construir una nueva, no podrá reutilizar ese lugar.

var h: THouse; begin h := THouse.Create(''My house''); h := THouse.Create(''My house''); // uh-oh, what happened to our first house? ... h.Free; h := nil;

Aquí sobrescribimos el contenido de la variable h con la dirección de una casa nueva, pero la antigua aún está en pie ... en algún lugar. Después de este código, no hay manera de llegar a esa casa, y se dejará en pie. En otras palabras, la memoria asignada permanecerá asignada hasta que la aplicación se cierre, momento en el que el sistema operativo la desarmará.

Diseño de memoria después de la primera asignación:

h v ---[ttttNNNNNNNNNN]--- 1234My house

Disposición de la memoria después de la segunda asignación:

h v ---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN] 1234My house 5678My house

Una forma más común de obtener este método es olvidarse de liberar algo, en lugar de sobrescribirlo como se indicó anteriormente. En términos de Delphi, esto ocurrirá con el siguiente método:

procedure OpenTheFrontDoorOfANewHouse; var h: THouse; begin h := THouse.Create(''My house''); h.OpenFrontDoor; // uh-oh, no .Free here, where does the address go? end;

Después de que este método se haya ejecutado, no hay lugar en nuestras variables donde exista la dirección a la casa, pero la casa todavía está ahí fuera.

Diseño de la memoria:

h <--+ v +- before losing pointer ---[ttttNNNNNNNNNN]--- | 1234My house <--+ h (now points nowhere) <--+ +- after losing pointer ---[ttttNNNNNNNNNN]--- | 1234My house <--+

Como puede ver, los datos antiguos se dejan intactos en la memoria y el asignador de memoria no los reutilizará. El asignador realiza un seguimiento de las áreas de la memoria que se han utilizado y no las reutilizará a menos que las libere.

Liberando la memoria pero manteniendo una referencia (ahora inválida)

Demuele la casa, borre una de las hojas de papel, pero también tiene otra hoja de papel con la dirección anterior, cuando vaya a la dirección, no encontrará una casa, pero podría encontrar algo que se parezca a las ruinas. de uno.

Tal vez incluso encuentre una casa, pero no es la casa a la que originalmente se le dio la dirección y, por lo tanto, cualquier intento de usarla como si le perteneciera podría fracasar horriblemente.

A veces incluso puede encontrar que una dirección vecina tiene una casa bastante grande configurada que ocupa tres direcciones (Main Street 1-3), y su dirección va hacia el centro de la casa. Cualquier intento de tratar esa parte de la gran casa de tres direcciones como una sola casa pequeña también podría fracasar horriblemente.

var h1, h2: THouse; begin h1 := THouse.Create(''My house''); h2 := h1; // copies the address, not the house ... h1.Free; h1 := nil; h2.OpenFrontDoor; // uh-oh, what happened to our house?

Aquí la casa fue demolida, a través de la referencia en h1 , y mientras que h1 se eliminó, h2 todavía tiene la dirección antigua, obsoleta. El acceso a la casa que ya no está en pie puede o no funcionar.

Esta es una variación del puntero que cuelga arriba. Ver su diseño de memoria.

Desbordamiento de búfer

Usted mueve más cosas a la casa de las que posiblemente pueda colocar, derramándose en la casa o patio de los vecinos. Cuando el propietario de la casa vecina vuelva a casa más tarde, encontrará todo tipo de cosas que considerará propias.

Esta es la razón por la que elegí una matriz de tamaño fijo. Para preparar el escenario, suponga que la segunda casa que asignamos se colocará, por alguna razón, antes que la primera en la memoria. En otras palabras, la segunda casa tendrá una dirección más baja que la primera. Además, están asignados uno al lado del otro.

Así, este código:

var h1, h2: THouse; begin h1 := THouse.Create(''My house''); h2 := THouse.Create(''My other house somewhere''); ^-----------------------^ longer than 10 characters 0123456789 <-- 10 characters

Diseño de memoria después de la primera asignación:

h1 v -----------------------[ttttNNNNNNNNNN] 5678My house

Disposición de la memoria después de la segunda asignación:

h2 h1 v v ---[ttttNNNNNNNNNN]----[ttttNNNNNNNNNN] 1234My other house somewhereouse ^---+--^ | +- overwritten

La parte que con mayor frecuencia causará el bloqueo es cuando sobrescribe partes importantes de los datos que almacenó que realmente no deberían cambiarse al azar. Por ejemplo, puede que no sea un problema que se hayan cambiado partes del nombre de h1-house, en términos de fallas en el programa, pero que sobrescriba la sobrecarga del objeto probablemente se bloquee cuando intente usar el objeto roto, al igual que sobrescribiendo enlaces que se almacenan en otros objetos en el objeto.

Listas enlazadas

Cuando sigue una dirección en un pedazo de papel, llega a una casa, y en esa casa hay otra hoja de papel con una nueva dirección, para la siguiente casa en la cadena, y así sucesivamente.

var h1, h2: THouse; begin h1 := THouse.Create(''Home''); h2 := THouse.Create(''Cabin''); h1.NextHouse := h2;

Aquí creamos un enlace desde nuestra casa hasta nuestra cabaña. Podemos seguir la cadena hasta que una casa no NextHouse referencia de NextHouse , lo que significa que es la última. Para visitar todas nuestras casas, podríamos usar el siguiente código:

var h1, h2: THouse; h: THouse; begin h1 := THouse.Create(''Home''); h2 := THouse.Create(''Cabin''); h1.NextHouse := h2; ... h := h1; while h <> nil do begin h.LockAllDoors; h.CloseAllWindows; h := h.NextHouse; end;

Diseño de la memoria (se agregó NextHouse como un enlace en el objeto, anotado con los cuatro LLLL en el siguiente diagrama):

h1 h2 v v ---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL] 1234Home + 5678Cabin + | ^ | +--------+ * (no link)

En términos básicos, ¿qué es una dirección de memoria?

Una dirección de memoria es en términos básicos sólo un número. Si piensa en la memoria como una gran variedad de bytes, el primer byte tiene la dirección 0, la siguiente la dirección 1 y así sucesivamente. Esto está simplificado, pero es lo suficientemente bueno.

Así que este diseño de memoria:

h1 h2 v v ---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN] 1234My house 5678My house

Podría tener estas dos direcciones (la más a la izquierda - es la dirección 0):

  • h1 = 4
  • h2 = 23

Lo que significa que nuestra lista de enlaces anterior podría tener este aspecto:

h1 (=4) h2 (=28) v v ---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL] 1234Home 0028 5678Cabin 0000 | ^ | +--------+ * (no link)

Es típico almacenar una dirección que "no apunta a ninguna parte" como una dirección cero.

En términos básicos, ¿qué es un puntero?

Un puntero es solo una variable que contiene una dirección de memoria. Por lo general, puede pedirle al lenguaje de programación que le dé su número, pero la mayoría de los lenguajes de programación y los tiempos de ejecución intentan ocultar el hecho de que hay un número debajo, solo porque el número en sí no tiene ningún significado para usted. Es mejor pensar en un puntero como una caja negra, es decir. realmente no sabes o no te importa cómo se implementa, siempre y cuando funcione.


No creo que los indicadores como concepto sean particularmente complicados: la mayoría de los modelos mentales de los estudiantes se relacionan con algo como esto y algunos bocetos rápidos pueden ayudar.

La dificultad, al menos lo que he experimentado en el pasado y he visto lidiar con otros, es que la gestión de los punteros en C / C ++ puede complicarse innecesariamente.


Pensé que agregaría una analogía a esta lista que me pareció muy útil al explicar los punteros (de vuelta en el día) como tutor de informática; primero, vamos a

Preparar el escenario :

Considere un estacionamiento con 3 espacios, estos espacios están numerados:

------------------- | | | | | 1 | 2 | 3 | | | | |

En cierto modo, esto es como las ubicaciones de memoria, son secuenciales y contiguos ... algo así como una matriz. En este momento no hay autos en ellos, por lo que es como una matriz vacía ( parking_lot[3] = {0} ).

Añadir los datos

Un estacionamiento nunca se queda vacío por mucho tiempo ... si lo hiciera, sería inútil y nadie construiría ninguno. Así que digamos que a medida que avanza el día, el lote se llena con 3 autos, un auto azul, un auto rojo y un auto verde:

1 2 3 ------------------- | o=o | o=o | o=o | | |B| | |R| | |G| | | o-o | o-o | o-o |

Estos carros son todos del mismo tipo (carros), por lo que una forma de pensar esto es que nuestros carros son algún tipo de información (por ejemplo, un int ) pero tienen valores diferentes ( blue , red , green ; eso podría ser una enum color).

Introduzca el puntero

Ahora, si te llevo a este estacionamiento y te pido que me encuentres un automóvil azul, extiendes un dedo y lo usas para apuntar a un automóvil azul en el lugar 1. Esto es como tomar un puntero y asignarlo a una dirección de memoria ( int *finger = parking_lot )

Tu dedo (el puntero) no es la respuesta a mi pregunta. Mirar su dedo no me dice nada, pero si miro hacia donde apunta el dedo (no referenciamos el puntero), puedo encontrar el automóvil (los datos) que estaba buscando.

Reasignando el puntero

Ahora puedo pedirte que busques un auto rojo y puedes redirigir tu dedo a un auto nuevo. Ahora su indicador (el mismo que antes) me muestra datos nuevos (el lugar donde se puede encontrar el auto rojo) del mismo tipo (el auto).

El puntero no ha cambiado físicamente, sigue siendo tu dedo, solo los datos que me mostraban han cambiado. (la dirección del "lugar de estacionamiento")

Punteros dobles (o un puntero a un puntero)

Esto funciona con más de un puntero también. Puedo preguntar dónde está el puntero, que apunta al carro rojo, y puedes usar tu otra mano y señalar con el dedo hacia el primer dedo. (esto es como int **finger_two = &finger )

Ahora, si quiero saber dónde está el carro azul, puedo seguir la dirección del primer dedo hacia el segundo dedo, hacia el carro (los datos).

El puntero que cuelga

Ahora digamos que te sientes como una estatua, y quieres sostener tu mano apuntando al auto rojo por tiempo indefinido. ¿Y si ese auto rojo se aleja?

1 2 3 ------------------- | o=o | | o=o | | |B| | | |G| | | o-o | | o-o |

Tu puntero sigue apuntando a donde estaba el auto rojo pero ya no está. Digamos que un carro nuevo se detiene allí ... un carro naranja. Ahora, si te vuelvo a preguntar "dónde está el auto rojo", sigues apuntando hacia allí, pero ahora estás equivocado. Eso no es un coche rojo, eso es naranja.

Aritmética de punteros

Ok, así que todavía estás apuntando al segundo lugar de estacionamiento (ahora ocupado por el auto Orange)

1 2 3 ------------------- | o=o | o=o | o=o | | |B| | |O| | |G| | | o-o | o-o | o-o |

Bueno, ahora tengo una nueva pregunta ... Quiero saber el color del auto en el próximo estacionamiento. Puedes ver que estás apuntando al punto 2, así que solo agregas 1 y estás apuntando al siguiente punto. ( finger+1 ), ya que quería saber qué datos estaban allí, tienes que verificar ese punto (no solo el dedo) para poder deferir el puntero ( *(finger+1) ) para ver que hay un verde Coche presente allí (los datos en ese lugar)


Una analogía que he encontrado útil para explicar los punteros es hipervínculos. La mayoría de las personas pueden entender que un enlace en una página web "apunta" a otra página en Internet, y si puede copiar y pegar ese hipervínculo, ambos apuntarán a la misma página web original. Si va y edita esa página original, siga uno de esos enlaces (punteros) y obtendrá la página actualizada.


Un ejemplo de un tutorial con un buen conjunto de diagramas ayuda mucho con la comprensión de los punteros .

Joel Spolsky hace algunos buenos puntos sobre la comprensión de los indicadores en su artículo de Guerrilla to Interviewing :

Por alguna razón, la mayoría de las personas parecen nacer sin la parte del cerebro que entiende los indicadores. Esto es una cuestión de aptitud, no de habilidad, requiere una forma compleja de pensamiento indirectamente doble que algunas personas simplemente no pueden hacer.