Dans cette interview à Slashdot, Linus Torvalds aurait déclaré:
J'ai vu trop de gens qui suppriment une entrée de liste à liaison unique en gardant une trace de l'entrée "prev", puis en supprimant l'entrée, en faisant quelque chose comme
if (prev)
prev-> next = entry-> next;
else
list_head = entry-> next;et chaque fois que je vois du code comme ça, je dis simplement "Cette personne ne comprend pas les pointeurs". Et c'est malheureusement assez courant.
Les personnes qui comprennent les pointeurs utilisent simplement un "pointeur vers le pointeur d'entrée" et l'initialisent avec l'adresse de list_head. Et puis, au fur et à mesure qu'ils parcourent la liste, ils peuvent supprimer l'entrée sans utiliser de conditions, en faisant simplement un "* pp = entry-> next".
En tant que développeur PHP, je n'ai pas touché de pointeurs depuis l' introduction au C à l'université il y a dix ans. Cependant, je pense que c'est un type de situation que je devrais au moins connaître. De quoi parle Linus? Pour être honnête, si on me demandait de mettre en œuvre une liste chaînée et de supprimer un élément, la «mauvaise» manière ci-dessus est la façon dont je procéderais. Que dois-je savoir pour coder comme Linus le dit le mieux?
Je demande ici plutôt que sur Stack Overflow car je n'ai pas vraiment de problème avec cela dans le code de production.
prev
, au lieu de stocker le nœud entier, vous pouvez simplement stocker l'emplacement deprev.next
, car c'est la seule chose qui vous intéresse. Un pointeur vers un pointeur. Et si vous faites cela, vous évitez l'idiotif
, car maintenant vous n'avez pas le cas gênant d'list_head
être un pointeur de l'extérieur d'un nœud. Le pointeur vers la tête de la liste est alors sémantiquement le même que le pointeur vers le nœud suivant.Réponses:
Utilisation de mes compétences L331 MS Paint:
La solution originale consiste à pointer vers Nodes via
curr
. Dans ce cas, vous vérifiez si le nœud suivantcurr
possède la valeur de suppression et, si tel est le cas, réinitialisez le pointeur decurr
nœudnext
. Le problème est qu'il n'y a aucun nœud qui pointe vers la tête de la liste. Cela signifie qu'il doit y avoir un cas spécial pour le vérifier.Ce que Linus propose (probablement) à la place n'est pas d'enregistrer le pointeur sur le nœud actuellement examiné, mais plutôt le pointeur sur le pointeur sur le nœud actuel (étiqueté
pp
). L'opération est la même - si lepp
pointeur pointe vers un nœud avec la bonne valeur, vous réinitialisez lepp
pointeur.La différence vient au tout début de la liste. Bien qu'il n'y ait aucun nœud qui pointe vers le début de la liste, il y a, en fait, un pointeur vers le début de la liste. Et c'est tout de même un pointeur vers un nœud, tout comme un autre
next
pointeur de nœuds . Il n'est donc pas nécessaire de prévoir une clause spéciale pour le début de la liste.la source
list_head
pointer vers quelque chose avec unnext
nœud qui pointe vers le premier élément de données réel (et à l'prev
initialiser sur le même objet factice). Je n'aime pas l'idée deprev
pointer vers quelque chose de type différent, car de telles astuces peuvent introduire un comportement indéfini via l'aliasing et rendre le code inutilement sensible à la disposition de la structure.prev
pointé sur Nodes, il pointe sur des pointeurs. Il pointe toujours vers quelque chose du même type, à savoir un pointeur vers un nœud. Votre suggestion est essentiellement la même - ayez unprev
point sur quelque chose "avec unnext
nœud". Si vous jetez le shell, vous obtenez simplement lelist_head
pointeur initial . Ou en d'autres termes - quelque chose qui est défini uniquement en ayant un pointeur sur le nœud suivant, est sémantiquement équivalent à un pointeur sur un nœud.list_head
etnext
contiendra le même "type" de pointeur. Pas un problème en C, peut-être, mais peut-être problématique en C ++.Exemple de code
Si nous avons besoin de logique pour détruire le nœud, nous pouvons simplement ajouter une ligne de code à la fin:
la source