C-数据结构-双向循环链表

常用的双向循环链表,不易出错
/*
通用性比较强的双向环链

为了函数的通用性,我们在.h .c 需要用户去定夺的关键字,内容全部做成通用的接口
利用typedef抽象出了一些接口比较统一的实现方法
*/

llist.h

#ifndef LLIST_H__
#define LLSIT_H__
#define LLIST_FORWARD	1
#definr LLIST_BACKWARD	2
typedef void llist_op(const void *);//回调函数
typedef int llist_cmp(const void *,const void *);

struct llist_node_st
{
	void *data;
	struct llist_node_st *prev;
	struct llist_node_st *next;	

};
typedef struct
{
	int size;
	struct llist_node_st head;
}LLSIT;



LLIST *llist_create(int initsize);
int llist_insert(LLIST *,const void *data,int mode);

void *llist_find(LLIST *, const void *key, llist_cmp *);//数据类型不统一使用void 百搭
int llist_delete(LLIST *,const void *key,llist_cmp *);
int llist_fetch(LLIST *,const void *key,llist_cmp *,void *data);

void llist_travel(LLIST *,llist_op *);
void llist_destroy(LLIST *);

#endif

llist.c

#include<stdio.h>
#include<stdlib.h>
#include"llist.h"
#include<string.h>

LLIST *llist_create(int initsize)//只包含一个头节点( 双向循环链表)
{
	LLIST *new;
	new = malloc(sizeof(*new));
	if(new == NULL)
		return NULL;
	new->size= initsize;
	new->head.data = NULL;
	new->head.prev = new->head;
	new->head.next = new->head;
	return new;
}
int llist_insert(LLIST *ptr,const void *data,int mode)
{
	struct llist_node_st *newnode;
	newnode = malloc(sizeof(*newnode));
	if(newnode == NULL)
		return -1;
	newdata->data = malloc(ptr->size);
	if(newnode->data == NULL)
		return -2;
	memcpy(newnode->data,data,ptr->size);
	if(mode == LLIST_FORWARD)
	{
		newnode->prev = &ptr->head;
		newnode->next = ptr->head.next;
		newnode->prev->next = newnode;//头节点的 next 指针设置为指向新节点 newnode
		newnode->next->prev = newnode;//原本在头节点之后的节点的 prev 指针设置为指向新节点 newnode。
	}
	else if(mode == LLIST_BACKWARD)
		{
			newnode->prev = ptr->head.prev;
			newnode->next = &ptr->head;
			newnode->prev->next = newnode;
			newnode->next->prev = newnode;
		}
		else
		{
			return -3;
		}
	return 0;
}
static struct list_node_st * find_(LLIST *ptr, const void *key, llist_cmp *cmp)
{
	struct llist_node_st *cur;
	for(cur = ptr->head.next;cur!=ptr.head;cur=cur->next)
	{
		if(cmp(key,cur->data) == 0)
			break;
	}
	return cur;
}

void *llist_find(LLIST *ptr, const void *key, llist_cmp *cmp)
{
	return find_(ptr,key,cmp)->data;
}
int llist_delete(LLIST *ptr,const void *key,llist_cmp *cmp)
{	
	struct llist_node_st *node;
	node = nodefind_(ptr,key,cmp);
	if(node == &ptr->head)
		return -1;
	node->prev->next = node->next;
	node->next->prev = node->prev;
	free(node->data);
	free(node);
	return 0;
}
int llist_fetch(LLIST *ptr,const void *key,llist_cmp *cmp,void *data)
{
	struct llist_node_st *node;
	node = nodefind_(ptr,key,cmp);
	if(node == &ptr->head)
		return -1;
	node->prev->next = node->next;
	node->next->prev = node->prev;
	if(data!=NULL)
		memcpy(data,node->data,ptr->size);
	free(node->data);
	free(node);
	return 0;
}
void llist_travel(LLIST *ptr,llist_op *op)//需要一个回调函数,需要用户给我传一个函数
{
	struct llist_node_st *cur;
	for(cur = ptr->head.next;cur!=&ptr->head;cur=cur->next)//为了封装成更通用的函数,不知道用户的结构类型,因此需要回调函数,且需要在 .h文件中使用 void 函数声明,且使用typedef重命名 看起来更好一些
		op(cur->data);//借用户之手,把他知道的数据类型打印了出来  具有通用性
	
}
void llist_destroy(LLIST *ptr)
{
	struct llist node_st *cur,*next;
	for(cur= ptr->head.next;cur != &ptr->head;cur= next)
	{
		next = cur->next;
		free(cur->data);
		free(cur);
	}
	free(ptr);
}

main.c


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

#define NAMESIZE	32

struct score_st
{
	int id;
	char name[NAMESIZE];
	int math;
	int chinese;
};

static void print_s(const void *record)
{
	const struct score_st *r = record;
	printf("%d %s %d %d\n",r->id,r->name,r->math,r->chinese);
}

