¿Por qué los indicadores son un factor de confusión tan importante para muchos estudiantes nuevos, e incluso antiguos, de nivel universitario en C o C ++? ¿Existen herramientas o procesos de pensamiento que lo ayudaron a comprender cómo funcionan los punteros a nivel variable, funcional y más allá?
¿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 empantanarlo en el concepto general? Básicamente, perforar como escenarios.
Respuestas:
Los punteros son un concepto que para muchos puede ser confuso al principio, en particular cuando se trata de copiar valores de puntero y aún hacer referencia al mismo bloque de memoria.
He descubierto que la mejor analogía es considerar el puntero como un trozo de papel con una dirección de la casa y el bloque de memoria al que hace referencia como la casa real. Todo tipo de operaciones se pueden explicar fácilmente.
He agregado un código de Delphi a continuación, y algunos comentarios cuando corresponde. Elegí Delphi porque mi otro lenguaje de programación principal, C #, no exhibe cosas como pérdidas de memoria de la misma manera.
Si solo desea aprender el concepto de alto nivel de punteros, debe ignorar las partes etiquetadas como "Diseño de memoria" en la explicación a continuación. Su objetivo es dar ejemplos de cómo se vería la memoria después de las operaciones, pero son de naturaleza de nivel más bajo. Sin embargo, para explicar con precisión cómo funcionan realmente los desbordamientos del búfer, fue importante que agregue estos diagramas.
Descargo de responsabilidad: para todos los efectos, 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, para los intentos de explicar la memoria y los punteros, es lo suficientemente preciso.
Supongamos que la clase THouse utilizada a continuación se ve así:
Cuando inicializa el objeto de la 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, ilustraré esto a continuación de esta manera:
El área "tttt" está sobrecargada, 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 otra cosa que no sea el asignador de memoria o las rutinas centrales del sistema, o corre el riesgo de bloquear el programa.
Asignar memoria
Haz que un emprendedor construya tu casa y te dé la dirección de la casa. A diferencia del mundo real, no se puede decir a la asignación de memoria 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.
Diseño de memoria:
Mantenga una variable con la dirección
Escriba la dirección de su nueva casa en una hoja de papel. Este documento servirá como 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.
Diseño de memoria:
Copiar valor del puntero
Simplemente escriba la dirección en una nueva hoja de papel. Ahora tiene dos hojas de papel que lo llevarán a la misma casa, no a dos casas separadas. Cualquier intento de seguir la dirección de un documento y reorganizar los muebles en esa casa hará que parezca que la otra casa ha sido modificada de la misma manera, a menos que pueda detectar explícitamente que en realidad es solo una casa.
Nota: Este es generalmente el concepto que tengo más problemas para explicar a las personas, dos punteros no significa dos objetos o bloques de memoria.
Liberando la memoria
Demoler la casa. Luego, puede reutilizar el papel para una nueva dirección si así lo desea, o borrarlo para olvidar la dirección de la casa que ya no existe.
Aquí primero construyo la casa y obtengo su dirección. Luego hago algo a la casa (lo uso, el ... código, lo dejé como ejercicio para el lector), y luego lo libero. Por último, borro la dirección de mi variable.
Diseño de memoria:
Punteros colgantes
Le dice a su empresario que destruya la casa, pero se olvida de borrar la dirección de su hoja de papel. Cuando más tarde mira el pedazo de papel, ha olvidado que la casa ya no está allí y va a visitarlo, con resultados fallidos (vea también la parte sobre una referencia no válida a continuación).
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.Como puede ver, h todavía apunta a los remanentes de los datos en la memoria, pero dado que puede no estar completo, su uso como antes podría fallar.
Pérdida de memoria
Pierdes el papel y no puedes encontrar la casa. Sin embargo, la casa todavía está parada en algún lugar, y cuando más tarde quieras construir una nueva casa, no puedes reutilizar ese lugar.
Aquí sobrescribimos el contenido de la
h
variable con la dirección de una casa nueva, pero la antigua todavía está en pie ... en algún lugar. Después de este código, no hay forma de llegar a esa casa, y se dejará en pie. En otras palabras, la memoria asignada permanecerá asignada hasta que se cierre la aplicación, momento en el cual el sistema operativo la destruirá.Diseño de memoria después de la primera asignación:
Diseño de memoria después de la segunda asignación:
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:
Después de que este método se haya ejecutado, no hay lugar en nuestras variables donde exista la dirección de la casa, pero la casa todavía está ahí afuera.
Diseño de memoria:
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 memoria que se han utilizado y no las reutilizará a menos que lo libere.
Liberando la memoria pero manteniendo una referencia (ahora no válida)
Demoler la casa, borrar 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 puede encontrar algo que se parezca a las ruinas de uno.
Tal vez incluso encuentre una casa, pero no es la casa a la que se le dio la dirección originalmente, y por lo tanto, cualquier intento de usarla como si le perteneciera podría fallar horriblemente.
A veces, incluso puede encontrar que una dirección vecina tiene una casa bastante grande que ocupa tres direcciones (Main Street 1-3), y su dirección va al centro de la casa. Cualquier intento de tratar esa parte de la gran casa de 3 direcciones como una sola casa pequeña también podría fallar horriblemente.
Aquí la casa fue demolida, a través de la referencia
h1
, y aunque tambiénh1
fue limpiada,h2
todavía tiene la dirección antigua y desactualizada. El acceso a la casa que ya no está en pie podría o no funcionar.Esta es una variación del puntero colgante anterior. Ver su diseño de memoria.
Tampón desbordado
Mueves más cosas a la casa de las que puedes caber, derramando en la casa o patio de los vecinos. Cuando el dueño de esa casa vecina más tarde vuelva a casa, encontrará todo tipo de cosas que considerará suyas.
Esta es la razón por la que elegí una matriz de tamaño fijo. Para establecer el escenario, suponga que la segunda casa que asignamos, por alguna razón, se colocará 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.
Por lo tanto, este código:
Diseño de memoria después de la primera asignación:
Diseño de memoria después de la segunda asignación:
La parte que con mayor frecuencia provocará un bloqueo es cuando sobrescribe partes importantes de los datos que almacenó que realmente no deberían modificarse al azar. Por ejemplo, podría no ser un problema que se cambiaran partes del nombre de la casa h1, en términos de bloquear el programa, pero sobreescribir la sobrecarga del objeto probablemente se bloqueará cuando intente usar el objeto roto, como lo hará sobrescribir enlaces que se almacenan en otros objetos en el objeto.
Listas enlazadas
Cuando sigue una dirección en una hoja 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.
Aquí creamos un enlace desde nuestra casa hasta nuestra cabaña. Podemos seguir la cadena hasta que una casa no tenga
NextHouse
referencia, lo que significa que es la última. Para visitar todas nuestras casas, podríamos usar el siguiente código:Diseño de memoria (se agregó NextHouse como un enlace en el objeto, indicado con los cuatro LLLL en el siguiente diagrama):
En términos básicos, ¿qué es una dirección de memoria?
Una dirección de memoria es, en términos básicos, solo un número. Si piensa en la memoria como una gran variedad de bytes, el primer byte tiene la dirección 0, el siguiente la dirección 1 y así sucesivamente. Esto es simplificado, pero lo suficientemente bueno.
Entonces este diseño de memoria:
Puede tener estas dos direcciones (la más a la izquierda - es la dirección 0):
Lo que significa que nuestra lista de enlaces anterior podría verse así:
Es típico almacenar una dirección que "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 tiempos de ejecución intenta 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 un cuadro negro, es decir. usted realmente no sabe ni le importa cómo se implementa realmente, siempre y cuando funcione.
fuente
En mi primera clase de Comp Sci, hicimos el siguiente ejercicio. De acuerdo, esta era una sala de conferencias con aproximadamente 200 estudiantes en ella ...
El profesor escribe en la pizarra:
int john;
John se pone de pie
El profesor escribe:
int *sally = &john;
Sally se pone de pie y señala a John
Profesor:
int *bill = sally;
Bill se pone de pie y señala a John
Profesor:
int sam;
Sam se pone de pie
Profesor:
bill = &sam;
Bill ahora señala 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.
fuente
Una analogía que he encontrado útil para explicar los punteros son los hipervínculos. La mayoría de la gente puede 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 cualquiera de esos enlaces (punteros) y obtendrá esa nueva página actualizada.
fuente
int *a = b
que no hace dos copias*b
).La razón por la cual 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 simple basada en bytecode (en cualquier idioma que elijan, python funciona muy bien para esto) con un conjunto de instrucciones enfocado 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 un poco más que una simple adición implicará punteros y seguramente lo obtendrán.
fuente
El concepto de un marcador de posición para un valor - variables - se asigna a algo que nos enseñan en la escuela - álgebra. No hay un paralelismo existente que pueda dibujar sin comprender cómo la memoria está físicamente distribuida dentro de una computadora, y nadie piensa en este tipo de cosas hasta que se trata de cosas de bajo nivel, en el nivel de comunicaciones C / C ++ / byte .
Direcciones de cajas. Recuerdo que cuando estaba aprendiendo a programar BASIC en microcomputadoras, había estos bonitos libros con juegos, y a veces había que meter valores en direcciones particulares. Tenían una imagen de un montón de cajas, etiquetadas incrementalmente con 0, 1, 2 ... y se explicó que solo una pequeña cosa (un byte) podía caber en estas cajas, y había muchas de ellas, algunas computadoras tenía hasta 65535! Estaban uno al lado del otro, y todos tenían una dirección.
Para un simulacro? Haz una estructura:
Mismo ejemplo que el anterior, excepto en C:
Salida:
¿Quizás eso explica algunos de los conceptos básicos a través del ejemplo?
fuente
La razón por la que tuve dificultades para comprender los punteros, al principio, es que muchas explicaciones incluyen mucha basura sobre pasar por referencia. Todo lo que hace es confundir el problema. Cuando usa un parámetro de puntero, todavía está pasando por valor; pero el valor es una dirección en lugar de, por ejemplo, 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
En el momento en que leí estas palabras, las nubes se separaron y un rayo de sol me envolvió con la comprensión del puntero.
Incluso si usted es un desarrollador de VB .NET o C # (como yo) y nunca usa código inseguro, vale la pena entender cómo funcionan los punteros, o no entenderá cómo funcionan las referencias a objetos. Entonces tendrá la noción común pero equivocada de que pasar una referencia de objeto a un método copia el objeto.
fuente
El "Tutorial sobre punteros y matrices en C" de Ted Jensen es un excelente recurso para aprender sobre 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 redes de Beej enseña la API de sockets de Unix, desde la cual puede comenzar a hacer cosas realmente divertidas. http://beej.us/guide/bgnet/
fuente
Las complejidades de los punteros van más allá de lo que podemos enseñar fácilmente. Hacer que los estudiantes se señalen entre sí y usar trozos de papel con las direcciones de las casas son herramientas de aprendizaje excelentes. Hacen un gran trabajo al presentar los conceptos básicos. De hecho, aprender los conceptos básicos es vital para utilizar con éxito los punteros. Sin embargo, en el código de producción, es común entrar en escenarios mucho más complejos de lo que pueden encapsular estas simples demostraciones.
He estado involucrado con sistemas donde teníamos estructuras que apuntaban a otras estructuras que apuntaban a otras estructuras. Algunas de esas estructuras también contenían estructuras incrustadas (en lugar de punteros a estructuras adicionales). Aquí es donde los punteros se vuelven realmente confusos. Si tiene múltiples niveles de indirección y comienza a terminar con un código como este:
puede volverse confuso realmente rápido (imagine muchas más líneas y potencialmente más niveles). Agregue conjuntos de punteros y punteros de nodo a nodo (árboles, listas vinculadas) y esto empeora aún más. He visto a algunos desarrolladores realmente buenos perderse una vez que comenzaron a trabajar en tales sistemas, incluso 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 coinciden entre sí en estilo o técnica. En situaciones como esa, la complejidad va a surgir naturalmente (aunque ciertamente, debemos 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 punteros. Un proyecto difícil hará más por la comprensión del puntero que mil demostraciones. Las demostraciones pueden obtener una comprensión superficial, pero para comprender los punteros, realmente debe usarlos.
fuente
Pensé que agregaría una analogía a esta lista que encontré muy útil al explicar los punteros (en el pasado) como Tutor de Informática; primero, vamos a:
Prepara el escenario :
Considere un estacionamiento con 3 espacios, estos espacios están numerados:
En cierto modo, esto es como ubicaciones de memoria, son secuenciales y contiguas ... 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}
).Agregar los datos
Un estacionamiento nunca permanece vacío por mucho tiempo ... si lo hiciera, no tendría sentido y nadie construiría ninguno. Entonces, 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:
Estos coches son todos del mismo tipo (coche) así que una forma de pensar de esto es que nuestros coches son una especie de datos (digamos una
int
) pero tienen valores diferentes (blue
,red
,green
, que podría ser de un colorenum
)Introduce el puntero
Ahora, si te llevo a este estacionamiento y te pido que me encuentres un auto azul, extiendes un dedo y lo usas para señalar un auto 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. Buscando en el dedo me ha dicho nada, pero si miro en el que usted dedo está apuntando a (dereferencing el puntero), que se puede encontrar el coche (los datos) que estaba buscando.
Reasignando el puntero
Ahora puedo pedirte que encuentres un auto rojo y puedes redirigir tu dedo a un auto nuevo. Ahora su puntero (el mismo que antes) me muestra nuevos datos (el lugar de estacionamiento donde se puede encontrar el automóvil rojo) del mismo tipo (el automóvil).
El puntero no ha cambiado físicamente, sigue siendo tu dedo, solo cambiaron los datos que me mostraban. (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 automóvil rojo, y puedes usar la otra mano y señalar con el dedo el primer dedo. (esto es como
int **finger_two = &finger
)Ahora, si quiero saber dónde está el automóvil azul, puedo seguir la dirección del primer dedo hacia el segundo dedo, hacia el automóvil (los datos).
El puntero colgante
Ahora digamos que te sientes muy parecido a una estatua, y quieres mantener tu mano apuntando al auto rojo indefinidamente. ¿Qué pasa si ese auto rojo se va?
El puntero sigue apuntando a que el coche rojo era pero ya no lo es. Digamos que un auto nuevo llega allí ... un auto Orange. Ahora, si te pregunto de nuevo, "¿dónde está el auto rojo", todavía estás señalando allí, pero ahora te equivocas. Eso no es un auto rojo, es naranja.
Aritmética de puntero
Ok, todavía estás apuntando al segundo lugar de estacionamiento (ahora ocupado por el auto Orange)
Bueno, ahora tengo una nueva pregunta ... Quiero saber el color del auto en el próximo lugar de estacionamiento. Puedes ver que estás apuntando al punto 2, así que solo agregas 1 y estás apuntando al siguiente punto. (
finger+1
), ahora ya que quería saber cuáles eran los datos allí, debe verificar ese punto (no solo el dedo) para poder deducir el puntero (*(finger+1)
) para ver que hay un automóvil verde presente allí (los datos en esa ubicación )fuente
"without getting them bogged down in the overall concept"
como un entendimiento de alto nivel. Y a su punto:"I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"
- se sorprendería mucho de cuántas personas no entienden los punteros, incluso a este nivelNo creo que los punteros como concepto sean particularmente complicados: la mayoría de los modelos mentales de los estudiantes se asignan a algo así y algunos bocetos rápidos pueden ayudar.
La dificultad, al menos la que he experimentado en el pasado y que he visto lidiar con otros, es que la gestión de los punteros en C / C ++ puede ser complicada sin necesidad.
fuente
Un ejemplo de un tutorial con un buen conjunto de diagramas ayuda enormemente a comprender los punteros .
Joel Spolsky hace algunos buenos puntos sobre la comprensión de los punteros en su artículo de la Guía de Guerrilla para la Entrevista :
fuente
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 suponen que es el CONCEPTO de los punteros lo que es difícil, y no la jerga, o el complicado enredo que C y C ++ hacen del concepto. Se emplean grandes cantidades de esfuerzo para explicar el concepto (como en la respuesta aceptada para esta pregunta) y casi se desperdicia en alguien como yo, porque ya entiendo todo eso. Simplemente explica la parte incorrecta del problema.
Para darle una idea de dónde vengo, soy alguien que entiende perfectamente los punteros y puedo usarlos de manera competente en lenguaje ensamblador. Porque en lenguaje ensamblador no se los conoce como punteros. Se les conoce como direcciones. Cuando se trata de programar y usar punteros en C, cometo muchos errores y realmente me confundo. Todavía no he resuelto esto. Dejame darte un ejemplo.
Cuando una api dice:
que quiere
podría querer:
un número que representa una dirección a un búfer
(Para decir eso, ¿digo
doIt(mybuffer)
, odoIt(*myBuffer)
?)un número que representa la dirección a una dirección a un búfer
(¿es eso
doIt(&mybuffer)
odoIt(mybuffer)
odoIt(*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 esdoIt(&&mybuffer)
? o inclusodoIt(&&&mybuffer)
)y así sucesivamente, y el lenguaje involucrado no lo deja tan claro porque involucra las palabras "puntero" y "referencia" que no tienen tanto significado y claridad para mí como "x contiene la dirección para y" y " esta función requiere una dirección para y ". La respuesta, además, depende de en qué consiste el "mybuffer", y qué pretende hacer con él. 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, por lo que sabe a dónde ir para modificar el contenido del puntero? La mayoría de las veces solo tengo que adivinar qué se entiende por "
"Puntero" está 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 quiere un puntero, ¿quiere la dirección que contiene la variable de puntero o la dirección de la variable de puntero? Estoy confundido.
fuente
double *(*(*fn)(int))(char)
, entonces el resultado de la evaluación*(*(*fn)(42))('x')
será adouble
. Puede quitar capas de evaluación para comprender cuáles deben ser los tipos intermedios.(*(*fn)(42))('x')
entonces?x
) donde, si evalúas*x
, obtienes un doble.fn
es y más en términos de lo que puede hacer con ustedfn
Creo que la principal barrera para entender los punteros son los malos maestros.
A casi todos se les enseñan mentiras sobre los punteros: que no son más que direcciones de memoria , o que te permiten señalar ubicaciones arbitrarias .
Y, por supuesto, son difíciles de entender, peligrosos y semimágicos.
Nada de lo cual es cierto. Los punteros son en realidad conceptos bastante simples, siempre y cuando se apegue a lo que el lenguaje C ++ tiene que decir sobre ellos y no los imbuya con atributos que "generalmente" funcionan en la práctica, pero que no están garantizados por el lenguaje. , por lo que no son parte del concepto real de un puntero.
Traté de escribir una explicación de esto hace unos meses en esta publicación de blog , espero que ayude a alguien.
(Tenga en cuenta que antes de que alguien se vuelva pedante conmigo, sí, el estándar 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 la memoria direcciones ". La distinción es importante)
fuente
Creo que lo que hace que los punteros sean difíciles de aprender es que hasta los punteros te sientes cómodo con la idea de que "en esta ubicación de memoria hay un conjunto de bits que representan un int, un doble, un personaje, lo que sea".
Cuando ve un puntero por primera vez, realmente no obtiene lo que hay 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 obtienes o no".
Se vuelven más fáciles de entender cuando comienzas a encontrar usos reales para ellos (como no pasar estructuras grandes a funciones).
fuente
La razón por la que es tan difícil de entender no es porque es un concepto difícil sino porque la sintaxis es inconsistente .
Primero se enteró de que la parte más a la izquierda de una creación de variable define el tipo de la variable. La declaración de puntero no funciona así en C y C ++. En cambio, dicen que la variable apunta al tipo a la izquierda. En este caso:
*
mypointer está apuntando a un int.No entendí completamente los punteros hasta que intenté usarlos en C # (con inseguro), funcionan exactamente de la misma manera pero con una sintaxis lógica y consistente. El puntero es un tipo en sí mismo. Aquí mypointer es un puntero a un int.
Ni siquiera me hagas empezar con los punteros de funciones
fuente
int *p;
tiene un significado simple:*p
es un número entero.int *p, **pp
significa:*p
y**pp
son enteros.*p
y**pp
son no enteros, porque nunca se ha inicializadop
opp
o*pp
a punto para cualquier cosa. Entiendo por qué algunas personas prefieren apegarse a la gramática en este caso, particularmente porque algunos casos extremos y casos complejos requieren que lo hagas (aunque, aun así, puedes evitarlo trivialmente en todos los casos que conozco) ... pero no creo que esos casos sean más importantes que el hecho de que enseñar la alineación correcta es engañoso para los novatos. ¡Sin mencionar que es feo! :)Podría trabajar con punteros cuando solo conocía C ++. Como que sabía qué hacer en algunos casos y qué no hacer por prueba / error. Pero lo que me dio una comprensión completa es el lenguaje ensamblador. Si realiza una depuración seria del nivel de instrucción con un programa de lenguaje ensamblador que ha escrito, debería ser capaz de comprender muchas cosas.
fuente
Me gusta la analogía de la dirección de la casa, pero siempre he pensado en la dirección del buzón. De esta forma, puede visualizar el concepto de desreferenciar el puntero (abrir el buzón).
Por ejemplo, siguiendo una lista vinculada: 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 pedazo de papel con la siguiente dirección.
En una lista vinculada lineal, el último buzón no tiene nada (final de la lista). En una lista circular vinculada, 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 saldrá mal cuando la dirección no sea válida. Suponiendo que pueda caminar hasta el buzón de una dirección no válida, imagine que hay un agujero negro o algo allí que da la vuelta al mundo :)
fuente
Creo que la razón principal por la que la gente tiene problemas es porque generalmente no se enseña de una manera interesante y atractiva. Me gustaría ver a un profesor obtener 10 voluntarios de la multitud y darles una regla de 1 metro cada uno, hacer que se paren en una determinada configuración y usar las reglas para señalarse entre sí. Luego muestre la aritmética del puntero moviendo a las personas (y donde señalan sus reglas). Sería una forma simple pero efectiva (y sobre todo memorable) de mostrar los conceptos sin atascarse demasiado en la mecánica.
Una vez que llega 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 en práctica la teoría de que no comprenden adecuadamente o porque la manipulación del puntero es inherentemente más difícil en esos idiomas. No recuerdo bien mi propia transición, pero conocía los indicadores en Pascal y luego me mudé a C y me perdí por completo.
fuente
No creo que los punteros sean confusos. La mayoría de la gente puede entender el concepto. Ahora, ¿en cuántos punteros puede pensar o con cuántos niveles de indirección se siente cómodo? No se necesitan muchos para poner a la gente al límite. El hecho de que pueden ser modificados accidentalmente por errores en su programa también puede hacer que sea muy difícil de depurar cuando las cosas salen mal en su código.
fuente
Creo que en realidad podría ser un problema de sintaxis. La sintaxis de C / C ++ para punteros parece inconsistente y más compleja de lo necesario.
Irónicamente, lo que realmente me ayudó a comprender los punteros fue encontrar el concepto de un iterador en la Biblioteca de plantillas estándar de c ++ . Es irónico porque solo puedo suponer que los iteradores fueron concebidos como una generalización del puntero.
A veces no puedes ver el bosque hasta que aprendes a ignorar los árboles.
fuente
(*p)
hubiera sido(p->)
, y por lo tanto tendríamos quep->->x
en lugar de la ambigua*p->x
a->b
simplemente significa(*a).b
.* p->x
significa* ((*a).b)
mientras que*p -> x
significa(*(*p)) -> x
. La combinación de operadores de prefijo y postfijo produce un análisis ambiguo.1+2 * 3
debería ser 9.La confusión proviene de las múltiples capas de abstracción mezcladas en el concepto de "puntero". Los programadores no se confunden con las referencias ordinarias en Java / Python, pero los punteros son diferentes porque exponen las características de la arquitectura de memoria subyacente.
Es un buen principio separar limpiamente las capas de abstracción, y los punteros no lo hacen.
fuente
foo[i]
significa ir a cierto lugar, avanzar una cierta distancia y ver qué hay allí. Lo que complica las cosas es la capa de abstracción adicional mucho más complicada que fue agregada por el Estándar únicamente para beneficio del compilador, pero modela las cosas de una manera que no se ajusta a las necesidades del programador y las necesidades del compilador por igual.La forma en que me gustaba explicarlo era en términos de matrices e índices: las personas pueden no estar familiarizadas con los punteros, pero generalmente saben qué es un índice.
Así que digo imagina que la RAM es una matriz (y solo tienes 10 bytes de RAM):
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í mismoRAM[RAM[index]]
.Dibujaría una matriz en una lista de papel y la usaría para mostrar cosas como muchos punteros que apuntan a la misma memoria, aritmética de puntero, puntero a puntero, etc.
fuente
Número de apartado de correos.
Es una información que le permite acceder a otra cosa.
(Y si hace cálculos aritméticos en los números del apartado de correos, puede tener un problema, porque la letra va en el cuadro 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).
fuente
No es una mala manera de entenderlo, a través de iteradores ... pero sigue buscando verás a Alexandrescu comenzar a quejarse de ellos.
Muchos desarrolladores anteriores de C ++ (que nunca entendieron que los iteradores son un puntero moderno antes de abandonar el lenguaje) saltan a C # y aún creen que tienen iteradores decentes.
Hmm, el problema es que todo lo que son los iteradores está en completo desacuerdo con lo que las plataformas de tiempo de ejecución (Java / CLR) están tratando de lograr: un uso nuevo, simple y para todos los desarrolladores. 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 lo es si lo haces por completo. Los iteradores son útiles ya que ayudan con la abstracción de algoritmos, otro ejemplo. Y el tiempo de compilación es el lugar para un algoritmo, muy simple. Sabes código + datos, o en ese otro idioma C #:
IEnumerable + LINQ + Massive Framework = 300 MB de tiempo de ejecución penalización indirecta de pésimo, arrastrando aplicaciones a través de montones de instancias de tipos de referencia.
"Le Pointer es barato".
fuente
Algunas respuestas anteriores han afirmado que "los punteros no son realmente difíciles", pero no se han dirigido directamente a donde "los punteros son difíciles". viene de. Hace algunos años fui tutor de estudiantes de CS de primer año (durante solo un año, ya que claramente lo chupé) y me quedó claro que la idea del puntero no es difícil. Lo difícil es entender por qué y cuándo querrías un puntero .
No creo que pueda divorciarse de esa pregunta, por qué y cuándo usar un puntero, de explicar problemas más amplios de ingeniería de software. 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, consiga esto, use punteros para especializar su comportamiento en su sitio de llamadas).
fuente
No veo qué es tan confuso sobre los punteros. Apuntan a una ubicación en la memoria, es decir, almacena la dirección de memoria. En C / C ++ puede especificar el tipo al que apunta el puntero. Por ejemplo:
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 ir a alguna ubicación en la que no debería estar. Como prueba, observe los numerosos agujeros de seguridad en las aplicaciones C / C ++ debido al desbordamiento del búfer (incrementando el puntero más allá del límite asignado).
fuente
Solo para confundir un poco más las cosas, a veces tienes que trabajar con manijas en lugar de punteros. Los identificadores son punteros a punteros, de modo que el back-end puede mover cosas en la memoria para desfragmentar el montón. Si el puntero cambia en la mitad de la rutina, los resultados son impredecibles, por lo que primero debe bloquear el controlador para asegurarse de que nada vaya a ninguna parte.
http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 habla un poco más coherentemente que yo. :-)
fuente
Todos los principiantes de C / C ++ tienen el mismo problema y ese problema ocurre no porque "los punteros son difíciles de aprender" sino "quién y cómo se explica". Algunos alumnos lo recopilan verbalmente, algo visualmente y la mejor manera de explicarlo es usar el ejemplo de "entrenamiento" (trajes para el 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í, ¿puede contener animales, plantas o personas (o una mezcla de ellos).
fuente