que - En C++ no puedo captar punteros y clases
punteros como parametros de funciones en c (27)
¿Leíste el lenguaje de programación C ++ de Bjarne Stroustrup? Él creó C ++.
El C ++ FAQ Lite también es bueno.
Estoy recién salido de la universidad y he trabajado en C ++ desde hace un tiempo. Entiendo todos los conceptos básicos de C ++ y los uso, pero estoy teniendo dificultades para captar temas más avanzados, como punteros y clases. He leído algunos libros y tutoriales y entiendo los ejemplos en ellos, pero cuando veo algunos ejemplos avanzados de la vida real no los puedo descifrar. Esto me está matando porque siento que me está impidiendo llevar mi programación en C ++ al siguiente nivel. ¿Alguien más tiene este problema? Si es así, ¿cómo lo lograste? ¿Alguien sabe de algún libro o tutorial que realmente describa bien los consejos y los conceptos de clase? o tal vez algún código de ejemplo con buenos comentarios descriptivos usando punteros avanzados y técnicas de clase? cualquier ayuda sería muy apreciada.
Aprende el lenguaje ensamblador y luego aprende C. Entonces sabrás cuáles son los principios subyacentes de la máquina (y, por lo tanto, los punteros).
Los punteros y las clases son aspectos fundamentales de C ++. Si no los entiendes, significa que realmente no entiendes C ++.
Personalmente me reprimí en C ++ durante varios años hasta que sentí que tenía una comprensión firme de C y lo que estaba sucediendo bajo el capó en lenguaje ensamblador. Aunque esto fue hace bastante tiempo, ahora creo que realmente benefició mi carrera entender cómo funciona la computadora a un nivel bajo.
Aprender a programar puede tomar muchos años, pero debes seguir con esto porque es una carrera muy gratificante.
El libro que me descubrió los indicadores fue Illustrating Ansi C de Donald Alcock. Está lleno de diagramas de flecha y caja dibujados a mano que ilustran punteros, aritmética de punteros, matrices, funciones de cuerda, etc.
Obviamente es un libro ''C'', pero para los fundamentos básicos es difícil de superar
El mejor libro que he leído sobre estos temas es Thinking in C ++ de Bruce Eckel. Puedes descargarlo gratis aquí .
El punto en el que realmente obtuve los punteros fue la codificación de TurboPascal en un FatMac (alrededor de 1984 más o menos), que era el lenguaje Mac nativo en ese momento.
La Mac tenía un modelo de memoria extraño por el cual cuando se asignaba la dirección la memoria se almacenaba en un puntero en el montón, pero la ubicación de eso no estaba garantizada y en su lugar las rutinas de manejo de memoria devolvían un puntero al puntero - referido como un asa . En consecuencia, para acceder a cualquier parte de la memoria asignada, era necesario desreferenciar el asa dos veces. Tomó un tiempo, pero la práctica constante finalmente llevó la lección a casa.
El manejo del puntero de Pascal es más fácil de comprender que C ++, donde la sintaxis no ayuda al principiante. Si realmente está atrapado entendiendo punteros en C, entonces su mejor opción podría ser obtener una copia de un compilador de Pascal e intentar escribir algún código de puntero básico en él (Pascal está lo suficientemente cerca de C obtendrá los conceptos básicos en unas pocas horas). ) Las listas enlazadas y similares serían una buena opción. Una vez que te sientas cómodo con los que regresan a C ++ y con los conceptos dominados, encontrarás que el acantilado no se verá tan escarpado.
En cierto sentido, puede considerar "punteros" como uno de los dos tipos más fundamentales de software, el otro como "valores" (o "datos") que existen en un gran bloque de ubicaciones de memoria direccionables de manera única. Piénsalo. Los objetos y las estructuras, etc., realmente no existen en la memoria, solo los valores y punteros lo hacen. De hecho, un puntero también es un valor ... el valor de una dirección de memoria, que a su vez contiene otro valor ... y así sucesivamente.
Entonces, en C / C ++, cuando declaras un "int" (intA), estás definiendo un fragmento de 32 bits de memoria que contiene un valor, un número. Si luego declara un "puntero int" (intB), está definiendo un fragmento de 32 bits de memoria que contiene la dirección de un int. Puedo asignar este último para que apunte al primero indicando "intB = & intA", y ahora los 32 bits de memoria definidos como intB, contienen una dirección correspondiente a la ubicación de intA en la memoria.
Cuando "desreferencia" el puntero intB, está mirando la dirección almacenada en la memoria del intB, buscando esa ubicación y luego mirando el valor almacenado allí (un número).
Comúnmente, he encontrado confusión cuando las personas pierden la noción de exactamente qué es lo que están tratando, ya que usan los operadores "&", "*" y "->" - ¿es una dirección, un valor o qué? Solo necesita mantenerse enfocado en el hecho de que las direcciones de memoria son simplemente ubicaciones, y que los valores son la información binaria almacenada allí.
Estábamos discutiendo algunos de los aspectos de C ++ y OO en el almuerzo, alguien (un gran ingeniero en realidad) estaba diciendo que, a menos que tenga una sólida experiencia en programación antes de aprender C ++, literalmente lo arruinará.
Recomiendo aprender otro idioma primero, luego cambiar a C ++ cuando lo necesite. No es que los punteros tengan algo grandioso, simplemente son una pieza residual de cuando era difícil para un compilador convertir las operaciones en un ensamblaje eficiente sin ellas.
Actualmente, si un compilador no puede optimizar una operación de matriz mejor que utilizando punteros, su compilador está roto.
Por favor, no me malinterpreten, no digo que C ++ sea horrible ni nada por el estilo y no quiero iniciar una discusión de defensa, lo he usado y lo uso de vez en cuando, solo te recomiendo que comiences con otra cosa. .
Realmente no es como aprender a manejar un auto manual y luego poder aplicarlo fácilmente a una automática, es más como aprender a conducir en una de esas enormes grúas de construcción y luego asumir que se aplicará cuando comiences a conducir un auto. se encuentra manejando su auto en el medio de la calle a 5 mph con las luces de emergencia encendidas.
[edit] repasando ese último párrafo - ¡Creo que esa puede haber sido la analogía más precisa que he tenido!
Las clases son relativamente fáciles de entender; OOP puede llevarle muchos años. Personalmente, no entendí completamente la POO verdadera hasta el año pasado. Es una lástima que Smalltalk no esté tan extendido en las universidades como debería ser. Realmente lleva a casa el punto de que OOP trata de objetos que intercambian mensajes, en lugar de que las clases sean variables globales autónomas con funciones.
Si realmente eres nuevo en las clases, entonces el concepto puede tomar un tiempo para comprenderlo. Cuando los encontré por primera vez en el 10 ° grado, no lo conseguí hasta que tuve a alguien que sabía lo que estaban haciendo, paso por el código y explica lo que estaba pasando. Eso es lo que sugiero que intentes.
Los punteros y las clases son temas completamente diferentes, así que no los agruparía juntos así. De los dos, diría que los indicadores son más fundamentales.
Un buen ejercicio para aprender sobre qué son los indicadores es el siguiente:
- crea una lista vinculada
- iterar a través de él de principio a fin
- invertirlo para que la cabeza ahora es la espalda y la parte posterior es ahora la cabeza
Hazlo todo en una pizarra primero. Si puede hacer esto fácilmente, no debería tener más problemas para comprender qué son los indicadores.
Los punteros ya parecen abordarse (sin juego de palabras) en otras respuestas.
Las clases son fundamentales para OO. Tuve tremendos problemas para convertir mi cabeza en OO, diez años de intentos fallidos. El libro que finalmente me ayudó fue "Aplicación de UML y patrones" de Craig Larman. Sé que suena como si se tratara de algo diferente, pero realmente hace un gran trabajo al facilitarte en el mundo de las clases y los objetos.
No hay substiture para practicar.
Es fácil leer un libro o escuchar una conferencia y sentir que estás siguiendo lo que está pasando.
Lo que recomendaría es tomar algunos de los ejemplos de código (supongo que los tiene en algún lugar del disco), compilarlos y ejecutarlos, luego intente cambiarlos para hacer algo diferente.
- Agregar otra subclase a una jerarquía
- Agregar un método a una clase existente
- Cambia un algoritmo que itera hacia adelante a través de una colección para ir hacia atrás en su lugar.
No creo que haya ningún libro de "bala de plata" que lo haga.
Para mí, lo que me llevó a casa lo que los punteros significaban era trabajar en ensamblaje, y ver que un puntero era en realidad solo una dirección, y que tener un puntero no significaba que lo que apuntaba era un objeto significativo.
Para clases:
El momento decisivo para mí fue cuando aprendí sobre interfaces. La idea de abstraer los detalles de cómo escribiste resolvió un problema, y dar solo una lista de métodos que interactúan con la clase fue muy perspicaz.
De hecho, mi profesor explícitamente nos dijo que calificaría nuestros programas conectando nuestras clases a su arnés de prueba. La calificación se haría en función de los requisitos que nos brindó y si el programa se bloqueó.
Para resumir, las clases te permiten cerrar la funcionalidad y llamarla de una manera más limpia (la mayoría de las veces, siempre hay excepciones)
Para comprender mejor los punteros, creo que puede ser útil observar cómo funciona el lenguaje ensamblador con los punteros. El concepto de punteros es realmente una de las partes fundamentales del lenguaje ensamblador y la arquitectura de instrucción del procesador x86. Tal vez te deje caer como punteros son una parte natural de un programa.
En cuanto a las clases, aparte del paradigma OO, creo que puede ser interesante observar las clases desde una perspectiva binaria de bajo nivel. No son tan complejos a este respecto en el nivel básico.
Puede leer Inside the C ++ Object Model si desea obtener una mejor comprensión de lo que hay debajo del modelo de objetos C ++.
Para entender los indicadores, no puedo recomendar el libro de K & R lo suficiente.
Para punteros y clases, aquí está mi analogía. Usaré una baraja de cartas. El mazo de cartas tiene un valor nominal y un tipo (9 de corazones, 4 de picas, etc.). Entonces en nuestro lenguaje de programación C ++ de "Deck of Cards" diremos lo siguiente:
HeartCard card = 4; // 4 of hearts!
Ahora, sabes dónde está el 4 de corazones porque, por suerte, estás sosteniendo el mazo, boca arriba en la mano, ¡y está en la parte superior! Entonces, en relación con el resto de las cartas, diremos que el 4 de corazones está al COMIENZO. Entonces, si te pregunté qué carta es al COMIENZO, dirías, "¡El 4 de corazones por supuesto!". Bueno, simplemente me "apuntó" hacia donde está la tarjeta. En nuestro lenguaje de programación "Deck of Cards", puede decir lo siguiente:
HeartCard card = 4; // 4 of hearts!
print &card // the address is BEGINNING!
Ahora, dale la vuelta a tu mazo de cartas. La parte de atrás ahora está COMENZANDO y usted no sabe lo que es la tarjeta. Pero, digamos que puedes hacer lo que quieras porque estás lleno de magia. Hagámoslo en nuestro langauge "Deck of Cards".
HeartCard *pointerToCard = MakeMyCard( "10 of hearts" );
print pointerToCard // the value of this is BEGINNING!
print *pointerToCard // this will be 10 of hearts!
Bueno, MakeMyCard ("10 de corazones") estaba haciendo tu magia y sabiendo que querías apuntar a COMENZAR, ¡haciendo de la carta un 10 de corazones! Le das la vuelta a tu tarjeta y, ¡voilá! Ahora, el * puede tirarte. Si es así, mira esto:
HeartCard *pointerToCard = MakeMyCard( "10 of hearts" );
HeartCard card = 4; // 4 of hearts!
print *pointerToCard; // prints 10 of hearts
print pointerToCard; // prints BEGINNING
print card; // prints 4 of hearts
print &card; // prints END - the 4 of hearts used to be on top but we flipped over the deck!
En cuanto a las clases, hemos estado usando clases en el ejemplo definiendo un tipo como HeartCard. Sabemos lo que es una HeartCard ... ¡Es una tarjeta con un valor y un tipo de corazón! Entonces, hemos clasificado eso como una HeartCard. Cada idioma tiene una forma similar de definir o "clasificar" lo que desea, ¡pero todos comparten el mismo concepto! Espero que esto haya ayudado ...
Puede encontrar este artículo de Joel instructivo. Por otro lado, si has estado "trabajando en C ++ durante algún tiempo" y te has graduado en CS, es posible que hayas ido a una JavaSchool (yo diría que no has trabajado en C ++ en absoluto; estado trabajando en C pero usando el compilador de C ++).
Además, solo para secundar las respuestas de hojou y nsanders, los punteros son fundamentales para C ++. Si no entiende los punteros, entonces no comprende los conceptos básicos de C ++ (admitir que este hecho es el comienzo de la comprensión de C ++, por cierto). De manera similar, si no comprende las clases, entonces no comprende los conceptos básicos de C ++ (u OO para el caso).
Para los punteros, creo que dibujar con cajas es una buena idea, pero trabajar en ensamblaje también es una buena idea. Cualquier instrucción que use el direccionamiento relativo le ayudará a comprender qué punteros son bastante rápidos, creo.
En cuanto a las clases (y la programación orientada a objetos de manera más general), recomendaría Stroustrups "The C ++ Programming Language" última edición. No solo es el material de referencia canónico de C ++, sino que también tiene bastante material en muchas otras cosas, desde jerarquías básicas de clases orientadas a objetos y herencia hasta principios de diseño en sistemas grandes. Es una lectura muy buena (si no es un poco gruesa y concisa en algunos puntos).
Solía tener un problema para entender los punteros en pascal. :) Una vez que comencé a hacer punteros de ensamblador era realmente la única forma de acceder a la memoria y simplemente me golpeaba. Puede sonar como un plano lejano, pero probar el ensamblador (que siempre es una buena idea para tratar de entender de qué se trata realmente la computadora) probablemente le enseñará consejos. Las clases, bueno, no entiendo tu problema, ¿fue tu educación una programación puramente estructurada? Una clase es solo una forma lógica de ver modelos de la vida real: estás tratando de resolver un problema que podría resumirse en una cantidad de objetos / clases.
Supongamos que un puntero es una dirección de matriz.
x = 500; // memory address for hello;
MEMORY[x] = "hello";
print MEMORY[x];
es una simplificación gráfica, pero en la mayoría de los casos, siempre y cuando no quieras saber qué número es ese o lo pongas a mano, estarás bien.
Cuando entendí que CI tenía unas pocas macros que tenía, que más o menos te permitían usar punteros como si fueran un índice de matriz en la memoria. Pero hace tiempo que perdí ese código y hace tiempo que lo olvidé.
Recuerdo que comenzó con
#define MEMORY 0;
#define MEMORYADDRESS( a ) *a;
y eso por sí solo no es útil. Con suerte, alguien más puede ampliar esa lógica.
Una de las cosas que realmente me ayudó a entender estos conceptos es aprender UML, el lenguaje de modelado unificado. Ver conceptos de diseño orientado a objetos en un formato gráfico realmente me ayudó a aprender lo que significan. A veces, tratar de comprender estos conceptos simplemente observando qué código fuente los implementa puede ser difícil de comprender.
Ver paradigmas orientados a objetos como la herencia en forma gráfica es una forma muy poderosa de captar el concepto.
El UML Distilled de Martin Fowler es una buena y breve introducción.
Este enlace tiene un video que describe cómo funcionan los punteros, con claymation. Informativo y fácil de digerir.
Esta página contiene buena información sobre las clases básicas.
Comprender los indicadores en C / C ++
Antes de que uno pueda entender cómo funcionan los punteros, es necesario comprender cómo se almacenan y se accede a las variables en los programas. Cada variable tiene 2 partes: (1) la dirección de memoria donde se almacenan los datos y (2) el valor de los datos almacenados.
La dirección de la memoria a menudo se denomina valor l de una variable, y el valor de los datos almacenados se conoce como el valor r (l y r significan izquierda y derecha).
Considera la declaración:
int x = 10;
Internamente, el programa asocia una dirección de memoria con la variable x. En este caso, supongamos que el programa asigna x para residir en la dirección 1001 (no es una dirección realista, sino que se elige por simplicidad). Por lo tanto, el lvalue (dirección de memoria) de x es 1001, y el valor r (valor de datos) de x es 10.
Se accede al valor r usando simplemente la variable "x". Para acceder al valor l, se necesita el operador "dirección de" (''&''). La expresión ''& x'' se lee como "la dirección de x".
Expression Value
----------------------------------
x 10
&x 1001
El valor almacenado en x se puede cambiar en cualquier momento (por ejemplo, x = 20), pero la dirección de x (& x) nunca se puede cambiar.
Un puntero es simplemente una variable que se puede usar para modificar otra variable. Lo hace teniendo una dirección de memoria para su valor r. Es decir, apunta a otra ubicación en la memoria.
La creación de un puntero a "x" se realiza de la siguiente manera:
int* xptr = &x;
El "int *" le dice al compilador que estamos creando un puntero a un valor entero. La parte "= & x" le dice al compilador que estamos asignando la dirección de x al valor r de xptr. Por lo tanto, le estamos diciendo al compilador que xptr "apunta a" x.
Suponiendo que xptr está asignado a una dirección de memoria de 1002, entonces la memoria del programa podría verse así:
Variable lvalue rvalue
--------------------------------------------
x 1001 10
xptr 1002 1001
La siguiente pieza del rompecabezas es el "operador indirecto" (''*''), que se utiliza de la siguiente manera:
int y = *xptr;
El operador indirecto le dice al programa que interprete el valor r de xptr como una dirección de memoria en lugar de un valor de datos. Es decir, el programa busca el valor de datos (10) almacenado en la dirección proporcionada por xptr (1001).
Poniendolo todo junto:
Expression Value
--------------------------------------------
x 10
&x 1001
xptr 1001
&xptr 1002
*xptr 10
Ahora que los conceptos han sido explicados, aquí hay un código para demostrar el poder de los punteros:
int x = 10;
int *xptr = &x;
printf("x = %d/n", x);
printf("&x = %d/n", &x);
printf("xptr = %d/n", xptr);
printf("*xptr = %d/n", *xptr);
*xptr = 20;
printf("x = %d/n", x);
printf("*xptr = %d/n", *xptr);
Para la salida, verá (Nota: la dirección de la memoria será diferente cada vez):
x = 10
&x = 3537176
xptr = 3537176
*xptr = 10
x = 20
*xptr = 20
Observe cómo la asignación de un valor a ''* xptr'' cambió el valor de ''x''. Esto se debe a que ''* xptr'' y ''x'' se refieren a la misma ubicación en la memoria, como lo demuestran ''& x'' y ''xptr'' que tienen el mismo valor.
Los indicadores no son un tipo de cosas mágicas, ¡las estás usando todo el tiempo!
Cuando tu dices:
int a;
y el compilador genera almacenamiento para ''a'', prácticamente dices que estás declarando
un int y desea nombrar su ubicación de memoria ''a''.
Cuando tu dices:
int * a;
estás declarando una variable que puede contener una ubicación de memoria de un int. Es así de simple. Además, no tenga miedo de la aritmética de puntero, solo tenga en mente un "mapa de memoria" cuando trate con punteros y piense en términos de recorrer direcciones de memoria.
Las clases en C ++ son solo una forma de definir tipos de datos abstractos. Sugiero leer un buen libro OOP para entender el concepto, entonces, si está interesado, aprenda cómo los compiladores C ++ generan código para simular OOP. Pero este conocimiento llegará a tiempo, si te quedas con C ++ lo suficiente :)
Los punteros y las clases no son realmente temas avanzados en C ++. Ellos son bastante fundamentales.
Para mí, los indicadores se solidificaron cuando comencé a dibujar cajas con flechas. Dibuja un cuadro para un int. Y int * ahora es un cuadro separado con una flecha que apunta al cuadro int.
Asi que:
int foo = 3; // integer
int* bar = &foo; // assigns the address of foo to my pointer bar
Con el cuadro de mi puntero (barra), tengo la opción de mirar la dirección dentro del cuadro. (Que es la dirección de memoria de foo). O puedo manipular cualquier cosa que tenga una dirección. Esa manipulación significa que estoy siguiendo esa flecha al entero (foo).
*bar = 5; // asterix means "dereference" (follow the arrow), foo is now 5
bar = 0; // I just changed the address that bar points to
Las clases son otro tema completamente. Hay algunos libros sobre diseño orientado a objetos, pero no sé cuáles son buenos para los principiantes de la parte superior de mi cabeza. Podrías tener suerte con un libro introductorio de Java.
En el caso de las clases, tuve tres técnicas que realmente me ayudaron a dar el salto a la programación orientada a objetos reales.
El primero fue que trabajé en un proyecto de juego que hizo un uso intensivo de clases y objetos, con uso intensivo de generalización (tipo de relación o es, una clase, por ejemplo, el estudiante es un tipo de persona) y composición (tiene una relación, ex. estudiante tiene un préstamo estudiantil). Romper este código tomó mucho trabajo, pero realmente trajo las cosas en perspectiva.
Lo segundo que me ayudó fue en mi clase de Análisis del sistema, donde tuve que hacer http://www.agilemodeling.com/artifacts/classDiagram.htm">Diagramas de clase de MUL. Estos realmente me ayudaron a entender la estructura de las clases. en un programa
Por último, ayudo a los estudiantes de mi universidad en la programación. Todo lo que puedo decir sobre esto es que aprendes mucho enseñando y viendo el enfoque de otras personas ante un problema. Muchas veces un estudiante intentará cosas que nunca hubiera pensado, pero que generalmente tienen mucho sentido y simplemente tienen problemas para implementar su idea.
Mi mejor consejo es que requiere mucha práctica, y cuanto más programes, mejor lo entenderás.
Su problema parece ser el núcleo C en C ++, no C ++ en sí. Obtenga el Kernighan & Ritchie ( El lenguaje de programación C ). Inhalalo. Es muy bueno, uno de los mejores libros de lenguaje de programación jamás escritos.
De la respuesta de lassevek a una pregunta similar sobre SO :
Punteros es un concepto que para muchos puede ser confuso al principio, en particular cuando se trata de copiar los valores del puntero y seguir haciendo referencia al mismo bloque de memoria.
He descubierto que la mejor analogía es considerar el puntero como un pedazo 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 pueden ser explicadas fácilmente:
- Copie el valor del puntero, solo escriba la dirección en una nueva hoja de papel
- Listas vinculadas, pedazo de papel en la casa con la dirección de la próxima casa en él
- Liberando la memoria, demoler la casa y borrar la dirección
- Fuga de memoria, pierde la hoja de papel y no puede encontrar la casa
- Liberar la memoria pero mantener una referencia (ahora no válida), demoler la casa, borrar una de las hojas de papel pero tener otra hoja de papel con la dirección anterior, cuando vaya a la dirección, no encontrará una casa , pero es posible que encuentre algo que se asemeje a las ruinas de uno
- Desbordamiento de búfer, mueve más cosas de lo que pueda en la casa, derramándose en la casa de los vecinos
Para los punteros:
Encontré que esta publicación tuvo una discusión muy atenta sobre los indicadores. Quizás eso ayude. ¿Estás familiarizado con refrences como en C #? ¿Eso es algo que realmente se refiere a algo más? Eso es probablemente un buen comienzo para entender los indicadores.
Además, consulte la publicación de Kent Fredric a continuación en otra forma de presentarse a los punteros.