static int id_cmp(const void *key,const void *record)
{
	const int *k = key;
	const struct score_st *r = record;
	return (*k - r->id);

}
static int name_cmp(const void *key,const void *record)
{
	const char *k = key;
	const struct score_st *r = record;
	return strcmp(k,r->name);
}


int main()
{
	int ret,i;
	int id =3;
	LLIST *handler;
	struct score_st tmp;
	handler = llist_create(sizeof(struct score_st));
	if(handler == NULL)
		exit(1);
	for(i =0;i<7;i++)
	{	
		tm.id =i;
		snprintf(tmp.name,NAMESIZE,"std%d",i);
		tmp.math = rand()%100;
		tmp.chinese = rand%()100;
		
		ret = llist_insert(handler,&tmp,LLIST_FORWARD);
		//ret = llist_insert(handler,&tmp,LLIST_BACKWARD);
		if(ret)
			exit(1);
	}
	
	char *del_name = "std6";
	ret = llist_delete(handler,&id,id_cmp);
	//ret = llist_delete(handler,del_name,name_cmp)//如何实现根据任何字段来删除
	if(ret)
		printf("llist_delete failed!\n");
		
	llist_travel(handler,print_s);
	printf("\n\n");
	struct score *data;
	data = llist_find(handler,&id,id_cmp);
	if(data == NULL)
		printf("Can not find!\n");
	else
		printf_s(data);
	llist_destroy(handler);

	exit(0);
}

Makefile

all:llist
llist:llist.o main.o
	$(CC) $^ -o $@
clean:
	rm llist *.o -rf

补充说明

在C语言中,typedef 关键字用于给现有的数据类型定义一个新的名字。你提供的 typedef void llist_op(const void *) 是一个非常有用的技巧,特别是在定义函数指针类型时。我们来详细解释一下这个定义。

解释 typedef void llist_op(const void *)

  1. 原始定义

    void llist_op(const void *);
    
    • 这行代码声明了一个函数类型 llist_op,它接受一个 const void * 类型的参数,并且没有返回值(void)。
  2. 使用 typedef

    typedef void llist_op(const void *);
    
    • 使用 typedef 后,llist_op 成为了一个新的类型名,可以用来声明这种类型的函数指针。

使用示例

假设你有一个函数,其签名与 llist_op 类型匹配:

void print_data(const void *data) {
    printf("%s\n", (const char *)data);
}

你可以使用 llist_op 类型来声明一个函数指针,并将其指向 print_data 函数:

llist_op *op = print_data;

更深入的例子

在链表操作中,这种用法非常常见。例如,你可能会定义一个链表结构,并使用 llist_op 类型的函数指针来遍历链表并对每个节点进行操作:

typedef struct node {
    void *data;
    struct node *next;
} Node;

typedef struct {
    Node *head;
    llist_op *operation; // 函数指针,用于链表操作
} LinkedList;

void traverse_list(LinkedList *list) {
    Node *current = list->head;
    while (current != NULL) {
        list->operation(current->data); // 使用函数指针执行操作
        current = current->next;
    }
}

在这个例子中,LinkedList 结构包含一个 llist_op 类型的函数指针 operation。当你遍历链表时,可以通过这个指针对每个节点的数据执行特定操作:

int main() {
    LinkedList list;
    list.operation = print_data; // 设置操作函数

    // 假设已经初始化并填充链表
    traverse_list(&list); // 遍历链表并打印每个节点的数据

    return 0;
}

小结

使用 typedef 定义函数指针类型可以使代码更加简洁和可读。通过 typedef void llist_op(const void *);,你可以方便地声明和使用这种函数指针,特别是在需要传递和调用回调函数的场合。这样,你的代码不仅更加易于理解,也更加灵活和模块化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/634299.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工作学习的电脑定时关机,定时重启,定时提醒

可以直接下载工具&#xff1a; 定时自动关机 大家好&#xff0c;&#xff01; 在我们学习与工作时&#xff0c;经常会遇到想要在完成一个任务后&#xff0c;再关闭电脑或对电脑重启&#xff0c;但这个时间点&#xff0c;操作电脑的人可能不能在电脑旁边&#xff0c;这样就需要…

电磁兼容(EMC):去耦电容设计详解

目录 1. 概念 2. 去耦电容工作机理 3. 去耦电容大小选择 4. 去耦电容PCB布局 电容在电路中不同作用有不同的称呼去耦电容、旁路电容、储能电容&#xff0c;而这些作用又可以统称为滤波。本文将详细解读一下三者之间的差别&#xff0c;并着重说明一下去耦电容的设计方法。 …

【Qt 学习笔记】Qt常用控件 | 布局管理器 | 空白项Spacer

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 布局管理器 | 添加空白Spacer 文章编号&#xff1a;Qt 学…

JS 实战 贪吃蛇游戏

一、css 部分 1. 居中 想要开始和暂停两个按钮居中&#xff0c;可以将盒子设置为弹性盒 也可以使用其他方法 【代码】 2. 将父元素设置为相对定位&#xff0c;偏于之后贪吃蛇长长的身子&#xff0c;是以父元素为基点的绝对定位&#xff0c;通过 left 和 top 来控制位置 二、…

