双向循环链表,内核链表

目录

双向循环链表

1.定义

2.创建双向链表

3.头插法

4.遍历打印

5.尾插法

6.删除

7.销毁

内核链表

 1.内核链表的结构体

2.初始化头结点:INIT_LIST_HEAD

3.头插法:list_add

4.尾插法list_add_tail

5.删除节点: list_del

6.根据当前成员的地址获得节点首地址 :list_entry


双向循环链表

1.定义
//存放数据类型
typedef int DataType;
//节点类型
typedef struct node{
    struct node *pPre;//前一个节点指针
    DataType Data;//数据
    struct node *pNext;//后一个节点指针
}LinkNode;
2.创建双向链表
LinkNode *CreateDouList(void){
    LinkNode *pTmpNode = NULL;
    pTmpNode = malloc(sizeof(LinkNode));
    if(NULL == pTmpNode){
        return NULL;
    }
    pTmpNode->pPre = pTmpNode->pNext =NULL;
    return pTmpNode;
}
3.头插法
//头插法
int HeadInsertDouList(LinkNode *pHead,DataType TmpData){
    LinkNode *pTmpNode = NULL;
    pTmpNode = malloc(sizeof(LinkNode));
    if(NULL == pTmpNode){
        return -1;
    }
    pTmpNode->Data = TmpData;
    pTmpNode->pNext = pHead->pNext;
    pTmpNode->pPre = pHead;
    pHead->pNext = pTmpNode;
    if(pTmpNode->pNext != NULL){
        pTmpNode->pNext->pPre = pTmpNode;
    }
    return 0;
}
4.遍历打印
int ShowDouList(LinkNode *pHead){
    LinkNode *pTmpNode = NULL;
    pTmpNode = pHead->pNext;
    while (pTmpNode != NULL)
    {
        printf("%d ",pTmpNode->Data);
        pTmpNode = pTmpNode->pNext;   
    }
    printf("\n");
    return 0;
    
}
5.尾插法
int TailInsertDouList(LinkNode *pHead,DataType TmpData){
    LinkNode *pTmpNode = NULL;
    LinkNode *pNewNode = NULL;
    pNewNode = malloc(sizeof(LinkNode));
    if(NULL == pNewNode){
        return 0;
    }
    pTmpNode = pHead;
    while (pTmpNode->pNext != NULL)
    {
        pTmpNode = pTmpNode->pNext;
    }
    pNewNode->Data = TmpData;
    pNewNode->pNext = NULL;
    pNewNode->pPre = pTmpNode;
    pTmpNode->pNext = pNewNode;
    return 0;
}
6.删除
int DeleteDouList(LinkNode *pHead,DataType TmpData){
    LinkNode *pTmpNode = NULL;
    LinkNode *p = NULL;
    int cnt = 0;
    pTmpNode = pHead->pNext;
    while (pTmpNode != NULL)
    {
        if(pTmpNode->Data == TmpData){
            p = pTmpNode->pNext;
            pTmpNode->pPre->pNext = pTmpNode->pNext;
           
            if(pTmpNode->pNext != NULL){
                pTmpNode->pNext->pPre = pTmpNode->pPre;
            }
            free(pTmpNode);
            cnt++;
            pTmpNode = p;
        }else{
            pTmpNode = pTmpNode->pNext;
        }
    }
    return cnt;
}
7.销毁
int DestroyDouList(LinkNode **ppHead)
{
    LinkNode *pFreeNode = NULL;
    LinkNode *pTmpNode = NULL;

    pTmpNode = *ppHead;
    pFreeNode = *ppHead;

    while (pTmpNode != NULL)
    {
        pTmpNode = pTmpNode->pNext;
        free(pFreeNode);
        pFreeNode = pTmpNode;
    }
    *ppHead = NULL;

    return 0;
}

内核链表


    1.一种链表结构能够操作多种类型的数据对象 
    2.节点包含数据变成数据包含节点

可以通过(struct list_head *)这个小结构获取到对应的大结构(包含了这个节点的数据Data)的地址,从而操作数据。
双向循环链表,内核链表_第1张图片

 1.内核链表的结构体
