You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

链表删除与插入节点行为差异:为何delete_from_list可修改头指针?

链表删除与插入操作的行为差异疑惑

我正在学习King所著《C语言程序设计:现代方法》(第二版,2008)中的链表章节,对删除和插入操作的行为差异有疑惑。作者在第429页提到:add_to_list不会修改传入的链表指针,而是返回新创建节点的指针,要让add_to_list直接更新first指针难度很大。但我发现,删除首节点时不会改动原链表,删除中间或末尾节点却会修改原链表;而且delete_from_list同样复制了first指针,为什么它能修改first的指向,add_to_list却做不到?我忽略了什么细节?

以下是相关代码示例:

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

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

struct node *delete_from_list(struct node *, int);
struct node *add_to_list(struct node *, int n);

int main(int argc, char **argv) {

  // setup a linked list
  
  // list's head
  struct node *first= NULL;

  // first node
  struct node *new_node= malloc(sizeof(struct node));
  new_node->value= 10;
  new_node->next= first;
  first= new_node;

  //second node
  new_node = malloc(sizeof(struct node));
  new_node->value= 20;
  new_node->next= first;
  first= new_node;

  //third node
  new_node= malloc(sizeof(struct node));
  new_node->value= 40;
  new_node->next= first;
  first= new_node;

  //fourth node
  new_node= malloc(sizeof(struct node));
  new_node->value= 30;
  new_node->next= first;
  first= new_node;

  int i;
  struct node *head= first;

  
  printf("\n------------------------\n");
  printf("    Original nodes: ");
  for(i=0; head!= NULL; head= head->next, i++)
    printf("\n%d-ith value: %d ", i, head->value);
  printf("\n------------------------\n");


  struct node *first_no20= delete_from_list(first, 20);
  struct node *head_no20= first_no20;
  
  printf("\n------------------------\n");
  printf("    Nodes without 20: ");
  for(i=0; head_no20!= NULL; head_no20= head_no20->next, i++)
    printf("\n%d-ith value: %d ", i,head_no20->value);
  printf("\n------------------------\n");

  
  printf("\n------------------------\n");
  head=first;
  printf("    Original nodes: ");
  for(i=0; head!= NULL; head= head->next, i++)
    printf("\n%d-ith value: %d ", i, head->value);
  printf("\n------------------------\n");


  struct node *first_no30= delete_from_list(first, 30);
  struct node *head_no30= first_no30;
 
  printf("\n------------------------\n");
  printf("    Nodes without 30: ");
  for(i=0; head_no30!= NULL; head_no30= head_no30->next, i++)
    printf("\n%d-ith value: %d ", i,head_no30->value);
  printf("\n------------------------\n");
  

  printf("\n------------------------\n");
  printf("    Original nodes: ");
  head=first;
  for(i=0; head!= NULL; head= head->next, i++)
    printf("\n%d-ith value: %d ", i, head->value);
  printf("\n------------------------\n");

  return 0;

}


struct node *delete_from_list(struct node *list, int n) {
  struct node *cur, *prev;

  for(cur=list, prev=NULL;
      cur != NULL && cur->value !=n;
      prev= cur, cur= cur->next)
    ;

  if(cur == NULL)
    return list;
  if(prev== NULL)
    list= list->next;
  else
    prev->next= cur->next;
  free(cur);

  return list;
}

struct node *add_to_list(struct node *list, int n) {
  struct node *new_node;

  new_node= malloc(sizeof(struct node));
  if(new_node == NULL) {
    printf("Error: malloc failed in add_to_list\n");
    exit(EXIT_FAILURE);
  }

  new_node->value = n;
  new_node->next= list;

  return new_node;
}

核心原因:C语言的值传递机制

C语言中所有函数参数都是值传递,函数拿到的是传入参数的副本,而非原变量本身。这是理解两者行为差异的关键。

1. add_to_list的行为本质

add_to_list接收的list是原first指针的副本。函数内部创建新节点后,让新节点的next指向该副本的指向,但无法修改原first变量的内容——因为副本和原变量是完全独立的两个指针。因此必须返回新节点的地址,由调用者手动将原first赋值为这个返回值,才能更新链表头。

比如直接调用add_to_list(first, 5)不会改变原first,必须写first = add_to_list(first, 5)才会生效。

2. delete_from_list的两种操作场景

delete_from_list同样接收list作为原first的副本,但操作分为两种情况:

  • 删除中间/末尾节点:此时修改的是prev->next——这是链表中某个节点的成员指针,并非函数参数list本身。prev指向原链表中的节点(指针副本指向同一块内存区域),所以修改prev->next会直接改动原链表的结构。
  • 删除首节点:函数内部将list(副本)指向list->next,但这个修改仅在函数内部有效,原first不会自动更新。因此函数必须返回修改后的list,调用者需要手动将原first赋值为返回值(如first = delete_from_list(first, 30)),否则原first仍指向已被释放的节点,会出现野指针问题。

你代码中删除20后原链表被修改,是因为删除的是中间节点,改动的是链表节点内部的next指针;而删除30(首节点)后,原first仍指向已释放的内存,这属于未定义行为——正确做法是将返回值赋值给first,让原指针指向新的链表头。

你忽略的关键细节

  • 无论是add_to_list还是delete_from_list,都无法直接修改原first变量,必须通过返回值让调用者手动更新。
  • 删除中间/末尾节点时,修改的是链表节点的成员(而非函数参数指针本身),因此会影响原链表;而插入操作是创建新节点,需要更新的是链表头指针本身,只能通过返回值传递。
  • 你代码中删除首节点后未更新原first,会导致野指针,这是错误的用法。

内容的提问来源于stack exchange,提问作者utobi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 11:02:07