【问题标题】:Delete struct node using pointer-to-pointer使用指针对指针删除结构节点
【发布时间】:2018-02-25 16:20:57
【问题描述】:

假设我有一个链表,下一个函数从链表中删除结构节点

struct list **lpp;
for (lpp = &list; *lpp != NULL; lpp = &(*lpp)->next)
{
    if ((*lpp)->item == i)
    {
        *lpp = (*lpp)->next;
        break;
    }
}

请解释一下:

  1. lpp = &(*lpp)->next,我可以写成lpp = lpp->next,这不一样吗?
  2. *lpp = (*lpp)->下一个

最后,我看不到这个函数如何从列表中删除一个结构节点

【问题讨论】:

  • 你没有展示一个函数,你展示了一个无上下文的sn-p。
  • 它没有。也许你应该展示这个功能。
  • 你能告诉我如何使用双指针删除链表节点吗?

标签: c


【解决方案1】:

lpp 指向列表的第一个元素或某个元素的next 指针。

通过*lpp = (*lpp)->next,您将其直接写入内存。例如。考虑一个列表

| el0 | -> | el1 | -> | el2 | -> NULL
 list     list->next

list 从您的代码指向 el0lpp = &list

现在,有两种情况:

  • el0 匹配 i: --> list 变为 |el0|.nextel1。运行此函数后,您有 | el1 | -> | el2 | -> NULL list list->next
  • elX 匹配i(与X>0):lpp&el_{X-1}.next*lpp = ...,这个.next 将指向elX.next。例如。假设 el1 匹配,你得到 | el0 | -> | el2 | -> NULL

lpp = &(*lpp)->next 用于获取对next 的引用。一个简单的lpp = lpp->next 是不够的,因为它是不同的类型。当您处理 lpp->next 时,*lpp 就像 *lpp->next 一样,它将取消引用下一个元素的内容。

单列表操作

虽然与这个问题无关,但由于其他讨论,更多代码......

假设一个数据结构像

struct node {
    int data;
    struct node *next;
};

在实际代码中,data 不会是该节点的成员,但struct node 将是另一个对象中的混合,并且使用类似container_of 的东西来访问它。但是对于这个问题,保持如上...

我们可以定义一些函数,比如

void slist_add(struct node *node, struct node *root)
{
    node->next = root->next;
    root->next = node;
}

void slist_remove(struct node **node)
{
    if (node)
        *node = (*node)->next;
}

struct node **slist_search(struct node *root, int key)
{
    struct node **ptr;

    for (ptr = &root->next; *ptr; ptr = &(*ptr)->next) {
        if ((*ptr)->data  == key)
            return ptr;
    }

    return NULL;
}

然后,我们使用一个空的struct node 作为锚点:

int main(void)
{
    struct node head = { .next = NULL };

    /* add a node */
    {
        struct node *n = malloc(sizeof *n);
        n->data = 23;

        slist_add(n, &head);
    }

    /* add a node */
    {
        struct node *n = malloc(sizeof *n);
        n->data = 42;

        slist_add(n, &head);
    }

    /* verify our expectations... */
    assert(head.next != NULL);
    assert(head.next->data == 42);

    assert(head.next->next != NULL);
    assert(head.next->next->data == 23);
    assert(head.next->next->next == NULL);

    /* remove the node */
    {
        struct node **ptr = slist_search(&head, 42);

        assert(ptr != NULL);
        assert(*ptr != NULL);
        assert((*ptr)->data == 42);

        if (ptr) {
           struct node *n = *ptr;
           slist_remove(ptr);
           free(n);
        }
    }

    /* remove the node */
    {
        struct node **ptr = slist_search(&head, 23);

        assert(ptr != NULL);
        assert(*ptr != NULL);
        assert((*ptr)->data == 23);

        if (ptr) {
           struct node *n = *ptr;
           slist_remove(ptr);
           free(n);
        }
    }

    assert(head.next == NULL);
}

