数据结构:内核list的实践

记录下C语言中数据结构链表的2种方式,其中内核的表达方式可谓是经典。

一、数据和指针域混合

数据域和指针域定义在同一个结构体定义中,C语言中没有模板,意味着一个代码工程有多少数据结构就需要多少对链表的.c和.h,事实上这样会导致很多的冗余代码。

头文件 list.h
#include<stdio.h>

typedef struct node
{
    DATATYPE data;    //数据域
    
    struct node *next;    //指针域
}node;

typedef struct list
{
    node *first;    //指向头节点
    node *last;     //指向尾节点(该成员可以要可以不要)
    
    int size;
};

//初始化单链表
void init_list(list *list);
//尾部新增数据node
void push_back(list *list, DATATYPE buf);
//头部新增数据node
void push_front(list *list, DATATYPE buf);
//打印链表数据结构
void show_list(list *list);
//尾部删除数据node
void pop_back(list *list, DATATYPE buf);
//头部删除数据node
void pop_front(list *list, DATATYPE buf);
//获取链表数据深度
int length(list *list);
//清空链表数据结构
void clear(list *list);
//销毁链表
void destroy(list *list);
接口文件 list.c
#include "list.h"
#include <stdio.h>

//初始化链表,head结点不计入size,且数据域无值
void init_list(list *list)
{
    list->first = list->last = (node *)malloc(sizeof(node));
    assert(NULL != list->first);
    
    //为head节点赋NULL初始值
    list->first->next = NULL;
    list->size = 0;
}

//尾插,存入新结点数据
void push_back(list *list, DATATYPE buf)
{
    //创建尾插节点
    node *s = (node *)malloc(sizeof(node));
    assert(NULL != s);

    s->data = buf;    //根据数据结构DATATYPE做相应处理
    s->next = NULL;

    //将尾插节点连接到链表尾部
    list->last-next = s;
    //更改管理节点中last的指向;
    list->last = s;
    //更改有效节点的个数
    list->size++;
}

//头插,存入新节点数据
void push_front(list *list, DATATYPE buf)
{
    node *s = (node *)malloc(sizeof(node));
    assert(NULL != s);

    s-data = buf;
    s-next = list->first->next;
    list->first->next = s;

    //如果是链表的第一个有效节点,需要改变尾指针的指向
    if(list->size == 0)
    {
        list->last = s;
    }
    
    //更改有效节点的个数
    list->size++;
}

//尾删,取出结点数据
void pop_back(list *list, DATATYPE* buf)
{
    //链表中是否还有结点
    if(list->size == 0)
        return ;
    //寻找倒数第二个结点
    node *p = list->first;
    while(p->next != list->last)
        p = p->next;
    
    //取出尾部节点数据
    buf = p->next->data;

    //删除尾部结点
    free(p->next);
    //更改尾指针的指向
    list->last = p;
    //更改现尾结点的指针域
    list->last->next = NULL;
    
    list->size --;
}

//头删,取出结点数据
void pop_front(list *list, DATATYPE* buf)
{
    //链表中是否有元素?
    if(list->size == 0)
        return;
    //临时保存要删除结点的地址
    Node *p = list->first->next;
    //删除该结点与链表的连接
    list->first->next = p->next;

    //取出头节点数据
    buf = p;

    //删除结点
    free(p);
    //该链表是否只有一个有效结点
    if(list->size == 1)
    {
        //更改尾指针的指向
        list->last = list->first;
    }
    //更改有效结点个数
    list->size--;
}

二、数据和指针域分离

数据域与指针域分离是Linux内核的写法,非常有特点,抽象了链表中共性的指针操作,让使用者只需要关注自己的数据域。

头文件list_common.h

只构造小结构体 list 相关的宏或则接口,该部分是共用的只需要一份;

//2024-04-01 实践记录
#include<stdio.h>
#include<stdlib.h>

//遍历链表,typeof内建函数在vc下无法编译,不确定什么原因
//返回:结构体成员在内存中的偏移量
#define offsetof(type, memb) (unsigned long)(&((type *)0)->memb)
//返回:通过结构体变量的一个成员的地址找到这个结构体变量的首地址;ptr,type,member分别代表指针、类型、成员
#define container_of(ptr, type, member) ({          \
	const typeof(((type *)0)->member)*__mptr = (ptr);    \
		     (type *)((char *)__mptr - offsetof(type, member)); })

