很多人在学习完链表之后几乎没有怎么用过,有这么几个原因可能导致大家对于直接写链表有一种恐惧心理:
1.在学习完C++之后,尤其是学习完STL之后,会接触到一种链表容器,这个容器可以很简单的实现大家所排斥的链表的创建,增删改查,以及一些常用的基础算法,比如链表的逆序,链表的排序都有很直接的类似于“java”一样方便的接口出来,可以直接去调用,就省得出现很多bug。
2.其实最主要的还是对于链表节点的“不适用性”而感到麻烦。这主要集中在,链表节点无法代表全部的结构体内容,就比如说,我要做一个聊天室,那么在聊天室中,我肯定要有一个用户链表,在这个用户链表中很多属性是不同的,就比如说,超级用户、管理员和普通用户,他们的权限,以及基本的属性肯定都是不同的。再或者说,如果是一个教务管理系统,然后其中有老师,有学生,他们的属性也是不同,甚至于导致他们的结构体的大小“sizeof()”都是不同的,那么问题就来了,如果在这种情况下,我们还需要将不同大小的节点都联系在一起,怎么处理?
首先看一段来自于linux内核的代码:
在内核Mkregtale.c文件中
- /*
- * This is a simple doubly linked list implementation that matches the
- * way the Linux kernel doubly linked list implementation works.
- */
- struct list_head {
- struct list_head *next; /* next in chain */
- struct list_head *prev; /* previous in chain */
- };
/*
* This is a simple doubly linked list implementation that matches the
* way the Linux kernel doubly linked list implementation works.
*/
struct list_head {
struct list_head *next; /* next in chain */
struct list_head *prev; /* previous in chain */
};
这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:
- struct score
- {
- int num;
- int English;
- int math;
- struct list_head list;//链表链接域
- };
- struct list_head score_head;//所建立链表的链表头
struct score
{
int num;
int English;
int math;
struct list_head list;//链表链接域
};
struct list_head score_head;//所建立链表的链表头
INIT_LIST_HEAD(&score_head);//初始化链表头 完成一个双向循环链表的创建
上面的红色部分初始化一个已经存在的list_head对象,score_head为一个结构体的指针,这样可以初始化堆栈以及全局区定义的score_head对象。调用INIT_LIST_HEAD()宏初始化链表节点,将next和prev指针都指向其自身,我们就构造了一个空的双循环链表。
初始化一个空链表:其实就是链表头,用来指向第一个结点!定义结点并且初始化!然后双向循环链表就诞生了
static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表 示编译程序在调用这个函数时,立即将该函数展开。
- /* Initialise a list head to an empty list */
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
/* Initialise a list head to an empty list */
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
list_add:在链表头插入节点
- /**
- * list_add - add a new entry
- * @new: new entry to be added
- * @head: list head to add it after
- *
- * Insert a new entry after the specified head.
- * This is good for implementing stacks.
- */
- static inline void list_add(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head, head->next);
- }
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
- /*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
- #ifndef CONFIG_DEBUG_LIST
- static inline void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next)
- {
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
- }
- #else
- extern void __list_add(struct list_head *new,
- struct list_head *prev,
- struct list_head *next);
- #endif
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
list_add_tail:在链表尾插入节点
- /**
- * list_add_tail - add a new entry
- * @new: new entry to be added
- * @head: list head to add it before
- *
- * Insert a new entry before the specified head.
- * This is useful for implementing queues.
- */
- static inline void list_add_tail(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head->prev, head);
- }
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
用法示例:
struct score
{
int num;
int English;
int math;
struct list_head list;//链表链接域
};
struct list_head score_head;//所建立链表的链表头
//定义三个节点 然后插入到链表中
struct score stu1, stu2, stu3;
list_add_tail(&(stu1.list), &score_head);//使用尾插法
Linux 的每个双循环链表都有一个链表头,链表头也是一个节点,只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。
list_del:删除节点
- /* Take an element out of its current list, with or without
- * reinitialising the links.of the entry*/
- static inline void list_del(struct list_head *entry)
- {
- struct list_head *list_next = entry->next;
- struct list_head *list_prev = entry->prev;
- list_next->prev = list_prev;
- list_prev->next = list_next;
- }
/* Take an element out of its current list, with or without
* reinitialising the links.of the entry*/
static inline void list_del(struct list_head *entry)
{
struct list_head *list_next = entry->next;
struct list_head *list_prev = entry->prev;
list_next->prev = list_prev;
list_prev->next = list_next;
}
list_entry:取出节点
- /**
- * list_entry - get the struct for this entry
- * @ptr:the &struct list_head pointer.
- * @type:the type of the struct this is embedded in.
- * @member:the name of the list_struct within the struct.
- */
- #define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
/**
* list_entry - get the struct for this entry
* @ptr:the &struct list_head pointer.
* @type:the type of the struct this is embedded in.
* @member:the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
- /**
- * container_of - cast a member of a structure out to the containing structure
- * @ptr: the pointer to the member.
- * @type: the type of the container struct this is embedded in.
- * @member: the name of the member within the struct.
- *
- */
- #define container_of(ptr, type, member) ({ \
- const typeof(((type *)0)->member)*__mptr = (ptr); \
- (type *)((char *)__mptr - offsetof(type, member)); })
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
list_for_each:遍历链表
- #define list_for_each(pos, head) \
- for (pos = (head)->next; prefetch(pos->next), pos != (head); \
- pos = pos->next)</span></span>
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)</span></span>
可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,
直到pos到达头节点指针head的时候结束。
而且 这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结点的域?
list.h 中定义了 list_entry 宏:
#define list_entry( ptr, type, member ) \
( (type *) ( (char *) (ptr) - (unsigned long) ( &( (type *)0 ) -> member ) ) )
分析:(unsigned long) ( &( (type *)0 ) -> member ) 把 0 地址转化为 type 结构的指针,然后获取该
结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中 (char *) (ptr) 求
出的是 ptr 的绝对地址,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址。使用方法非常的巧妙!
在看linux的代码中,我们可以了解到,其实在真正的项目中,链表要和业务模块要分开,也就是说,链表要做到,插入删除,创建等等的操作独立。
我写了一个小例子,大家应该可以看明白,是一个什么意思:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Teacher
{
int age;
int id;//如果id是1就是代表是Teacher1如果id是2就代表是Teacher2
}Teacher;
typedef struct Teacher1
{
int age;
int id;
int num1;
}Teacher1;
typedef struct Teacher2
{
int age;
int id;
int num2;
}Teacher2;
int main(int argc,char**argv)
{
Teacher1 *t1 = (Teacher1*)malloc(sizeof(Teacher1));
Teacher2*t2 = (Teacher2*)malloc(sizeof(Teacher2));
t1->age = 22;
t1->id = 1;
t1->num1 = 33;
t2->age = 44;
t2->id = 2;
t2->num2 = 55;
int*address = &(t1->age);
if(((Teacher*)address)->id == 1)
{
printf("num = %d\n", ((Teacher1*)address)->num1);
}
//printf("%d\n", ((Teacher*)address)->id);//这就是企业级链表得使用方法
system("pause");
return 0;
}
我们可以看到,如果在结构体的首地址申明一个“节点”,让他们去组成链表,然后再去使用强制类型转化的方法去将一个节点转化为一个新的内容,从而实现一种类似多态的思想。
那么接下来,我们需要做的事情就是将链表操作的方法做成接口,然后将接口进行一个包装,放入DLL,或者SO中,让外部使用,代码如下:
//接口抛出
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define offscfof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr,type,member) (type *)((char *)ptr-offscfof(type,member))
typedef struct _node{
struct _node *pNext;
}Node;
typedef struct _student1{
int num;
char name[20];
Node mynode;
}Student1;
//说明:结构体也可以写成以下模式
//typedef struct _student1{
// Node mynode;
// int num;
// char name[20];
//}Student1;
//创建链表
int SList_Create(Node **pout/*out*/);
//获取链表长度
int Get_List_Len(Node *pin/*in*/);
//查找指定位置节点
int FindNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/);
//插入指定位置节点
int InsertOption(Node *pin/*in*/, Node *pnode/*in*/, int pos/*in*/);
//删除指定节点
int RemoveNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/);
void main(){
//创建链表指针
Node *phead;
int i = 0,j=0;
int ret = SList_Create(&phead);
//说明:为什么我要创建一个无用的头节点
//理由①:不创建一个头节点,那么初始化函数SList_Create()就没有必要存在
//理由②:插入第一个节点的时候无法插入,以为没有头结点,所以插不进去第一个节点(这是主要理由)
if (ret!=0)
{
printf("创建链表头节点失败!\n");
}
//添加新节点
Student1 *pa = (Student1 *)malloc(sizeof(Student1));
pa->num = 1;
strcpy(pa->name, "小米");
pa->mynode.pNext = NULL;
ret=InsertOption(phead, &pa->mynode, Get_List_Len(phead));
if (ret != 0)
{
printf("添加新节点a失败!\n");
goto END;
}
Student1 *pb = (Student1 *)malloc(sizeof(Student1));
pb->num = 1;
strcpy(pb->name, "小明");
pb->mynode.pNext = NULL;
ret = InsertOption(phead, &pb->mynode, Get_List_Len(phead));
if (ret != 0)
{
printf("添加新节点b失败!\n");
goto END;
}
//打印出所有的节点
for (j = 0; j < Get_List_Len(phead); j++)
{
Node *temp = NULL;
Student1 *temp2 = NULL;
FindNode(phead, &temp, j);
if (temp==NULL)
{
printf("查询节点失败\n");
}
else{
temp2 = container_of(temp, Student1, mynode);
printf("学生的编号:%d;学生的姓名%s\n", temp2->num, temp2->name);
}
}
END:
//删除所有链表节点
while (Get_List_Len(phead)){
Node *temp = NULL;
Student1 *temp2 = NULL;
RemoveNode(phead, &temp, 0);
temp2 = container_of(temp, Student1, mynode);
if (temp == NULL)
{
printf("节点删除失败!\n");
}
else{
if (temp2 != NULL)
{
free(temp2);
}
}
}
//释放头节点
if (phead==NULL)
{
free(phead);
}
system("pause");
}
//创建链表(顺序创建链表)
int SList_Create(Node **pout/*out*/){
int ERRO_MSG = 0;
if (pout==NULL)
{
ERRO_MSG = 1;
printf("pout==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pM = (Node *)malloc(sizeof(Node));
pM->pNext = NULL;
*pout = pM;
return ERRO_MSG;
}
//获取链表长度
int Get_List_Len(Node *pin/*in*/){
Node *pHead = NULL, *pCurrent = NULL;
int index = 0;
pCurrent = pin->pNext;
while (pCurrent){
pCurrent = pCurrent->pNext;
index++;
}
return index;
}
//查找指定位置节点
int FindNode(Node *pin/*in*/, Node **pnode/*out*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pnode == NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pnode==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL, *pPrior = NULL;
pCurrent = pPrior = pin->pNext;
if (pCurrent==NULL)
{
ERRO_MSG = 2;
printf("链表中暂时没有数据 erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
int index = 0;
while (pCurrent){
if (index==pos)
{
*pnode = pCurrent;
break;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
if (*pnode==NULL)
{
ERRO_MSG = 3;
printf("链表中没有找到该节点 erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
return ERRO_MSG;
}
//插入指定位置节点
int InsertOption(Node *pin/*in*/, Node *pnode/*in*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pnode==NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pnode==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL,*pPrior=NULL;
pHead = pPrior = pin;
pCurrent = pin->pNext;
pMalloc = pnode;
if (pCurrent==NULL)
{
if (pos==0)
{
pHead->pNext = pMalloc;
return ERRO_MSG;
}
else{
ERRO_MSG = 2;
printf("链表为空,无法在指定位置插入节点\n", ERRO_MSG);
return ERRO_MSG;
}
}
int index = 0;
while (pCurrent){
if (pos == index)
{
pPrior->pNext = pMalloc;
pMalloc->pNext = pCurrent;
return ERRO_MSG;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
pPrior->pNext = pMalloc;
return ERRO_MSG;
}
//删除指定节点
int RemoveNode(Node *pin/*in*/, Node **pdel/*out*/, int pos/*in*/){
int ERRO_MSG = 0;
if (pin == NULL || pdel==NULL)
{
ERRO_MSG = 1;
printf("pin == NULL || pdel==NULL erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
Node *pHead = NULL, *pCurrent = NULL, *pMalloc = NULL, *pPrior = NULL;
pHead = pPrior = pin;
pCurrent = pin->pNext;
if (pCurrent==NULL)
{
ERRO_MSG = 2;
printf("你要删除的链表为空! erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
int index = 0, flag = 0;
while (pCurrent){
if (index == pos)
{
pPrior->pNext = pCurrent->pNext;
*pdel = pCurrent;
break;
}
pPrior = pCurrent;
pCurrent = pCurrent->pNext;
index++;
}
if (*pdel==NULL)
{
ERRO_MSG = 3;
printf("链表中没有该位置的节点! erro msg:%d\n", ERRO_MSG);
return ERRO_MSG;
}
return ERRO_MSG;
}