【讨论】:

  • 它崩溃了!像这样测试。使用n1->data = 24; 添加另一个节点并使用slist_search(&head, 24); 仅删除此节点您没有正确处理head
  • 仍未修复。头部处理不当。提示:void slist_add(struct node *node, struct node *root) 无法正常工作 - 使用指向指针的指针。
  • @sg7 你在添加其他元素后删除/调整了assert(),对吧?
  • 我刚刚添加了一个新节点。我保留了所有的断言。我收到Assertion 'head.next->next == NULL' failed.
  • @sg7 断言用于在执行 this 操作顺序时记录列表的布局。当其他元素添加到列表中时,布局更改并触发断言。 main() 函数是某种单元测试和文档;更改测试时,也必须调整预期结果;)
【解决方案2】:

您的代码是一个极其简化且不完整的节点删除尝试。
您必须处理边缘情况以及实际上free 内存。

这一行:

 *lpp = (*lpp)->next;

负责taking out列表中的节点。 仅当*lpp 是列表头并且列表中有另一个元素时才有效。 *lpp 指向您不再需要的节点,它被列表中的下一个节点替换

 (*lpp)->next;

lpp = &(*lpp)->next,我可以写成lpp = lpp->next,这不是吗 一样吗?

不,不是。而lpp = lpp->next 将无法编译。

& 是一个取消引用运算符。它正在获取节点指针的地址。 你可以把这一行写成

lpp = & ( (*lpp)->next ); 

并且您可以将(*lpp)->next 识别为列表中的下一个节点指针。

lpp 是一个指向指针的指针。 *lpp->next 是编译器已知的表达式,但不是 lpp->next

我猜你误会了
lpp = & ( (*lpp)->next );

作为

lpp = &* (lpp->next); 

并认为&* 会自行取消。

如果要删除列表中间的节点,则必须连接 从要删除的节点之前存在的节点到标记为删除的节点之后的节点。

类似于:

         prev = current;                       
         to_free = current->next;         // node to be freed  

         prev->next = to_free->next;      // connect nodes before deletion        
         free(to_free)

你能告诉我如何使用删除链表节点吗 双指针? – 费拉93

我已经添加了删除节点的测试程序:

#include <stdio.h>
#include <stdlib.h>

// Basic simple single list implementation to illustrate 
// a proper deletion of the node which has a specfic data value.

// Node in List
typedef struct node {
    int data;
    struct node* next; // pointer to next node
}node;

// returns newly created node
node* node_new(int data)
{
    node* new_node = malloc(sizeof(node)); // allocate memory for the node 

    if (new_node == NULL)
        return NULL;                             // protection 

    new_node->data = data;                       // remember the data 
    new_node->next = NULL;                       // no next node

    return new_node;                             // return new created node
}

// The method creates a node and prepends it at the beginning of the list.
// 
// Frequently used names for this method:
// 
// insert at head
// add first
// prepend
//
// returns new head or NULL on failer

node* add_node(node **head, node* new_node)

{
   // Add item to the front of the in_list, return pointer to the prepended node (head)    

    if(head == NULL)
        return NULL;

    if(new_node == NULL)                         // problem, not enough memory
       return NULL;                              // in_list->head has not changed 

/* 
                 new_node      
                   |*| -->  NULL   
                   next        
*/       
    if(*head == NULL)                    // if list is empty 
    {
        *head = new_node;                // the new_node becomes a head   
    }
    else // list already have a head node
    {
/*
                |2|-->|1|-->NULL
                 ^ 
                 |
                 *
                head (2) (list pointer)
*/
        new_node->next = *head;         // now, the new node next pointer points to the node pointed by the list head, see below:       
/* 
          new_node     
            |3|-->     |2|-->|1|-->NULL
                        ^  
                        |
                        *
                       head (list pointer)
*/          
        *head = new_node;               // the list head has to move to new_node ( a new prepanded node)  
 /* 
          new_node       
            |3|-->  |2|-->|1|-->NULL
             ^       
             |           
             *           
            head (3) (list pointer)
*/         
    }
    return *head;                       // we are returning pinter to new_node
}

