Bağlantılı listeden bir öğeyi kaldırmanın doğru yolu


10

Bu Slashdot röportajında Linus Torvalds şöyle diyor:

"Prev" girdisini takip ederek tek bir bağlantılı liste girişini silen ve daha sonra girişi silmek için,

eğer (önceki)
  önceki-> sonraki = giriş-> sonraki;
else
  list_head = giriş-> sonraki;

ve böyle bir kod gördüğümde, "Bu kişi işaretçileri anlamıyor" diye gidiyorum. Ve ne yazık ki oldukça yaygın.

İşaretçileri anlayan insanlar sadece bir "giriş işaretçisine işaretçi" kullanır ve bunu list_head adresiyle başlatır. Ve sonra listeyi geçtiklerinde, girdiyi herhangi bir koşul kullanmadan, yalnızca "* pp = entry-> next" yaparak kaldırabilirler.

Bir PHP geliştiricisi olarak ben on yıl önce üniversiteye C Giriş beri işaretçiler dokunmadım . Ancak, bunun en azından aşina olmam gereken bir tür durum olduğunu hissediyorum. Linus ne hakkında konuşuyor? Dürüst olmak gerekirse, bağlantılı bir listeyi uygulamam ve bir öğeyi kaldırmam istendiğinde, yukarıdaki 'yanlış' yol, bu konuda gitmemin yoludur. Linus'un en iyi dediği gibi kodlamak için nelere ihtiyacım var?

Aslında üretim kodunda bu bir sorun yok gibi Yığın Taşma yerine burada soruyorum.


1
Söylediği şey, prevdüğümün konumunu saklamanız gerektiğinde, tüm düğümü saklamak yerine, sadece konumunu saklayabilirsiniz prev.next, çünkü bu 'ilgilendiğiniz tek şey. Bir işaretçiye işaretçi. Ve bunu yaparsanız, aptallıktan kaçınırsınız if, çünkü şimdi list_headbir düğümün dışından bir işaretçi olma garip örneğiniz yok . Listenin başına gelen işaretçi, anlamsal olarak bir sonraki düğüme işaretçi ile aynıdır.
Sıradan

@Ordous: Anlıyorum, teşekkürler. Neden bir yorum? Bu kısa, net ve aydınlatıcı bir cevaptır.
dotancohen

@Ordous Bu kod snippet'inde yer alan her şey bir işaretçi olduğundan, noktasının tüm düğümü saklamak ya da ona bir işaretçi saklamakla bir ilgisi olamaz.
Doval

Yanıtlar:


9

L331 MS Paint becerilerimi kullanma:

resim açıklamasını buraya girin

Orijinal çözüm, Düğümler üzerinden işaret etmektir curr. Bu durumda, sonraki düğümün currsilme değerine sahip olup olmadığını kontrol edin ve varsa currdüğüm nextişaretçisini sıfırlayın . Sorun, listenin başına işaret eden bir düğüm olmamasıdır. Bu, kontrol etmek için özel bir durum olması gerektiği anlamına gelir.

Linus'un (büyük olasılıkla) önerdiği şey, işaretçiyi incelenen geçerli düğüme değil, geçerli düğüme işaretçiyi (etiketli pp) kaydetmektir . İşlem aynıdır - eğer ppişaretçi doğru değere sahip bir düğüme işaret ediyorsa, işaretçiyi sıfırlarsınız pp.

Aradaki fark listenin en başında geliyor. Listenin başına işaret eden bir Düğüm olmasa da, aslında listenin başına bir işaretçi vardır. Ve bir düğümün göstergesiyle aynıdır, tıpkı başka bir düğüm işaretçisiyle aynıdır next. Bu nedenle, listenin başlangıcı için özel bir maddeye gerek yoktur.


Ah şimdi görüyorum .... her gün yeni bir şeyler öğreniyorsun.
Lawrence Aiello

1
Ben şeyler doğru tanımlamak düşünüyorum, ama uygun çözüm ilk gerçek veri öğeyi işaret (ve aynı kukla nesneye başlattı) list_headbir nextdüğümü olan bir şey işaret etmektir prev. prevFarklı hilelere işaret etme fikrini sevmiyorum , çünkü bu tür hileler takma yoluyla Tanımsız Davranış getirebilir ve kodu yapı düzenine gereksiz yere duyarlı hale getirebilir.
supercat

@supercat Tam da bu nokta. prevDüğümleri işaret etmek yerine, işaretçileri gösterir. Her zaman aynı türden bir şeye işaret eder, yani bir Düğüme işaretçi. Öneriniz aslında aynıdır - prev" nextdüğümü olan" bir şeye işaret edin . Eğer kabuğu atarsanız, sadece ilk list_headişaretçiyi alırsınız . Veya başka bir deyişle - yalnızca bir sonraki düğüme bir işaretçi ile tanımlanmış bir şey, anlamsal olarak bir düğüme bir işaretçi ile eşdeğerdir.
Sıradan

@Ordous: Bu mantıklı, ancak bunu varsayıyor list_headve nextaynı "tür" işaretçisini taşıyacak. C'de bir sorun değil, belki de C ++ 'da sorunlu.
supercat

@supercat Her zaman, bağlantılı bir listenin, dil bilimi içermeyen "kanonik" temsilini varsaydım. Ama gerçekten C ve C ++ arasında bir fark yaratıp yaratmadığını ve standart uygulamaların ne olduğunu yargılayacak kadar yetkin değilim.
Sıradan

11

resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin resim açıklamasını buraya girin

Kod Örneği

// ------------------------------------------------------------------
// Start by pointing to the head pointer.
// ------------------------------------------------------------------
//    (next_ptr)
//         |
//         v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[....]
Node** next_ptr = &list->head;

// ------------------------------------------------------------------
// Search the list for the matching entry.
// After searching:
// ------------------------------------------------------------------
//                                  (next_ptr)
//                                       |
//                                       v
// [head]----->[..]----->[..]----->[..]----->[to_remove]----->[next]
while (*next_ptr != to_remove) // or (*next_ptr)->val != to_remove->val
{
    Node* next_node = *next_ptr
    next_ptr = &next_node->next;
}

// ------------------------------------------------------------------
// Dereference the next pointer and set it to the next node's next
// pointer.
// ------------------------------------------------------------------
//                                           (next_ptr)
//                                                |
//                                                v
// [head]----->[..]----->[..]----->[..]---------------------->[next]
*next_ptr = to_remove->next;

Düğümü yok etmek için bir mantığa ihtiyacımız varsa, sonunda bir kod satırı ekleyebiliriz:

// Deallocate the node which is now stranded from the list.
free(to_remove);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.