J'ai déjà beaucoup travaillé avec des listes liées en Java, mais je suis très novice en C ++. J'utilisais cette classe de nœuds qui m'a été donnée dans un projet très bien
class Node
{
public:
Node(int data);
int m_data;
Node *m_next;
};
mais j'avais une question qui n'a pas été très bien répondue. Pourquoi est-il nécessaire d'utiliser
Node *m_next;
pour pointer vers le nœud suivant de la liste au lieu de
Node m_next;
Je comprends qu'il vaut mieux utiliser la version pointeur; Je ne vais pas discuter des faits, mais je ne sais pas pourquoi c'est mieux. J'ai eu une réponse pas si claire sur la façon dont le pointeur est meilleur pour l'allocation de mémoire, et je me demandais si quelqu'un ici pourrait m'aider à mieux comprendre cela.
c++
pointers
linked-list
m0meni
la source
la source
Node m_next
n'est pas une référence à un nœud, c'est un stockage pour l'ensembleNode
lui-même.Réponses:
Ce n'est pas seulement mieux, c'est le seul moyen possible.
Si vous stockiez un
Node
objet à l' intérieur de lui-même, que serait-sizeof(Node)
il? Ce seraitsizeof(int) + sizeof(Node)
, qui serait égal àsizeof(int) + (sizeof(int) + sizeof(Node))
, qui serait égal àsizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))
, etc. à l'infini.Un objet comme celui-là ne peut pas exister. C'est impossible .
la source
En Java
stocke un pointeur vers un autre nœud. Vous n'avez pas le choix. En C ++
signifie la même chose. La différence est qu'en C ++, vous pouvez en fait stocker l'objet plutôt qu'un pointeur vers celui-ci. C'est pourquoi vous devez dire que vous voulez un pointeur. En C ++:
signifie stocker le nœud ici (et cela ne peut clairement pas fonctionner pour une liste - vous vous retrouvez avec une structure définie de manière récursive).
la source
Node
propre constructeur du membre serait appelé, ce qui instancierait une autre nouvelle instance ... et vous obtiendriez une pseudo-récursion sans fin. Ce n'est pas vraiment un problème de taille en termes complètement stricts et littéraux, mais un problème de performances.Node
celui qui est en fait dans le premierNode
. Vous créez donc un pointeur, qui est essentiellement la manière dont Java gère les objets, par opposition aux primitives. Lorsque vous appelez une méthode ou créez une variable, Java ne stocke pas une copie de l'objet ni même l'objet lui-même; il stocke une référence à un objet, qui est essentiellement un pointeur avec un petit gant d'enfant enroulé autour de lui. C'est ce que disent essentiellement les deux réponses.C ++ n'est pas Java. Quand tu écris
en Java, cela revient à écrire
en C ++. En Java, le pointeur est implicite, en C ++, il est explicite. Si vous écrivez
en C ++, vous placez une instance de
Node
là à l'intérieur de l'objet que vous définissez. Il est toujours là et ne peut pas être omis, il ne peut pas être attribué avecnew
et il ne peut pas être supprimé. Cet effet est impossible à obtenir en Java, et il est totalement différent de ce que fait Java avec la même syntaxe.la source
Vous utilisez un pointeur, sinon votre code:
… Ne compilerait pas , car le compilateur ne peut pas calculer la taille de
Node
. C'est parce que cela dépend de lui-même - ce qui signifie que le compilateur ne peut pas décider de la quantité de mémoire qu'il consommerait.la source
k == sizeof(Node)
tient et que votre type contient des données, il devrait également contenir celasizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)
et ensuitesizeof(Node) > sizeof(Node)
.aleph_0
fonctionne. (Juste être trop pédant :-))sizeof
soit un type intégral non signé, il y a donc l'espoir de tailles transfinies ou même réelles. (être encore plus pédant!: p)Node
n'est même pas défini avant la fin de cet extrait de code, vous ne pouvez donc pas vraiment l'utiliser à l'intérieur. Permettre à quelqu'un de déclarer implicitement des pointeurs vers une classe encore non déclarée est une petite astuce qui est autorisée par le langage afin de rendre de telles structures possibles, sans avoir besoin de lancer explicitement des pointeurs tout le temps.Ce dernier (
Node m_next
) devrait contenir le nœud. Cela ne le montrerait pas. Et il n'y aurait alors aucune liaison d'éléments.la source
L'approche que vous décrivez est compatible non seulement avec C ++, mais aussi avec son langage sous - ensemble ( la plupart du temps) C . Apprendre à développer une liste chaînée de style C est un bon moyen de vous familiariser avec les techniques de programmation de bas niveau (comme la gestion manuelle de la mémoire), mais ce n'est généralement pas le cas. une bonne pratique pour le développement C ++ moderne.
Ci-dessous, j'ai implémenté quatre variantes sur la façon de gérer une liste d'éléments en C ++.
raw_pointer_demo
utilise la même approche que la vôtre - la gestion manuelle de la mémoire requise avec l'utilisation de pointeurs bruts. L'utilisation de C ++ ici est uniquement pour le sucre syntaxique , et l'approche utilisée est par ailleurs compatible avec le langage C.shared_pointer_demo
la liste, la gestion se fait toujours manuellement, mais la gestion de la mémoire est automatique (n'utilise pas de pointeurs bruts). Ceci est très similaire à ce que vous avez probablement expérimenté avec Java.std_list_demo
utilise lelist
conteneur de bibliothèque standard . Cela montre à quel point les choses deviennent plus faciles si vous comptez sur des bibliothèques existantes plutôt que sur la vôtre.std_vector_demo
utilise levector
conteneur de bibliothèque standard . Cela gère le stockage de la liste dans une seule allocation de mémoire contiguë. En d'autres termes, il n'y a pas de pointeurs vers des éléments individuels. Dans certains cas plutôt extrêmes, cela peut devenir considérablement inefficace. Dans les cas typiques, cependant, il s'agit de la meilleure pratique recommandée pour la gestion des listes en C ++ .À noter: De tous ces éléments, seul le
raw_pointer_demo
requiert en fait que la liste soit explicitement détruite afin d'éviter une «fuite» de mémoire. Les trois autres méthodes détruiraient automatiquement la liste et son contenu lorsque le conteneur sort du cadre (à la fin de la fonction). Le fait est que C ++ est capable d'être très "Java" à cet égard - mais seulement si vous choisissez de développer votre programme en utilisant les outils de haut niveau à votre disposition.la source
Node_reference
déclaration ci-dessus aborde l'une des différences les plus intéressantes au niveau du langage entre Java et C ++. En Java, déclarer un objet de typeNode
utiliserait implicitement une référence à un objet alloué séparément. En C ++, vous avez le choix entre l'allocation de référence (pointeur) et l'allocation directe (pile) - vous devez donc gérer la distinction explicitement. Dans la plupart des cas, vous utiliseriez l'allocation directe, mais pas pour les éléments de liste.Aperçu
Il existe 2 façons de référencer et d'allouer des objets en C ++, alors qu'en Java, il n'y a qu'une seule façon.
Afin d'expliquer cela, les schémas suivants montrent comment les objets sont stockés en mémoire.
1.1 Eléments C ++ sans pointeurs
Avertissement : la syntaxe C ++ utilisée dans cet exemple est similaire à la syntaxe de Java. Mais, l'allocation de mémoire est différente.
1.2 Eléments C ++ utilisant des pointeurs
Si vous vérifiez la différence entre les deux méthodes, vous verrez que dans la première technique, l'élément d'adresse est alloué au sein du client, tandis que dans la seconde, vous devez créer chaque adresse de manière explicite.
Attention: Java alloue des objets en mémoire comme cette seconde technique, mais la syntaxe est comme la première, ce qui peut prêter à confusion pour les nouveaux venus en "C ++".
la mise en oeuvre
Ainsi, votre exemple de liste pourrait être quelque chose de similaire à l'exemple suivant.
Résumé
Étant donné qu'une liste liée a une quantité variable d'éléments, la mémoire est allouée selon les besoins et selon les disponibilités.
METTRE À JOUR:
Il convient également de le mentionner, comme l'a commenté @haccks dans son message.
Cela parfois, des références ou des pointeurs d'objet, indiquent des éléments imbriqués (alias "UML Composition").
Et parfois, des références ou des pointeurs d'objet, indiquent des éléments externes (aka "UML Aggregation").
Mais les éléments imbriqués de la même classe ne peuvent pas être appliqués avec la technique "sans pointeur".
la source
Par ailleurs, si le tout premier membre d'une classe ou d'une structure est le pointeur suivant (donc pas de fonctions virtuelles ou toute autre fonctionnalité d'une classe qui signifierait que le suivant n'est pas le premier membre d'une classe ou d'une structure), alors vous peut utiliser une classe ou une structure "de base" avec juste un pointeur suivant, et utiliser du code commun pour les opérations de base de liste chaînée comme ajouter, insérer avant, récupérer à partir de l'avant, .... En effet, C / C ++ garantit que l'adresse du premier membre d'une classe ou d'une structure est la même que l'adresse de la classe ou de la structure. La classe ou structure de nœud de base n'aurait qu'un pointeur suivant à utiliser par les fonctions de liste chaînée de base, puis le transtypage serait utilisé selon les besoins pour convertir entre le type de nœud de base et les types de nœud «dérivés». Note latérale - en C ++, si la classe de nœud de base n'a qu'un pointeur suivant,
la source
La raison en est que lorsque vous créez un
Node
objet, le compilateur doit allouer de la mémoire pour cet objet et pour cela, la taille de l'objet est calculée.La taille du pointeur vers n'importe quel type est connue du compilateur et, par conséquent, avec le pointeur auto-référentiel, la taille de l'objet peut être calculée.
Si
Node m_node
est utilisé à la place, le compilateur n'a aucune idée de la taille deNode
et il restera bloqué dans une récursivité infinie du calculsizeof(Node)
. Rappelez-vous toujours: une classe ne peut pas contenir un membre de son propre type .la source
Parce que cela en C ++
équivaut à cela en Java
où les deux créent un nouvel objet
MyClass
utilisant le constructeur par défaut.la source
Il y a bien sûr une réponse triviale.
Si elles ne sont pas un lien d' un nœud à l'autre par un pointeur, ils ne seraient pas des listes liées .
L'existence des listes chaînées en tant que chose est due au fait que nous voulons pouvoir enchaîner des objets entre eux. Par exemple: nous avons déjà un objet de quelque part. Nous voulons maintenant mettre cet objet réel (pas une copie) à la fin d'une file d'attente, par exemple. Ceci est réalisé en ajoutant un lien du dernier élément déjà sur la file d'attente à l'entrée que nous ajoutons. En termes de machine, c'est remplir un mot avec l'adresse de l'élément suivant.
la source