He trabajado con listas vinculadas anteriormente en Java, pero soy muy nuevo en C ++. Estaba usando esta clase de nodo que me fue dada en un proyecto muy bien
class Node
{
public:
Node(int data);
int m_data;
Node *m_next;
};
pero tenía una pregunta que no fue respondida muy bien. ¿Por qué es necesario usar
Node *m_next;
para apuntar al siguiente nodo en la lista en lugar de
Node m_next;
Entiendo que es mejor usar la versión de puntero; No voy a discutir hechos, pero no sé por qué es mejor. Obtuve una respuesta no tan clara sobre cómo el puntero es mejor para la asignación de memoria, y me preguntaba si alguien aquí podría ayudarme a comprenderlo mejor.
c++
pointers
linked-list
m0meni
fuente
fuente
Node m_next
no es una referencia a un nodo, es almacenamiento para todo elNode
mismo.Respuestas:
No es solo mejor, es la única forma posible.
Si almacenaras un
Node
objeto dentro de sí mismo, ¿cuál seríasizeof(Node)
? Seríasizeof(int) + sizeof(Node)
, que sería igual asizeof(int) + (sizeof(int) + sizeof(Node))
, que sería igual asizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))
, etc., al infinito.Un objeto como ese no puede existir. Es imposible .
fuente
En java
almacena un puntero a otro nodo. No tienes elección al respecto. En C ++
significa lo mismo. La diferencia es que en C ++ puede almacenar el objeto en lugar de un puntero. Es por eso que tienes que decir que quieres un puntero. En C ++:
significa almacenar el nodo aquí (y eso claramente no puede funcionar para una lista; terminas con una estructura definida recursivamente).
fuente
Node
, se llamaría al propio constructor del miembro , lo que crearía una instancia de otra nueva instancia ... y obtendría una pseudo-recursión interminable. No es realmente un problema de tamaño en términos completamente estrictos y literales, ya que es un problema de rendimiento.Node
que esté realmente dentro de la primeraNode
. Entonces crea un puntero, que es esencialmente cómo Java maneja los objetos, en lugar de las primitivas. Cuando llama a un método o crea una variable, Java no almacena una copia del objeto o incluso el objeto en sí; almacena una referencia a un objeto, que es esencialmente un puntero con un poco de guante para niños envuelto alrededor. Esto es lo que ambas respuestas están diciendo esencialmente.C ++ no es Java. Cuando escribes
en Java, eso es lo mismo que escribir
en C ++. En Java, el puntero es implícito, en C ++ es explícito. Si tú escribes
en C ++, coloca una instancia de
Node
allí dentro del objeto que está definiendo. Siempre está ahí y no se puede omitir, no se puede asignarnew
y no se puede eliminar. Este efecto es imposible de lograr en Java, y es totalmente diferente de lo que Java hace con la misma sintaxis.fuente
Utiliza un puntero, de lo contrario su código:
... no se compilaría, ya que el compilador no puede calcular el tamaño de
Node
. Esto se debe a que depende de sí mismo, lo que significa que el compilador no puede decidir cuánta memoria consumiría.fuente
k == sizeof(Node)
mantiene y su tipo tiene datos, también tendría que mantener esosizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)
y luegosizeof(Node) > sizeof(Node)
.aleph_0
funciona. (Solo siendo demasiado pedante :-))sizeof
sea un tipo integral sin signo, por lo que existe la esperanza de tamaños transfinitos o incluso reales. (siendo incluso más pedante: p!)Node
ni siquiera se define antes del final de este fragmento, por lo que realmente no puede usarlo en su interior. Permitir que uno declare implícitamente punteros a una clase aún no declarada es un pequeño truco permitido por el lenguaje para hacer posibles tales estructuras, sin la necesidad de emitir punteros explícitamente todo el tiempo.Este último (
Node m_next
) debería contener el nodo. No lo señalaría. Y entonces no habría vinculación de elementos.fuente
El enfoque que usted describe es compatible no sólo con C ++, pero también con su (en su mayoría) lengua subgrupo C . Aprender a desarrollar una lista vinculada de estilo C es una buena manera de presentarse a las técnicas de programación de bajo nivel (como la administración manual de memoria), pero generalmente no es una práctica recomendada para el desarrollo moderno de C ++.
A continuación, he implementado cuatro variaciones sobre cómo administrar una lista de elementos en C ++.
raw_pointer_demo
utiliza el mismo enfoque que el suyo: se requiere la gestión manual de la memoria con el uso de punteros sin formato. El uso de C ++ aquí es solo para azúcar sintáctico , y el enfoque utilizado es compatible con el lenguaje C.shared_pointer_demo
la lista, la administración todavía se realiza manualmente, pero la administración de la memoria es automática (no utiliza punteros sin formato). Esto es muy similar a lo que probablemente haya experimentado con Java.std_list_demo
usa ellist
contenedor de biblioteca estándar . Esto muestra cuánto más fácil se vuelven las cosas si confía en las bibliotecas existentes en lugar de crear las suyas propias.std_vector_demo
usa elvector
contenedor de biblioteca estándar . Esto gestiona el almacenamiento de la lista en una única asignación de memoria contigua. En otras palabras, no hay punteros a elementos individuales. Para ciertos casos bastante extremos, esto puede volverse significativamente ineficiente. Sin embargo, en casos típicos, esta es la mejor práctica recomendada para la gestión de listas en C ++ .De nota: De todos estos, solo el
raw_pointer_demo
realmente requiere que la lista se destruya explícitamente para evitar la "pérdida" de memoria. Los otros tres métodos destruirían automáticamente la lista y su contenido cuando el contenedor se salga del alcance (al finalizar la función). El punto es: C ++ es capaz de ser muy "similar a Java" a este respecto, pero solo si elige desarrollar su programa utilizando las herramientas de alto nivel a su disposición.fuente
Node_reference
declaración anterior aborda una de las diferencias de nivel de lenguaje más interesantes entre Java y C ++. En Java, declarar un objeto de tipoNode
implícitamente usaría una referencia a un objeto asignado por separado. En C ++, tiene la opción de asignación de referencia (puntero) versus asignación directa (pila), por lo que debe manejar la distinción explícitamente. En la mayoría de los casos, usaría la asignación directa, aunque no para los elementos de la lista.Visión general
Hay 2 formas de referenciar y asignar objetos en C ++, mientras que en Java solo hay una.
Para explicar esto, los siguientes diagramas muestran cómo se almacenan los objetos en la memoria.
1.1 Elementos de C ++ sin punteros
Advertencia : la sintaxis de C ++ utilizada en este ejemplo es similar a la sintaxis de Java. Pero, la asignación de memoria es diferente.
1.2 Elementos de C ++ que utilizan punteros
Si marca la diferencia entre ambas formas, verá que en la primera técnica, el elemento de dirección se asigna dentro del cliente, mientras que en la segunda forma, debe crear cada dirección explícitamente.
Advertencia: Java asigna objetos en la memoria como esta segunda técnica, pero la sintaxis es como la primera forma, lo que puede ser confuso para los recién llegados a "C ++".
Implementación
Entonces, su ejemplo de lista podría ser algo similar al siguiente ejemplo.
Resumen
Como una lista vinculada tiene una cantidad variable de elementos, la memoria se asigna según sea necesario y, según esté disponible.
ACTUALIZAR:
También vale la pena mencionar, como @haccks comentó en su publicación.
Que a veces, referencias o punteros de objetos, indican elementos anidados (también conocido como "Composición UML").
Y a veces, las referencias o punteros de objetos, indican elementos externos (también conocido como "Agregación UML").
Pero los elementos anidados de la misma clase no se pueden aplicar con la técnica "sin puntero".
fuente
En una nota al margen, si el primer miembro de una clase o estructura es el siguiente puntero (por lo que no hay funciones virtuales o cualquier otra característica de una clase que signifique que el siguiente no es el primer miembro de una clase o estructura), entonces usted puede usar una clase o estructura "base" con solo un puntero siguiente y usar un código común para operaciones básicas de listas vinculadas como agregar, insertar antes, recuperar desde el frente, .... Esto se debe a que C / C ++ garantiza que la dirección del primer miembro de una clase o estructura es la misma que la dirección de la clase o estructura. La clase o estructura del nodo base solo tendría un puntero siguiente para ser utilizado por las funciones básicas de la lista enlazada, luego la conversión de texto se usaría según sea necesario para convertir entre el tipo de nodo base y los tipos de nodo "derivados". Nota al margen: en C ++, si la clase de nodo base solo tiene un siguiente puntero,
fuente
La razón es que cuando crea un
Node
objeto, el compilador tiene que asignar memoria para ese objeto y para eso se calcula el tamaño del objeto.El compilador conoce el tamaño del puntero a cualquier tipo y, por lo tanto, con el puntero autorreferencial se puede calcular el tamaño del objeto.
Si
Node m_node
se usa en su lugar, entonces el compilador no tiene idea del tamaño deNode
y se atascará en una recursión infinita de cálculosizeof(Node)
. Recuerde siempre: una clase no puede contener un miembro de su propio tipo .fuente
Porque esto en C ++
es equivalente a esto en Java
donde ambos crean un nuevo objeto de
MyClass
usar el constructor predeterminado.fuente
Por supuesto, hay una respuesta trivial.
Si no vincularan un nodo al siguiente mediante un puntero, no serían listas vinculadas .
La existencia de listas vinculadas es porque queremos poder encadenar objetos juntos. Por ejemplo: ya tenemos un objeto de alguna parte. Ahora queremos poner ese objeto real (no una copia) al final de una cola, por ejemplo. Esto se logra al agregar un enlace desde el último elemento que ya está en la cola a la entrada que estamos agregando. En términos de máquina, eso es completar una palabra con la dirección del siguiente elemento.
fuente