/* 链表节点类型 */
struct list_head {
	struct list_head *next;
	struct list_head *prev;
};
2.初始化头结点:INIT_LIST_HEAD
#define INIT_LIST_HEAD(head) do {			\
		(head)->next = (head)->prev = head;	\
	} while (0)

功能:让头结点的next和prev都指向自己

3.头插法:list_add
list_add (struct list_head *new, struct list_head *head)
{
	new->prev = head;
	new->next = head->next;

	new->prev->next = new;
	new->next->prev = new;
}

功能:将new这个节点添加到头结点之后的位置。

注意:1.始终要保持着head的pre指向最后一个节点,最后一个节点的next指向头节点(双向链表)。

            2.new是(struct list_head *)类型的,也就是小结构,所以这个添加节点添加的是小结构,不是大结构

4.尾插法list_add_tail
/* 尾插法 */
static inline void
list_add_tail (struct list_head *new, struct list_head *head)
{
	new->next = head;
	new->prev = head->prev;

	new->prev->next = new;
	new->next->prev = new;
}

功能:将new这个节点,添加到head这个节点之前的位置

注意事项同头插法

5.删除节点: list_del
list_del (struct list_head *old)
{
	old->prev->next = old->next;
	old->next->prev = old->prev;

	old->next = (void *)0xbabebabe;
	old->prev = (void *)0xcafecafe;
}

功能:从链表中删除节点

6.根据当前成员的地址获得节点首地址 :list_entry
#define list_entry(ptr, type, member)					\
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

存在的意义:无论node在大结构中的哪个位置,都可以找到大结构的首地址,不然只有小结构在大结构的开头,小结构的地址可以充当大结构的地址

例子:type:大结构(data_t),member:小结构(node),pst:当前成员的地址

typedef struct data{
    struct list_head node;
    int num;
}data_t;

双向循环链表,内核链表_第2张图片

(unsigned long)(&((type *)0)->member))):大结构中小结构的偏移量

((type *)((char *)(ptr):把当前成员地址(小结构)转变成大结构

((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 根据当前成员的地址(小)获得节点首地址(大结构)

7.从前向后遍历每个数据:list_for_each_entry

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     &pos->member != (head); 					\
	     pos = list_entry(pos->member.next, typeof(*pos), member))

练习:

使用内核链表存储学生信息并打印

#include "list.h"
#include 

//节点类型
typedef struct student 
{
    char name[32];
    struct list_head node;      //链表节点
    char sex;
    int age;
    int score;
}stu_t;

int main(void)
{
    struct list_head head;
    struct list_head *pTmpNode = NULL;
    stu_t *pstu = NULL;

    //初始化空白节点
    INIT_LIST_HEAD(&head);

    stu_t s1 = {"张三", {NULL, NULL},  'm', 19, 100};
    stu_t s2 = { "李四", {NULL, NULL},'m', 19, 80};
    stu_t s3 = {"王二", {NULL, NULL}, 'f', 19, 80};
    stu_t s4 = {"赵五", {NULL, NULL}, 'f', 16, 90};
    stu_t s5 = {"马六", {NULL, NULL}, 'm', 14, 60};

    list_add_tail(&s1.node, &head);
    list_add_tail(&s2.node, &head);
    list_add_tail(&s3.node, &head);
    list_add_tail(&s4.node, &head);
    list_add_tail(&s5.node, &head);

    list_del(&s3.node);

#if 0
    list_for_each(pTmpNode, &head)
    {
        pstu = list_entry(pTmpNode, stu_t, node);
        printf("姓名:%s\n", pstu->name);
        printf("性别:%c\n", pstu->sex);
        printf("年龄:%d\n", pstu->age);
        printf("成绩:%d\n", pstu->score);
    }
#endif
    list_for_each_entry(pstu, &head, node)
    {
        printf("姓名:%s\n", pstu->name);
        printf("性别:%c\n", pstu->sex);
        printf("年龄:%d\n", pstu->age);
        printf("成绩:%d\n", pstu->score);
    }

    return 0;
}   

你可能感兴趣的:(linux,c语言,数据结构)