数据结构:内核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;
}
相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜8 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa8 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio