概述
在操作系统内核中,绝大多数数据结构都是通过链表来实现的,相对于数组,链表可以很方便的用来管理数据,给数据管理带来了更多的可能性。
之前的文章《也没想象中那么神秘的数据结构-一种通用化双向链表设计(底层源码)》中已经对概念和注意等阐述过,这里就不再重复了,有兴趣的小伙伴,可点击上方链接查看具体信息。
注意
1、C语言相比c++、java和python等语言,因为没有类相关的语法支撑,所以一直以来被冠以面向过程语言。
2、实际项目中,特别是大型项目中,我们应该采用面向对象设计而非面向过程设计的方法。
3、面向过程只是一种设计思想,而不应该用语言对其加以局限。
4、C语言也可通过结构体、函数指针等语法实现面向对象的设计,这里后期小编会再出一篇文章对此进行阐述和说明。
介绍
本文旨在将通用链表的操作和属性抽象封装成一个类,类的方法对应通用链表的基本操作,后期使用的时候,只需要将类实例化,即可调用相关属性方法,完成链表的一系列操作。
核心
链表类设计:
属性:链表
行为:包括插入节点、查找节点、删除节点、替换节点等,具体见下方代码。
/* 双向链表类 dll = class doubly linked list */ struct dll { struct t_list *p_list; /* 链表 */ void (*print_node)(struct t_node *node) /* 打印节点信息 */ int (*key_in_node)(struct t_node *node, void *key) /* 判断节点中是否包含指定信息 */ void (*add)(struct dll *p_dll, struct t_node *p_node); /* 添加节点到链表尾部 */ void (*insert_pos)(struct dll *p_dll, int pos, struct t_node *node); /* 在链表指定位置插入节点 */ struct t_node *(*get_first)(struct dll *p_dll); /* 获取链表头结点,不删除 */ struct t_node *(*get)(struct dll *p_dll); /* 获取链表头结点并删除 */ void (*insert)(struct dll *p_dll, struct t_node *p_prev, struct t_node *p_node); /* 在链表指定节点之后插入节点 */ struct t_node *(*get_last)(struct dll *p_dll); /* 获取链表尾结点,不删除 */ struct t_node *(*get_next)(struct t_node *p_node); /* 获取下一个结点,不删除 */ struct t_node *(*get_n_th)(struct dll *p_dll, int pos); /* 获取第N个结点,不删除 */ struct t_node *(*get_prev)(struct t_node *p_node); /* 获取前一个结点,不删除 */ struct t_node *(*rm_n_th)(struct dll *p_dll, int pos); /* 移除链表中第N个结点 */ void (*del_n_th)(struct dll *p_dll, int pos); /* 删除链表中第N个结点 */ void (*replace)(struct dll *p_dll, int pos, struct t_node *node); /* 替换链表指定位置节点 */ int (*get_count)(struct dll *p_dll); /* 链表长度获取 */ void (*rm)(struct dll *p_dll, struct t_node *p_node); /* 移除链表中指定节点 */ void (*del)(struct dll *p_dll, struct t_node *p_node); /* 删除链表中指定节点 */ struct t_node *(*get_n_step)(struct t_node *p_node, int n); /* 获取距离指定节点N步的结点 */ int (*find)(struct dll *p_dll, struct t_node *p_node); /* 查找指定节点在链表中的位置 */ struct t_node * (*find_key)(struct dll *p_dll, void *key); /* 根据指定信息查找节点 */ struct t_node *(*rm_key)(struct dll *p_dll, void *key); /* 移除链表中指定信息的节点 */ void (*del_key)(struct dll *p_dll, void *key); /* 删除链表中指定信息的节点 */ void (*show)(struct dll *p_dll); /* 打印链表信息 */ void (*destory)(struct dll *p_dll); /* 销毁释放链表 */ void (*concat)(struct dll *p_dll, struct t_list *p_add); /* 拼接两个链表 */ void (*extract)(struct dll *p_dll, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst); /* 提取链表中的子链 */ };
在指定位置插入节点:
1. 找到指定位置。
2. 插入节点。
/** * @在链表指定位置插入节点 * @p_dll:链表类 pos:指定位置 p_node:插入节点 **/ static void _insert_pos(struct dll *p_dll, int pos, struct t_node *node) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (p_node == NULL)) return; if ((p_node = lst_n_th(p_dll->p_list, pos)) == NULL) /* 获取第N个结点 */ return; lst_insert(p_dll->p_list, lst_prev(p_node), p_node); }
替换链表中的指定位置节点:
1. 找到指定位置节点。
2. 删除旧节点。
3. 插入新节点。
4. 销毁旧节点。
/** * @替换链表中第n个节点为指定节点 * @p_dll:链表类 pos:指定位置 p_node:插入节点 **/ static void _replace(struct dll *p_dll, int pos, struct t_node *node) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (p_node == NULL)) return; if ((p_node = lst_n_th(p_dll->p_list, pos)) == NULL) /* 获取第N个结点 */ return; lst_del(p_dll->p_list, p_node); lst_insert(p_dll->p_list, lst_prev(p_node), p_node); free(p_node); }
根据指定信息查找节点:
1. 遍历整个链表。
2. 对比各节点信息与指定信息是否一致。
/** * @根据指定信息查找节点 * @p_dll:链表类 key:指定信息 * @找到返回结点,否则返回NULL **/ static struct t_node * _find_key(struct dll *p_dll, void *key) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (key == NULL)) return NULL; if (p_dll->key_in_node == NULL) return NULL; p_node = lst_first(p_dll->p_list); while (p_node != NULL) { if (p_dll->key_in_node(p_node, key) == 0) return p_node; p_node = lst_next(p_node); } return NULL; }
删除指定信息节点:
1. 找到指定信息节点。
2. 删除并销毁节点。
/** * @删除链表中指定信息的节点并释放内存 * @p_dll:链表类 key:指定信息 **/ static void _del_key(struct dll *p_dll, void *key) { struct t_node *p_node = _find_key(p_dll, key); if (p_node == NULL) return; lst_del(p_dll->p_list, p_node); free(p_node); }
输出整个链表信息:
1. 遍历整个链表。
2. 调用输出函数(这里用到了设计模式中的模板模式,感兴趣的读者可查看文章《我用C语言玩对象,框架化的模板模式》),输出所有节点信息。
/** * @打印输出链表信息 * @p_dll:链表类 **/ static void _show(struct dll *p_dll) { struct t_node *p_node = NULL; if (p_dll == NULL) return; if (p_dll->print_node == NULL) return; p_node = lst_first(p_dll->p_list); while (p_node != NULL) { p_dll->print_node(p_node); p_node = lst_next(p_node); } printf("\n"); }
示例
★包含头文件class_dll.h和源文件class_dll.c(均已验证通过)。
class_dll.h
/** * @Filename : class_dll.h * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 通用双向链表类实现 * @Explain : 将双向链表抽象成类,实现二次封装,提高通用性 **/ #ifndef __CLASS_DLL_H__ #define __CLASS_DLL_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h> struct t_list; /* 链表定义 */ struct t_node; /* 节点定义 */ /* 判断节点中是否包含指定信息 */ typedef int (*p_cmp_key_func)(struct t_node *node, void *key); /* 打印节点信息 */ typedef void (*p_print_node_func)(struct t_node *node); /* 双向链表类 dll = class doubly linked list */ struct dll { struct t_list *p_list; /* 链表 */ void (*print_node)(struct t_node *node) /* 打印节点信息 */ int (*key_in_node)(struct t_node *node, void *key) /* 判断节点中是否包含指定信息 */ void (*add)(struct dll *p_dll, struct t_node *p_node); /* 添加节点到链表尾部 */ void (*insert_pos)(struct dll *p_dll, int pos, struct t_node *node); /* 在链表指定位置插入节点 */ struct t_node *(*get_first)(struct dll *p_dll); /* 获取链表头结点,不删除 */ struct t_node *(*get)(struct dll *p_dll); /* 获取链表头结点并删除 */ void (*insert)(struct dll *p_dll, struct t_node *p_prev, struct t_node *p_node); /* 在链表指定节点之后插入节点 */ struct t_node *(*get_last)(struct dll *p_dll); /* 获取链表尾结点,不删除 */ struct t_node *(*get_next)(struct t_node *p_node); /* 获取下一个结点,不删除 */ struct t_node *(*get_n_th)(struct dll *p_dll, int pos); /* 获取第N个结点,不删除 */ struct t_node *(*get_prev)(struct t_node *p_node); /* 获取前一个结点,不删除 */ struct t_node *(*rm_n_th)(struct dll *p_dll, int pos); /* 移除链表中第N个结点 */ void (*del_n_th)(struct dll *p_dll, int pos); /* 删除链表中第N个结点 */ void (*replace)(struct dll *p_dll, int pos, struct t_node *node); /* 替换链表指定位置节点 */ int (*get_count)(struct dll *p_dll); /* 链表长度获取 */ void (*rm)(struct dll *p_dll, struct t_node *p_node); /* 移除链表中指定节点 */ void (*del)(struct dll *p_dll, struct t_node *p_node); /* 删除链表中指定节点 */ struct t_node *(*get_n_step)(struct t_node *p_node, int n); /* 获取距离指定节点N步的结点 */ int (*find)(struct dll *p_dll, struct t_node *p_node); /* 查找指定节点在链表中的位置 */ struct t_node * (*find_key)(struct dll *p_dll, void *key); /* 根据指定信息查找节点 */ struct t_node *(*rm_key)(struct dll *p_dll, void *key); /* 移除链表中指定信息的节点 */ void (*del_key)(struct dll *p_dll, void *key); /* 删除链表中指定信息的节点 */ void (*show)(struct dll *p_dll); /* 打印链表信息 */ void (*destory)(struct dll *p_dll); /* 销毁释放链表 */ void (*concat)(struct dll *p_dll, struct t_list *p_add); /* 拼接两个链表 */ void (*extract)(struct dll *p_dll, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst); /* 提取链表中的子链 */ }; /** * @创建DLL类 * @_cmp_key:信息比较函数 _print_node:打印节点信息函数 * @成功返回类地址,失败返回NULL **/ struct dll *new_dll(p_cmp_key_func _cmp_key, p_print_node_func _print_node); #ifdef __cplusplus } #endif #endif /* __INClstLibh */
class_dll.c
/** * @Filename : class_dll.c * @Revision : $Revision: 1.0 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 通用双向链表类实现 * @Explain : 将双向链表抽象成类,实现二次封装,提高通用性 **/ #include "dll.h" #include "class_dll.h" /** * @添加节点到链表尾部 * @p_dll:链表类 p_node:插入节点 **/ static void _add(struct dll *p_dll, struct t_node *p_node) { if ((p_dll == NULL) || (p_node == NULL)) return; lst_add(p_dll->p_list, p_node); } /** * @在链表指定位置插入节点 * @p_dll:链表类 pos:指定位置 p_node:插入节点 **/ static void _insert_pos(struct dll *p_dll, int pos, struct t_node *node) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (p_node == NULL)) return; if ((p_node = lst_n_th(p_dll->p_list, pos)) == NULL) /* 获取第N个结点 */ return; lst_insert(p_dll->p_list, lst_prev(p_node), p_node); } /** * @获取链表头结点,不删除 * @p_dll:链表类 * @返回头结点 **/ struct t_node *_get_first(struct dll *p_dll) { return (lst_first(p_dll->p_list)); } /** * @获取链表头结点并删除 * @p_dll:链表类 * @返回头结点 **/ static struct t_node *_get(struct dll *p_dll) { return (lst_get(p_dll->p_list)); } /** * @在链表指定节点之后插入节点 * @p_dll:链表类 p_prev:指定节点 p_node:插入节点 **/ static void _insert(struct dll *p_dll, struct t_node *p_prev, struct t_node *p_node) { lst_insert(p_dll->p_list, p_prev, p_node); } /** * @获取链表尾结点,不删除 * @p_dll:链表类 * @返回尾结点 **/ static struct t_node *_get_last(struct dll *p_dll) { return (lst_last(p_dll->p_list)); } /** * @获取下一个结点,不删除 * @p_node:节点 * @返回下一个结点 **/ static struct t_node *_get_next(struct t_node *p_node) { return (lst_next(p_node)); } /** * @获取第N个结点,不删除 * @p_dll:链表类 p_node:节点 * @返回结点 **/ static struct t_node *_get_n_th(struct dll *p_dll, int pos) { return (lst_n_th(p_dll->p_list, pos)); } /** * @获取前一个结点,不删除 * @p_node:节点 * @返回前一个结点 **/ static struct t_node *_get_prev(struct t_node *p_node) { return (lst_prev(p_node)); } /** * @移除链表中第N个结点不释放内存 * @p_dll:链表类 pos:节点偏移量 * @成功返回结点,否则返回NULL **/ static struct t_node * _rm_n_th(struct dll *p_dll, int pos) { struct t_node *p_node = lst_n_th(p_dll->p_list, pos); lst_del(p_dll->p_list, p_node); return(p_node); } /** * @删除链表中第N个结点释放内存 * @p_dll:链表类 pos:节点偏移量 **/ static void _del_n_th(struct dll *p_dll, int pos) { struct t_node *p_node = lst_n_th(p_dll->p_list, pos); lst_del(p_dll->p_list, p_node); free(p_node); } /** * @替换链表中第n个节点为指定节点 * @p_dll:链表类 pos:指定位置 p_node:插入节点 **/ static void _replace(struct dll *p_dll, int pos, struct t_node *node) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (p_node == NULL)) return; if ((p_node = lst_n_th(p_dll->p_list, pos)) == NULL) /* 获取第N个结点 */ return; lst_del(p_dll->p_list, p_node); lst_insert(p_dll->p_list, lst_prev(p_node), p_node); free(p_node); } /** * @获取链表长度 * @p_dll:链表类 * @返回链表长度 **/ static int _get_count(struct dll *p_dll) { return (lst_count(p_dll->p_list)); } /** * @移除链表中指定节点,未释放内存 * @p_dll:链表类 p_node:指定节点 **/ static void _rm(struct dll *p_dll, struct t_node *p_node) { lst_del(p_dll->p_list, p_node); } /** * @删除链表中指定节点并释放内存 * @p_dll:链表类 p_node:指定节点 **/ static void _del(struct dll *p_dll, struct t_node *p_node) { lst_del(p_dll->p_list, p_node); free(p_node); } /** * @获取距离指定节点N步的结点,不删除 * @n:距离,>0在后面,<0在前面 p_node:指定节点 * @返回结点 **/ static struct t_node *_get_n_step(struct t_node *p_node, int n) { return (lst_n_step(p_node, n)); } /** * @查找指定节点在链表中的位置 * @p_dll:链表类 p_node:指定节点 * @返回位置(从1开始,不存在返回-1) **/ static int _find(struct dll *p_dll, struct t_node *p_node) { return (lst_find(p_dll->p_list, p_node)); } /** * @根据指定信息查找节点 * @p_dll:链表类 key:指定信息 * @找到返回结点,否则返回NULL **/ static struct t_node * _find_key(struct dll *p_dll, void *key) { struct t_node *p_node = NULL; if ((p_dll == NULL) || (key == NULL)) return NULL; if (p_dll->key_in_node == NULL) return NULL; p_node = lst_first(p_dll->p_list); while (p_node != NULL) { if (p_dll->key_in_node(p_node, key) == 0) return p_node; p_node = lst_next(p_node); } return NULL; } /** * @移除链表中指定信息的节点,未释放内存 * @p_dll:链表类 key:指定信息 * @成功返回包含信息结点,否则返回NULL **/ static struct t_node * _rm_key(struct dll *p_dll, void *key) { struct t_node *p_node = _find_key(p_dll, key); if (p_node == NULL) return NULL; lst_del(p_dll->p_list, p_node); return (p_node); } /** * @删除链表中指定信息的节点并释放内存 * @p_dll:链表类 key:指定信息 **/ static void _del_key(struct dll *p_dll, void *key) { struct t_node *p_node = _find_key(p_dll, key); if (p_node == NULL) return; lst_del(p_dll->p_list, p_node); free(p_node); } /** * @打印输出链表信息 * @p_dll:链表类 **/ static void _show(struct dll *p_dll) { struct t_node *p_node = NULL; if (p_dll == NULL) return; if (p_dll->print_node == NULL) return; p_node = lst_first(p_dll->p_list); while (p_node != NULL) { p_dll->print_node(p_node); p_node = lst_next(p_node); } printf("\n"); } /** * @销毁释放链表 * @p_dll:链表类 **/ static void _destory(struct dll *p_dll) { lst_free(p_dll->p_list); } /** * @拼接两个链表 * @p_dll:链表类 p_add:添加链表 **/ static void _concat(struct dll *p_dll, struct t_list *p_add) { lst_concat(p_dll->p_list, p_add); } /** * @提取链表中的子链 * @p_dll:链表类 p_start:起始节点 p_end:终止节点 p_dst:子链 **/ static void _extract(struct dll *p_dll, struct t_node *p_start, struct t_node *p_end, struct t_list *p_dst) { lst_extract(p_dll->p_list, p_start, p_end, p_dst); } /** * @创建DLL类 * @_cmp_key:信息比较函数 _print_node:打印节点信息函数 * @成功返回类地址,失败返回NULL **/ struct dll *new_dll(p_cmp_key_func _cmp_key, p_print_node_func _print_node) { struct t_list *p_list = NULL; struct dll *p_dll = (struct dll *)malloc(sizeof(struct dll)); if (p_dll == NULL) return NULL; if ((p_list = (struct t_list *)malloc(sizeof(struct t_list))) == NULL) { free(p_dll); return NULL; } memset(p_dll, 0, sizeof(struct dll)); lst_init(p_list); p_dll->p_list = p_list; p_dll->key_in_node = _cmp_key; p_dll->print_node = _print_node; p_dll->add = _add; p_dll->insert_pos = _insert_pos; p_dll->get_first = _get_first; p_dll->get = _get; p_dll->insert = _insert; p_dll->get_last = _get_last; p_dll->get_next = _get_next; p_dll->get_n_th = _get_n_th; p_dll->get_prev = _get_prev; p_dll->del_n_th = _del_n_th; p_dll->replace = _replace; p_dll->get_count = _get_count; p_dll->rm = _rm; p_dll->del = _del; p_dll->get_n_step = _get_n_step; p_dll->find = _find; p_dll->find_key = _find_key; p_dll->rm_key = _rm_key; p_dll->del_key = _del_key; p_dll->show = _show; p_dll->destory = _destory; p_dll->concat = _concat; p_dll->extract = _extract; return p_dll; }
结论
示例代码将文章《也没想象中那么神秘的数据结构-一种通用化双向链表设计(底层源码)》中源码进行二次封装,抽象出属性和通用性方法设计通用类,后期读者在使用的时候,只需要对其进行实例化,调用其中的方法即可。同时,公众号内文章《也没想象中那么神秘的数据结构-一种通用化双向链表设计(测试例程)》也会向读者展示具体使用方法。
往期 · 推荐
也没想象中那么神秘的数据结构-一种通用化的双向链表设计(底层源码)
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:数据结构源码,也可点击此处下载。