// Print out list
void print_nodes(node** head)
{
    node* node;

    if (head == NULL) {
        return;
    }

    if (*head == NULL){
        printf("List is empty!\n");
        return;
    }

    printf("List: ");
    node = *head;

    while(node != NULL)
    {
        printf(" %d", node->data);

        node = node->next;
    }

    printf("\n");
}

struct node *find(struct node *start, int data)             // find p to be removed
{
    node* node;

    if (start == NULL)
        return NULL;

    node = start;

    while(node != NULL)
    {
        if (node->data == data)
            return node; 

        node = node->next;
    }

    return NULL;
}

int delete(struct node **start, int data)
{
     struct node *p, *prev, *next, *to_free;

     if (start == NULL)                      // protection
        return 0;

     p = find(*start, data);                 // find element to be removed

     if (p == NULL)
        return 0;

     if (*start == NULL)
        return 0;                            // protection

     if(*start == p)                         // head == p
     {
        if((*start)->next !=NULL)
        {
            *start = (*start)->next;         // move head

            printf("Will be removed: %p\n",p);        
            free(p);                         // remove old head

            return 1;
        }
        else // the only node
        {
            free(p);                        // free the node pointed by *start (header)

            printf("Last node removed\n");
            *start = NULL;                  // header points to NULL 

            return 1;
        }
     }

     // p != start:

     next = *start; 
     while (next != NULL)
     {
        prev = next;                       
        to_free = next->next;                // candidate to be freed   

       if( to_free == p )
        {
            prev->next = to_free->next;      // connect nodes before deletion 

            free(to_free);                   // now free the remembered `next`
            to_free = NULL;                  // so it does not point to the released memory
            return 1;
        }

        next = next->next;                   // this node was not a match 
     } //while

    return 0; 
}

int main() {   
   node *head = NULL; 

   printf("head: %p\n", head);

   node *n1 = node_new(1);
   node *n2 = node_new(2);
   node *n3 = node_new(3);

   print_nodes(&head);

   add_node(&head, n1);
   add_node(&head, n2);
   add_node(&head, n3);

   printf("head points to:  %p\n", head);

  // list has 3 elements
   print_nodes(&head);

   delete(&head, 3);  
   print_nodes(&head);

   delete(&head, 1);  
   print_nodes(&head);

   delete(&head, 2);
   print_nodes(&head);

   printf("head points to: %p\n", head);

   print_nodes(&head);

  return 0;
}

输出:

head: (nil)
List is empty!
head points to:  0x5617cd3802b0
List:  3 2 1
Will be removed: 0x5617cd3802b0
List:  2 1
List:  2
Last node removed
List is empty!
head points to: (nil)
List is empty!

【讨论】:

  • 仅当*lpp 是列表头并且列表中有另一个元素时才有效。不,它甚至适用于其他元素。
  • @MatteoItalia 如果我们在列表中间,prev_el 将不会连接到next_el。我想念什么? *lpp = (*lpp)-&gt;next; 似乎还不够。 (prev_el)->(delete_el-)>(next_el)
  • 非常不优雅的代码;所有的if (... check whether head...) 都是多余的,可以使用指针对指针省略。
  • @ensc 感谢您的评论。在我看来,保护函数免于取消引用 NULL 指针是值得的。
  • 使用pointer-to-pointers的意义在于避免这样的检查。没有它们,代码也能完美运行;你可以用一行实现list_delete() 操作(或两行,当你真的想要一个健全的检查节点是否是NULL
猜你喜欢
  • 2012-04-22
  • 2018-09-20
  • 2020-05-09
  • 2013-12-06
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 2021-01-15
相关资源
最近更新 更多