【Qt 学习笔记】Qt常用控件 | 布局管理器 | 表单布局Form Layout

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 布局管理器 | 表单布局Form Layout 文章编号&#xff1a…

Android AV World 序

序 做Android系统开发很久了&#xff0c;基于高通和MTK硬件平台&#xff0c;使用Android10量产了一些车载项目。由于功能模块属于系统底层支撑&#xff0c;类似于docker&#xff0c;涉及到音视频的处理&#xff0c;及Display Graphics的一些处理&#xff0c;需要调试解决显示花…

【Linux】:进程切换

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux进程切换的知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精…

车间人员作业行为智能检测 AI视觉在生产车间制造中的应用

车间人员作业行为智能检测系统基于神经网络人工智能视觉算法&#xff0c;车间人员作业行为智能检测通过对车间监控摄像头获取的视频图像进行分析和识别&#xff0c;实现了对人员操作行为的智能检测。系统对工人的操作环节进行分解&#xff0c;根据时间、动作标准等方面制定了规…

FSC认证是什么?森林认证的好处是什么?

FSC认证&#xff08;Forest Stewardship Council&#xff0c;森林管理委员会认证&#xff09;是一种运用市场机制来促进森林可持续经营&#xff0c;实现生态、社会和经济目标的工具。以下是关于FSC认证的详细介绍&#xff1a; 一、FSC认证包括两个方面&#xff1a; 森林经营认…

Day 38 防火墙技术IPtables

一&#xff1a;防火墙简介 1.简介 ​ iptables其实并不是真正的防火墙&#xff0c;我们可以把他理解为一个客户端的代理&#xff0c;用户是通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的“安全框架”中&#xff0c;这个“安全框架”才是真正的防火墙。这个框…

极光将于2024年6月6日公布2024年第一季度财报

2024年5月23日&#xff0c;中国深圳——中国领先的客户互动和营销科技服务商极光&#xff08;Aurora Mobile&#xff0c;纳斯达克股票代码&#xff1a;JG&#xff09;&#xff08;以下称“极光”或“公司”&#xff09;宣布将于2024年6月6日周四美国股市开市前公布截至2024年3月…

Web前端开发技术-格式化文本 Web页面初步设计

目录 Web页面初步设计 标题字标记 基本语法&#xff1a; 语法说明&#xff1a; 添加空格与特殊符号 基本语法&#xff1a; 语法说明: 特殊字符对应的代码: 代码解释&#xff1a; 格式化文本标记 文本修饰标记 计算机输出标记 字体font标记 基本语法&#xff1a; 属…

精准键位提示,键盘盲打轻松入门

在说明精准键位提示之前&#xff0c;我们先来看一张图&#xff1a; 这是一张标准的基准键位图&#xff0c;也就是打字时我们双手的8个手指放在基准键位上&#xff0c;在打不同的字母时&#xff0c;我们的手指以基准键位为中心&#xff0c;或上、或下、或左、或右&#xff0c;在…

2024 中青杯高校数学建模竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024 长三角高校数学建模竞赛&#xff08;A题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有详尽的建模过…

Cadence HDL如何拷贝模版项目?

【记录】防止遗忘~ 首先&#xff0c;由于每次绘制原理图都要重新设置各种背景颜色&#xff0c;库路径等等&#xff0c;超级不方便&#xff0c;所以发现可以通过绘制一次模版项目&#xff0c;往后只用拷贝模版项目就可以了。 Cadence HDL新建项目时拷贝模版项目&#xff0c;再…

Vue2全局封装modal弹框

Vue2全局封装modal弹框使用&#xff1a; 一.components下封装 1.index.js import ModalCheck from ./modal-check.vue export default ModalCheck2.modal-check.vue <template><div><Modalv-model"selSingleShow":title"editTitle(convertCa…

二叉树遍历操作详解

目录 一、思路详解 1.1 递归思路 1.2 递归分支图 1.3 递归栈帧图 二、C语言实现 2.1 前序遍历 2.2 中序遍历 2.3 后序遍历 三、查找值为x的结点 3.1 递归思路 3.2 C语言代码 一、思路详解 采用递归的思想解决问题&#xff0c;以高度为3的满二叉树为例。 1.1 递归思…

FBB-Frontiers in Bioengineering and Biotechnology

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 Frontiers in Bioengineering and Biotechnology是专注生物工程和生物技术领域的开放获取期刊。 研究范围涵盖生物材料、生物力学、生物工艺工程、生物安全和生物安保&#xff0c;生物传…

Power BI实现动态度量值

假设有一张销售数据表Sale: 报表上有一个切片器(Slicer)(下拉框样式)&#xff0c; 当选择"第一"时&#xff0c;计算列[FirstSale]与列[Target]的百分比&#xff0c; 选择"第二"时&#xff0c;计算列[SecondSale]与列[Target]的百分比 选择"第三&qu…