带头结点和不带头结点单链表存在的问题
带头结点和不带头结点单链表注意的小细节
在写不带头结点的单链表中发现了一个问题,这个问题在带头结点的单链表中也存在,那就是值传递的问题。
首先来看一下
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#define Destroy Clear //Destroy和Clear操作是一样的
//和设头结点的单链表在定义存储结构上是相同的
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
}NODE,*PNODE;
PNODE Create2()
{
//在表尾插入数据
int i,n;
PNODE pHead=NULL,pNew,pTail;
printf("请输入数据元素的个数:");
scanf("%d",&n);
if(n<1)
exit(-1);
pHead=(PNODE)malloc(sizeof(NODE));//生成一个首结点
if(NULL==pHead)
exit(-1);
printf("请输入第1个元素的值:");
scanf("%d",&pHead->data);
pHead->next=NULL;
pTail=pHead;
for(i=0;i<n-1;i++)
{
pNew=(PNODE)malloc(sizeof(NODE));
if(NULL==pNew)
exit(-1);
printf("请输入第%d个元素的值:",i+2);
scanf("%d",&pNew->data);
pNew->next=pTail->next;
pTail->next=pNew;
pTail=pNew;
}
return pHead;
}
void Clear(PNODE pHead)
{
PNODE p=pHead;
while(p!=NULL)
{
p=pHead->next;
free(pHead);
pHead=p;
}
}
void Traverse(PNODE pHead)
{
PNODE p=pHead;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
PNODE pHead=Create2();
printf("初始化后元素:");
Traverse(pHead);
printf("销毁链表后:\n");
Clear(pHead);
Traverse(pHead);
return 0;
}
这是从不带头结点的单链表源码中提取的一段代码,以下是程序运行结果
为什么在程序最后会出现-572662307这样的乱码呢?而为什么在前一篇数据结构之带头结点的单链表中最后在销毁链表之后,并且遍历链表元素之后没有出现乱码呢?
首先来看一下从上一篇提取问题代码之后的代码
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node
{
int data;
struct Node *next;
}NODE,*PNODE;
PNODE Create2()
{
int i,n;
PNODE pHead,pTail,pNew;
pHead=(PNODE)malloc(sizeof(NODE));
if(NULL==pHead)
exit(-1);
pHead->next=NULL; //生成头结点
pTail=pHead;
printf("请输入数据元素的个数:");
scanf("%d",&n);
for(i=0;i<n;i++)
{
pNew=(PNODE)malloc(sizeof(NODE));
if(NULL==pNew)
exit(-1);
printf("请输入第%d个元素的值:",i+1);
scanf("%d",&pNew->data);
pTail->next=pNew;
pTail=pTail->next;
pNew->next=NULL;
}
return pHead;
}
void Destroy(PNODE pHead)
{ //销毁pHead指向的单链表:头结点也不存在只剩一个头指针,且头指针为NULL
PNODE p;
while(pHead!=NULL)
{
p=pHead->next;
free(pHead);
pHead=p;
}
}
void Traverse(PNODE pHead)
{
PNODE p=pHead->next;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
PNODE pHead=Create2();
printf("初始化后的元素:");
Traverse(pHead);
Destroy(pHead);
printf("销毁单链表后:");
Traverse(pHead);
return 0;
}
代码运行之后:
通过观察比较,表面的问题出现在Traverse()这个函数上,
而两个Traverse的区别是:在带头结点的单链表中先执行
p=pHead->next;
然后判断p是否为空在输出p->data;
而在不带头结点的单链表中先执行
p=pHead;
然后判断p是否为空在输出p->data;
两者差别就在于不带头结点的输出的是pHead->data;带头结点输出的是pHead->next->data;
下面来分析一下这个问题产生的原因:注意:上图为执行不带头结点的Clear函数或Destroy函数,带头结点的Destroy函数执行图只不过就在第一个节点前添加一个头节点,但导致的问题都是相同的。
首先,在Clear函数时,传递的实参是pHead,形参也是pHead,这样就相当于值传递,让实参和形参都指向相同的元素,但是销毁链表时,是形参的pHead在执行,当释放第一个节点后,形参pHead指向下一个元素,但实参由于指向第一个结点,并且已被释放,所以此时实参pHead内容为乱码,不再是0x01,但是在遍历的时候,不带头结点的会先判断pHead是否为空,因为其中为乱码所以不为空,所以会输出pHead->data为乱码,pHead指向它的下一个结点,检测到下一个结点为空,跳出while循环,所以执行结果会看到乱码,而在带头结点的在进行循环前pHead已经指向它的下一个结点,也就是说它的下一个结点已经为空,所以不会执行while循环,直接结束,也就不会看到乱码现象。
这种问题该如何修改呢?
以下提供了两种解决方法(以不带头结点的单链表为例)
第一种:用C语言方式
具体代码如下:
void Clear(PNODE *pHead)
{
PNODE p=*pHead;
while(p!=NULL)
{
p=(*pHead)->next;
free(*pHead);
*pHead=p;
}
}
int main()
{
PNODE pHead=Create2();
printf("初始化后元素:");
Traverse(pHead);
printf("销毁链表后:\n");
Clear(&pHead);
Traverse(pHead);
return 0;
}
对以上两个函数进行修改即可
下面给出修改后的函数执行图:
当释放第一个节点后注意:形参pHead存储的是实参pHead变量本身的地址,而通过在函数里使用*pHead意义就是代表形参pHead的内容,也就是实参pHead,由此可以看出修改前后的区别就是,修改前是通过形参来操作函数的销毁,实参不动。而修改过后通过形参控制实参,由实参来操作函数的销毁。由此可以看出两者的本质区别
第二种:用c++引用实现
void Clear(PNODE &pHead)
{
PNODE p=pHead;
while(p!=NULL)
{
p=pHead->next;
free(pHead);
pHead=p;
}
}
int main()
{
PNODE pHead=Create2();
printf("初始化后元素:");
Traverse(pHead);
printf("销毁链表后:\n");
Clear(pHead);
Traverse(pHead);
return 0;
}
在原始错误的基础上只需要将Clear函数的形参改为(PNODE &pHead)即可。
另外一个值的注意的是在对不带头结点的单链表进行操作时传递参数必须是以上的两种方式,这也是和带头结点单链表在插入和删除操作上的重要区别