通用链表
- 学习使用通用链表之前肯定写过的单向链表或者双向链表,以上两种链表在同一条链上只能挂在同一类型的结构体,因为不同结构体中的指针域类型不相同,所以通用链表很好的解决了这个问题,说到这里,相信在你的脑海已经有了通用链表的架构了,接下来我来详细的介绍一下通用链表的创建和基本应用。
- 通用链表虽然可以处理不同类型的数据,但是操作相对比较复杂。
1. 双向链表和通用链表图解对比
1.1 双向链表指针域图解
从图中可以看出双向链表的前向指针指向前一个结点的首地址,后向指针指向下一个节点的首地址,并且指针类型相同,且只能指向与自己类型相同的结构体。
1.2 通用链表指针域图解
从图中可以看出通用链表的前向指针指向前一个结点的指针域的首地址,后向指针指向下一个节点的指针域的首地址,所以在不同的结构体中定义相同的指针域就可以了,也就是说,单独定义一个结构体类型,其成员只有指针域,并且在其他结构体中定义这种结构体类型的变量就ok了。下面我们看一些代码。
2. 通用链表的结构体代码示例
我这里以学生管理系统为例对指针域类型的结构体,学生类型的结构体,教师类型的结构体进行演示
2.1 定义指针域类型的结构体
先来看一下代码:
//**************指针域************//
typedef struct node //该结构体是专门的指针域,包含在每个节点之中
{
struct node *prev; //前向指针
struct node *pnext; //后向指针
}NODE,*PNODE;
代码注释的已经很清楚了,我就不啰嗦了!!!
2.2 定义学生、教师类型的结构体
2.2.1 学生结构体代码:
typedef struct student //学生
{
int num;
char name[20];
float score;
NODE point; //定义一个指针域类型的变量
}STU,*PSTU;
2.2.2 教师结构体代码:
typedef struct teacher //教师
{
int num;
char name[20];
char subj[20];
NODE point; //同上
}TEA,*PTEA;
结构体定义好之后我们来创建一个链表。
3. 通用链表的操作
- 还有需要注意的一点是通用链表的创建都是用宏定义来写的。
- 链表的操作大概有创建表头,插入结点(头插法和尾插法等),删除结点,查找结点,修改结点等,我这里就写其中的几个作为列子来写一下。
3.1 创建链表头(头结点的初始化)
在使用之前头一定要有相应的空间
代码如下:
#define Head_node_initial(head) \
{ \
(head)->prev=NULL; \
(head)->pnext=NULL; \
}
注意:宏定义都是写成一行,别丢了换行符 ‘\’,下同。
3.2 插入结点
这里需要注意的是我在写插入结点代码是没有对该结点进行初始化,一定要记得在使用这个宏定义之前先对结点进行初始化。
3.2.1 头插法:
代码如下:
#define Insert_one_node_by_head(head,pnew) \
{ \
(pnew)->pnext=(head)->pnext; \
if((head)->pnext!=NULL) \
(head)->pnext->prev=(pnew); \
(pnew)->prev=(head); \
(head)->pnext=(pnew); \
}
3.2.1 尾插法:
代码如下:
#define Insert_one_node_by_tail(head,pnew) \
{ \
PNODE pa=head; \
while(pa->pnext!=NULL) pa=pa->pnext; \
(pa)->pnext=(pnew); \
(pnew)->pnext=NULL; \
(pnew)->prev=(pa); \
}
3.3 删除结点
代码如下:
#define Delete_one_node(pdel) \
{ \
if((pdel)->pnext==NULL) \
(pdel)->prev->pnext=NULL; \
(pdel)->prev->pnext=(pdel)->pnext; \
(pdel)->pnext->prev=(pdel)->prev; \
}
4. 访问通用链表数据域 (难点)
这一部分也是通用链表里面最难理解的一部分,由于通用链表的结点的指针指向另外一个结点的指针域的首地址,这就意味着不可以直接访问数据域,所以需要通过指针域的首地址去找到结点的首地址,从而进一步访问数据域
计算首地址的核心思想:
- 将该结点的首地址看作从地址 0 开始;
- 那么从0到指针域的首地址就是偏移量;
- 指针域的首地址-偏移量=结点首地址;
4.1 求解结点的首地址
代码如下:
#define Get_node_first_addr(pload,Type_struct,pointer) \
{ \
((Type_struct*)((char *)(pload)-(char *)&(((Type_struct*)0)->pointer)))\
}
下面我对这段代码进行一下分析:
(Type_struct*)(char *)(pload)
pload—-该结点指针域的首地址。(char *)&(((Type_struct*)0)->pointer)
((Type_struct*)0)
—-将节点的起始位置看作从 0 开始,强制转换类型取决于结点的结构体类型(char *)&(((Type_struct*)0)->pointer)
—-指针域首地址,这里的指针域首地址就等于偏移量(因为结点首地址从0开始的)指针域的首地址-偏移量=结点首地址;
以上代码都是我亲测过的,你只需要在我的代码基础上直接调用宏就可以使用。