昨天用结构体指针写了一个双链表的程序,编译环境是VC6.0,之前写单链表的时候也是用的这个编译器,但是昨天出了一个让我很费解的问题,代码如下:
/**********************************************************
* C语言实现双链表
*文件名:list.c
*作者:Mr Wan
*编写时间:2015.9.9
*功能:实现双链表的简单操作
*版本:1.0
*
**********************************************************/
#include<stdio.h>
#include<stdlib.h>
#define SIZE sizeof(NODE)//每个节点所占内存的大小
typedef enum
{
FALSE=0,
TRUE=1
}BOOL;//自定义BOOL类型
typedef struct
{
int val_0;//数据val_0
int val_1;//数据val_1
}DAT;//节点的数据域
typedef struct
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
}NODE;//定义节点结构体
static unsigned int Length=0;//链表的长度,即出去零节点外之后的所有节点数
NODE* InitList(void);
BOOL AddNode(NODE* Head,DAT dat);
BOOL AddNodeAt(NODE* Head,unsigned int pos,DAT dat);
void ListTest(NODE* Head);
void main(void)
{
NODE* List=NULL;
DAT dat;
dat.val_0=1;
dat.val_1=13;
List=InitList();
AddNode(List,dat);
ListTest(List);
AddNode(List,dat);
ListTest(List);
AddNode(List,dat);
ListTest(List);
}
/**********************************************************
* NODE* InitList(void)
*功能:初始化一个链表,并将链表头指针返回
*输入:void
*输出:链表的头指针
*
**********************************************************/
NODE* InitList(void)
{
NODE* Head=(NODE*)malloc(SIZE);
if(NULL==Head)
{
printf("申请内存失败!\n");
return NULL;
}
else
{
Head->prev=NULL;
Head->next=NULL;
Length=0;
printf("链表初始化成功!\n");
return Head;
}
}
/**********************************************************
* BOOL AddNode(NODE* Head,DAT dat)
*功能:在一个已知的链表最后添加一个节点
*输入:已知链表的表头指针,待添加节点的数据域
*输出:TRUE--成功/FALSE--失败
*
**********************************************************/
BOOL AddNode(NODE* Head,DAT dat)
{
unsigned int i=0;
NODE* p=Head;
NODE* New=NULL;
while(i<Length)
{
p=p->next;
i++;
}
New=(NODE*)malloc(SIZE);
if(NULL==New)
{
printf("申请内存失败!\n");
return FALSE;
}
else
{
p->next=New;
New->prev=p;
New->next=NULL;
New->dat=dat;
Length++;
return TRUE;
}
}
/**********************************************************
* BOOL AddNodeAt(NODE* Head,unsigned int pos,DAT dat)
*功能:在一个已知的链表中的pos位置之前添加一个节点
*输入:已知链表的表头指针,添加的位置pos,待添加节点的数据域
*输出:TRUE--成功/FALSE--失败
*
**********************************************************/
BOOL AddNodeAt(NODE* Head,unsigned int pos,DAT dat)
{
unsigned int i=0;
NODE* p=Head;
NODE* New=NULL;
if(pos>Length)
{
printf("pos不应该大于链表的Length!\n");
return FALSE;
}
else
{
while(i<pos-1)
{
p=p->next;
i++;
}//while
New=(NODE*)malloc(SIZE);
if(NULL==New)
{
printf("申请内存失败!\n");
return FALSE;
}
else
{
New->next=p->next;
p->next->prev=New;
p->next=New;
New->prev=p;
New->dat=dat;
Length++;
return TRUE;
}
}//else
}
/**********************************************************
* void ListTest(NODE* Head)
*功能:链表的测试函数
*输入:待测试链表的表头指针
*输出:void
*
**********************************************************/
void ListTest(NODE* Head)
{
unsigned int i=0;
NODE* p=Head;
while(NULL!=p->next)
{
p=p->next;
i++;
printf("NODE%d的数据域中val_0=%d,val_1=%d\n",i,p->dat.val_0,p->dat.val_1);
}
printf("Test Over,Length of List=%d.\n\n\n",Length);
}
如上,编译器报错:
C:\Documents and Settings\Administrator\桌面\双链表\list.c(168) : error C2037: left of 'prev' specifies undefined struct/union 'NODE'
编译器提示:代码中p->next->prev=New;有错误。按照错误提示就是说,prev左边有未定义的NODE型结构体。这我就很纳闷了,prev左边是p->next,按道理说p->next也是NODE*型的,p->next->prev也是NODE*型的,而New也是NODE*型的,也就是说等号两边的变量类型是一致的,可是编译器还是报了错。
我跟着编译器提示的错误,尝试着将p->next进行了强制类型转换,如下:
(NODE*)(p->next)->prev=New;编译器还是提示错误,我又试着将上述代码该成了这样:((NODE*)(p->next))->prev=New;这个时候编译器不报错了,运行结果正常,这样我就又不理解了,上述两条代码有什么区别吗?假如有区别的话,应该区别就在于运算优先级的区别。那么我就不明白了上述两条代码的运算优先级有什么不一样。求高手解答。
上述这个问题先放在一边,我将代码copy到百度知道求高手指教,有网友回答如下:恐怕问题出在结构体模板定义之中,NODE还没有被声明,却在结构体体中应用了。还是为结构体起一个名字吧……
给结构体起一个名字?什么意思?我看了一下,我代码中关于结构体的定义,如下:
typedef struct
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
}NODE;//定义节点结构体
我将
struct
{
……
}
这个类型变为NODE型,但是上述结构体没有给他取名字,
我试着 将代码改成这样:
typedef struct NODE
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
}NODE;//定义节点结构体
这时候编译没有报错,结果正确。这样就证明上面那个网友说的是正确的。按照之前错误的写法,typedef只是将
struct
{
……
}
这个类型重新定义别名NODE型,然而在取别名之前要先把被取别名的对象定义好,即先对结构体进行定义:
struct
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
};
从上述代码中我们可以看到,在这个未取名的struct中,我们定义了两个struct NODE*的结构体指针,
接下来编译器才将未取名的struct取个别名叫NODE。
也就是说在给结构体取别名为NODE之前,在结构体中我们已经定义了两个struct NODE*的结构体指针,这样两个NODE之间就没有关系,这两个NODE不是一个NODE,结构体里面的NODE和重新给结构体取的别名NODE两者的作用域也是不一样的。也就是说代码中:p->next是结构体里面的那个NODE*类型,里面的那个NODE*和结构体NODE是不一样的,结构体NODE里面有三个成员变量,而结构体里面的那个NODE是没有成员变量的,这样以来p->next->prev就会出错,因为p->next是结构体里面的那个NODE*类型的,所以p->next是没有成员变量prev的。
所以后来我们将p->next强制转换成NODE*,这个时候就是将结构体里面的那个struct NODE*强制转换成和外面的结构体一样的类型,因为外面的结构体类型的作用域是全局的,所以在强制转换时,虽然有两种NODE*,但是由于结构体里面的NODE*是局部变量,所以强制转化是将被转换的对象转换成和外面的结构体一样的类型,而外面的结构体类型是有成员变量的,这样以来编译器就不报错了。
但是正确的做法不是这样的,经过和同学讨论,正确的应该是这样写:
typedef struct NODE
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
}NODE;//定义节点结构体
这就是说,我们先定义一个结构体
struct NODE
{
DAT dat;//节点的数据域
struct NODE* prev;//前置节点地址
struct NODE* next;//后驱节点地址
}
在这个结构体里面我们又定义了两个struct NODE*的变量,由于在这两个变量定义之前,我们已经对NODE进行了声明,所以里面的NODE和外面的NODE就是一样的了,然后我们再给外层的结构体取一个别名就NODE,这三个NODE就是一样的NODE了。这个时候就对了。
最后,刚刚咨询了本科C语言老师关于(NODE*)(p->next)->prev=New;和((NODE*)(p->next))->prev=New;的不同,老师的原话是这样的:->的优先级高与强制类型转换 (NODE*)的优先级,这样就解释得了为什么前者仍然报错而后者不报错了。
因为->的优先级比强制类型转换(NODE*)优先级别高,这样前者就相当于将p->next->prev转换成NODE*,这样以来还是没有解决我们上述提到的问题——p->next是结构体内层的结构体,他没有成员变量prev,这样编译器仍然报错。而后者相当于是将p->next转换成NODE*,这时候NODE*是全局的NODE*也就是外层结构体,所以这时候p->next具有成员变量prev,这个时候编译器就不会报错了。