先说结论:当单链表具有初始化函数的时候,插入函数的形参可以不用多重指针,直接传入头结点的地址也行,但是初始化函数的形参必须要用多重指针,即指向头结点指针的指针。
目录
代码实现
单链表的C语言实现如下(这里使用的是《大话数据结构》里的部分实现)
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定义LinkList */
/* 初始化链式线性表 */
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if(!(*L)) /* 存储分配失败 */
return ERROR;
(*L)->next=NULL; /* 指针域为空 */
return OK;
}
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
j = 1; /* j为计数器 */
while (p && j<i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{
p = p->next; /* 让p指向下一个结点 */
++j;
}
if ( !p || j>i )
return ERROR; /* 第i个元素不存在 */
*e = p->data; /* 取第i个元素的数据 */
return OK;
}
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
为什么初始化函数要用多重指针作为形参?
当初始化函数直接使用指向链表头结点的指针时,初始化函数的代码如下:
/* 初始化链式线性表 */
Status InitList(LinkList L)
{
L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if(!L) /* 存储分配失败 */
return ERROR;
L->next=NULL; /* 指针域为空 */
return OK;
}
调用此函数的代码如下
//调用初始化函数
LinkList node;
InitList(node);
执行结果如图所示
可以看到当调用InitList(LinkList L)时,实参node和形参L都指向空表。当初始化完成之后(退出函数之前),实参node依旧指向空表,而形参L指向新创建的链表头结点。
如果初始化函数的形参使用多重指针
/* 初始化链式线性表 */
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if(!(*L)) /* 存储分配失败 */
return ERROR;
(*L)->next=NULL; /* 指针域为空 */
return OK;
}
//调用函数
LinkList node;
InitList(&node); //将指针node的地址作为参数,而不是node指向的地址作为参数。
执行结果如图所示
图中调用函数时和初始化完成之后(退出函数之前)形参L的值没有改变,改变的是实参node的值。调用函数时node指向空表,初始化之后node指向新创建链表的头结点。
为什么会造成这种情况?
C语言中实参变量和形参变量之间的数据传递是单向的“值传递”。 --谭浩强 《C程序设计第五版》
这句话可以这样理解,在调用函数时,形参变量从实参变量那里拷贝了一份数据。在函数内部对形参变量的数据进行处理,并不会影响到实参变量的数据。
如果将实参传递给形参,企图在函数内部通过修改形参达到修改实参的目的,是行不通的。如果实参和形参是指针类型的话,是可以在函数内部修改实参和形参指向的变量,达到修改变量值的目的,这就解释了为什么链表初始化函数的参数要使用多重指针。
//调用初始化函数
LinkList node;
InitList(node);
初始化的目的是创建头节点,并将头结点的地址赋给变量node,如果将node作为实参传递给有相同类型形参的初始化函数,则达不到初始化的目的。
举个例子
怎样在函数中实现交换两个整型变量。
代码1.交换两个指针指向的整型数据
void swap(int *p1, int *p2){
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
调用函数
nt num1 = 1;
int num2 = 4;
int *point1, *point2;
*point1 = num1;
*point2 = num2;
swap(point1, point2);
函数执行过程如图所示
如图所示,函数运行完成(退出函数之前),实参和形参指向的变量的值发生了交换。
代码2:交换两个指针的值
void swap(int *p1, int *p2){
int *temp;
temp = p1;
p1 = p2;
p2 = temp;
}
调用此函数
int num1 = 1;
int num2 = 4;
int *point1, *point2;
*point1 = num1;
*point2 = num2;
swap(point1, point2);
函数执行过程如下
如图所示,实参的值都没有发生变化,改变的只是形参的值,没有达到交换num1和num2的值的目的。
为什么插入函数的形参可用可不用多重指针?
我想书上之所以使用多重指针是为了让读者更容易区分哪些操作要修改单链表,哪些不需要。凡是需要修改单链表的操作,比如初始化,插入,删除,在参数中都使用了多重指针。其他操作,比如查询操作,在参数中没有使用多重指针。
在形参中使用多重指针
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
在形参中不使用多重指针
/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList L,int i,ElemType e)
{
int j;
LinkList p,s;
p = L;
j = 1;
while (p && j < i) /* 寻找第i个结点 */
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
上面两个函数都是在头结点以后的结点做添加操作,并不是对LinkLIst L或LinkList *L做修改,所以上述两种形式都可以完成插入结点的操作。
有什么不明白的地方欢迎在评论区讨论。
tips:作图软件用的是vscode的draw.io插件。