//以pos遍历以head为头的链表
#define list_entry(ptr, type, member)    \
    ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_for_each_entry(pos, head, member)				\
    for (pos = container_of((head)->next, typeof(*pos), member);		\
	 &pos->member != (head);					\
	 pos = container_of(pos->member.next, typeof(*pos), member))

//定义一个双向链表
struct list_head {
	struct list_head *next, *prev;
};
//初始化链表
static inline void
INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list->prev = list;
}

//链表操作
static inline void
__list_add(struct list_head *entry,
                struct list_head *prev, struct list_head *next)
{
    next->prev = entry;
    entry->next = next;
    entry->prev = prev;
    prev->next = entry;
}

//在head之后插入节点
static inline void
list_add(struct list_head *entry, struct list_head *head)
{
    __list_add(entry, head, head->next);
}

//在尾部添加节点
static inline void
list_add_tail(struct list_head *entry, struct list_head *head)
{
    __list_add(entry, head->prev, head);
}

//删除链表结点
static inline void __list_del(struct list_head* prev, struct list_head* next)
{
	next->prev = prev;
	prev->next = next;
}

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = (void *) 0;
	entry->prev = (void *) 0;
}
头文件xxx_list.h

构造大结构体的定义,与具体的数据结构相关,需要定义多份;

#include<stdio.h>
#include<stdlib.h>
#include"list_common.h"

#define DATATYPE int

typedef struct node{
    int data;
	struct list_head list;
}node, *pnode;

pnode init_list(void);
int push_front(pnode head, DATATYPE buf);
void list_front(pnode node);
int pop_front(pnode head, DATATYPE buf);
int show(pnode head);
接口文件xxx_list.c

构造大结构体的接口,与具体的数据结构相关,需要定义多份;

//2024-04-02 实践记录
#include<stdio.h>
#include<stdlib.h>
#include"list_common.h"

//借助内核链表宏定义完成双向循环链表空表初始化
pnode init_list(void)
{
    pnode head = (pnode)malloc(sizeof(node));
    if(NULL == head)
    {
        perror("malloc failed.\n");
        return NULL;
    }
    
    INIT_LIST_HEAD(&head->list);
    return head;
}

int push_front(pnode head, DATATYPE buf)
{
    pnode newnode = (pnode)malloc(sizeof(node));
    if(NULL == newnode)
    {
        perror("malloc failed.\n");
        return -1;
    }
    
    newnode->data = buf;//根据实际数据结构操作 
    
    list_add(&(newnode->list), &(head->list));
    return 0;
}

//删除结点
void list_front(pnode node)
{
    list_del(&(node->list));
    free(node);
}

int pop_front(pnode head, DATATYPE buf)
{
    node p;
    struct list_head *pos = (&(head->list))->next;
    p = list_entry(pos, node, list);
    
    buf = p->data;    //根据实际数据结构赋值
    list_front(p);
}

//结点打印
int show(struct node *head)
{
    node *pos;
    list_for_each_entry(pos, &(head->list), list)
    {
        printf("data is:", pos->data);//与大结构的具体定义相关        
    }

    return 0;
}

测试代码:

//2024-04-02 实践记录

#include<stdio.h>
#include<stdlib.h>
#include"node_list.h"

int main()
{
    int i, n;
    int num;
    
    pnode head = init_list();
    
    printf("please input num you want create node: \n");
    scanf("%d", &n);

    for(i = 1; i <= n; i ++)
    {
        push_front(head, i);
    }

    show(head);

    pop_front(p, &num);
    printf("get node.data: %d.\n", num);
    show(head);

    return 0;
}
相关推荐
Swift社区42 分钟前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht43 分钟前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht1 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20241 小时前
Swift 数组
开发语言
南东山人1 小时前
一文说清:C和C++混合编程
c语言·c++
stm 学习ing2 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc3 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe3 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin3 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python