【问题标题】:Setting a struct element address to null in C++ (singly linked list)在 C++ 中将结构元素地址设置为 null(单链表)
【发布时间】:2017-12-12 00:33:46
【问题描述】:

我有这个结构:

struct Node {
    int num;
    Node *next;

    Node(int, Node*);
};

类内Collection

当我尝试删除列表的最后一个元素时,使用此函数:

void Collection::remove(int num){
   Node *target = find(num);
   if (target == nullptr) return;
   n--;
   Node *temp = target ->next;
   if (temp == nullptr) {
       delete target;  
       target = nullptr;       // This is where the problem occurs
       return;
   } 
   target->num = temp->num;
   target->next = temp->next;
   delete temp;
}

之前Node的*next仍然指向现在为空的地址位置,如何将target内存位置设置为null

更多信息,这是我的功能find()

Collection::Node* Collection::find(int num) {
    Node *temp = head;
    while (temp != nullptr && temp->num != num) temp = temp->next;
    return temp;
}

【问题讨论】:

    标签: c++ pointers struct singly-linked-list nullptr


    【解决方案1】:

    您实际上是在正常删除逻辑上使用了轻微的变体,这在大多数情况下都有效。这种变化是,不是删除当前元素,而是将 data 从后面的元素移到其中,然后删除后面的元素。这意味着您永远不必备份到前一个元素,这对于单链表来说是困难的(无需存储额外信息或再次遍历列表)。

    不幸的是,不起作用的一种情况是,当您删除列表中的最后一个元素时。在这种情况下,没有后续元素可以从中移动数据,因此您需要删除 current 元素并调整前一个元素以成为列表的新结尾。

    而且,既然您无论如何都需要这样做,那么您也可以在所有情况下恢复正常逻辑:-)

    使用该逻辑,您基本上需要在要删除的元素之前访问元素。然后你设置它的next 指针绕过你正在删除的那个。

    您还需要处理删除列表中的 first 元素的特殊情况。假设您有一个 head 成员指向第一个元素,curr/prev 分别指向您要删除的节点及其前身,伪代码将是:

    if curr is head:
        set head to curr.next
    else
        set prev.next = curr.next
    free curr
    

    在不涉及太多更改的情况下轻松完成此操作的一种方法是修改您的 find 方法,以便您也可以使用它来获取上一个节点:

    Node* Collection::find(
        int num,
        Node **pPrev = nullptr
    ) {
        // Initially store null as previous item.
    
        if (pPrev != nullptr) *pPrev = nullptr;
    
        // Cycle through list until found or not there.
    
        Node *temp = head;
        while ((temp != nullptr) && (temp->num != num)) {
            // Update previous before advancing.
    
            if (pPrev != nullptr) *pPrev = temp;
            temp = temp->next;
        }
        return temp;
    }
    

    请注意,我只是在代码中使用了Node 而不是Collection::Node,因为您的问题有两种变体,我不确定您是否在一个名为@987654329 的命名空间中@。只需根据您的实际使用情况进行必要的调整即可。

    无论如何,将第二个参数默认为nullptr 将允许您完全按照您当前的方式调用它(使用一个参数,它不会费心尝试存储前一个节点指针)。

    但是,如果你想要那个前一个指针,你可以使用这样的东西来删除:

    Node *prev;
    Node *curr = find(num, &prev);    // Get previous as well.
    if (curr != nullptr) {            // Only delete if actually found.
        if (prev == nullptr)          // Null means current is head.
            head = curr->next;
        else                          // Otherwise adjust previous.
            prev->next = curr->next;
        delete curr;                  // Regardless of head-ness, delete.
    }
    

    【讨论】:

    • 既然这是一个单链表,有没有办法在不循环到列表末尾的情况下做到这一点? (因为我的班级仅限于head 指针)
    • @AthScc,我已经展示了如何在没有 extra 循环的情况下找到前一个节点。我假设这就是您的意思,因为将总是至少有一个循环,即找到您要删除的元素的循环。
    【解决方案2】:

    在我看来,这一切都像是老式的 C :)

    要实际更改 Collection::remove 函数之外的内存,您应该修改 find 函数以实际返回指针到指针:

    Node** find(int num) {
         Node **temp = &head;
    
         while (*temp != NULL && (*temp)->num != num)
         {
             temp = &((*temp)->next);
         }
         return temp;
    

    }

    有了指针上的指针,你就可以改变你原来的记忆:

    int main(int argc, char *argv[])
    {
       head = new Node(1, new Node(2, new Node(3, NULL)));
    
       Node **elem = find(2);
    
    
       if (*elem)
        {
            delete *elem;
            *elem = NULL;
    
            cout << "elem 2 deleted" << endl;
        }
    
    
       elem = find(2);
       if (*elem)
       {
          delete *elem;
          *elem = NULL;
    
           cout << "elem 2 deleted" << endl;
       }
    
    
    
       cout << "Hello World!" << endl;
       return 0;
    }
    

    您的 Collection::remove 将如下所示:

    void Collection::remove(int num){
       Node **target = find(num);
       if (*target == nullptr) return;
       n--;
       Node *temp = (*target)->next;
       if (temp == nullptr) {
           delete *target;
           *target = nullptr;
           //cout << "elem " << num << " deleted " << endl;
           return;
       }
       (*target)->num = temp->num;
       (*target)->next = temp->next;
       delete temp;
    }
    

    但您的删除功能似乎无法正常工作。如果该元素等于 num 参数,它将仅删除列表的最后一个元素。 为了使其正常工作,您可能需要使此列表向前和向后迭代,以使前一个元素指向下一个元素。或者您也可以更改 find 函数以返回前一个元素。

    【讨论】:

    • 这是我感兴趣的逻辑,但我如何用这个重新连接列表的其余部分?
    • 我找到了一个使用您的代码进行查找的解决方案,它没有理由重复链接它。 codepad.org/wplNFKMx 谢谢 :)
    【解决方案3】:

    将指针设置为nullptr 不一定会更改其值。如果您取消引用 nullptr,您将煽动未定义的行为 (see here)。在这种情况下,未定义的行为意味着您仍然可以引用该内存并对其进行解释。但下一次它可能会让你的程序崩溃。你在这里做的很糟糕:

    if (temp == nullptr) {
           delete target;  
           target = nullptr;       // You delete target
    } 
    target->num = temp->num;       // Then you immediately dereference it.
    

    您需要在删除target之前设置下一个

    target->next = temp->next;
    delete temp;
    

    【讨论】:

    • 我的错,我忘了把return放在if的末尾,同样的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-28
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多