C基础 之 list 库奥义
前言 - 关于 list 思考
list 是最基础的数据结构也是数据结构的基础. 高级 C 代码纽带也是 list.
struct list {
struct list * next;
...
}
链表结构和业务数据绑定在一起. 朴实无华丽, 重剑可破军
struct list {
struct list * next;
void * node;
}
所有业务结点抽象为 void * 万能指针. 瑕疵是存在 sizeof (void *) 内存浪费.
struct $list {
struct $list * next;
};
#define $LIST_HEAD struct $list $node
$LIST_HEAD 宏放在需要实现的链式结构的头位置. 有点继承味道, 例如下面这样
struct list {
$LIST_HEAD;
...
}
住用利用隐含条件 &list = &(list.$node) => list->next = &(list.$node)->next.
struct $list {
struct $list * next;
};
#define $LIST struct $list $node;
typedef struct {
struct $list * root; // 存储链表的头节点
icmp_f fadd; // 链表中插入数据执行的方法
icmp_f fget; // 链表中查找数据执行的方法
node_f fdie; // 链表中删除数据执行的方法
} * list_t;
//
// list_next - 获取结点n的下一个结点.
// n : 当前结点
//
#define list_next(n) ((void *)((struct $list *)(n))->next)
//
// list_create - 构建 list 对象
// fadd : 插入数据方法
// fget : 获取数据方法
// return : 创建好的链表对象
//
#define list_create(fadd, fget) \
list_create_((icmp_f)fadd, (icmp_f)fget)
inline list_t list_create_(icmp_f fadd, icmp_f fget) {
list_t list = malloc(sizeof *list);
list->root = NULL;
list->fadd = fadd;
list->fget = fget;
list->fdie = NULL;
return list;
}
注册行为定义如下
//
// icmp_f - 比较行为的类型
// : int add_cmp(const void * now, const void * node)
//
typedef int (* icmp_f)(); //
// node_f - 销毁当前对象节点
// : void list_die(void * node);
//
typedef void (* node_f)(void * node);
当时产生这个想法是太迷恋基于函数注册的方式. 希望一次注册终身受用.
struct list {
struct list * next;
...
}
or
//
// list.h 通用的单链表库
// void * list = NULL;
//
struct $list {
struct $list * next;
};
#define $LIST struct $list $node;
简单业务上使用第一个原生链表, 在特定场合(顺序有要求)使用内核链表.
正文 - 接口设计
list 首先从总体接口设计感受此中气息
//
// list.h 通用的单链表库
// void * list = NULL;
//
struct $list {
struct $list * next;
}; #define $LIST struct $list $node; //
// list_next - 获取结点n的下一个结点.
// n : 当前结点
//
#define list_next(n) ((void *)((struct $list *)(n))->next) //
// list_delete - 链表数据销毁操作
// list : 基础的链表结构
// pist : 指向基础的链表结构
// fdie : 链表中删除数据执行的方法
// return : void
//
#define list_delete(list, fdie) \
list_delete_((void **)&(list), (node_f)(fdie))
extern void list_delete_(void ** pist, node_f fdie); //
// list_get - 匹配得到链表中指定值
// list : 基础的链表结构
// fget : 链表中查找数据执行的方法
// left : 待查找的结点内容
// return : 查找到的节点, NULL 表示没有查到
//
#define list_get(list, fget, left) \
list_get_((list), (icmp_f)(fget), (const void *)(intptr_t)(left))
extern void * list_get_(void * list, icmp_f fget, const void * left); //
// list_pop - 匹配弹出链表中指定值
// list : 基础的链表结构
// pist : 指向基础的链表结构
// fget : 链表中查找数据执行的方法
// left : 待查找的结点内容
// return : 查找到的节点, NULL 表示没有查到
//
#define list_pop(list, fget, left) \
list_pop_((void **)&(list), (icmp_f)(fget), (const void *)(intptr_t)(left))
extern void * list_pop_(void ** pist, icmp_f fget, const void * left); //
// list_add - 链表中添加数据, 从小到大 fadd(left, ) <= 0
// list : 基础的链表结构
// pist : 指向基础的链表结构
// fadd : 插入数据方法
// left : 待插入的链表结点
// return : void
//
#define list_add(list, fadd, left) \
list_add_((void **)&(list), (icmp_f)(fadd), (void *)(intptr_t)(left))
extern void list_add_(void ** pist, icmp_f fadd, void * left);
大量用到一个宏技巧
// list : 基础的链表结构
// pist : 指向基础的链表结构
#define list_delete(list, fdie) \
list_delete_((void **)&(list), (node_f)(fdie))
通过宏将一维指针转成二维指针来使用. 缺点是指针不可复制. 或者复制后不能再使用上一个指针.
(等同于破坏型智能指针)优势在于潇洒, 宁可 BUG 不断, 也要帅气到底 ~
接口实现部分
开头从 delete 讲起. C 可以没有 create(alloc) , 但一定要有 delete(free). 来不及销毁证据那就不用出去嗨了.
//
// list_delete - 链表数据销毁操作
// pist : 指向基础的链表结构
// fdie : 链表中删除数据执行的方法
// return : void
//
void
list_delete_(void ** pist, node_f fdie) {
if (pist && fdie) {
// 详细处理链表数据变化
struct $list * head = *pist;
while (head) {
struct $list * next = head->next;
fdie(head);
head = next;
}
*pist = NULL;
}
}
核心招式在于 *pist = NULL; 希望置空. (虽然没有卵用, 因为指针可复制, 存在多个引用)
如果场景不允许复制的话, 可以一用.
对于后面几个函数核心设计围绕头结点处理上, 如果处理的对象是头结点, 需要重新设置.
//
// list_get - 匹配得到链表中指定值
// list : 基础的链表结构
// fget : 链表中查找数据执行的方法
// left : 待查找的结点内容
// return : 查找到的节点, NULL 表示没有查到
//
void *
list_get_(void * list, icmp_f fget, const void * left) {
if (fget) {
struct $list * head = list;
while (head) {
if (fget(left, head) == )
return head;
head = head->next;
}
}
return NULL;
} //
// list_pop - 匹配弹出链表中指定值
// pist : 指向基础的链表结构
// fget : 链表中查找数据执行的方法
// left : 待查找的结点内容
// return : 查找到的节点, NULL 表示没有查到
//
void *
list_pop_(void ** pist, icmp_f fget, const void * left) {
struct $list * head, * next;
if (!pist || fget)
return NULL; // 看是否是头节点
head = *pist;
if (fget(left, head) == ) {
*pist = head->next;
return head;
} // 不是头节点挨个处理
while (!!(next = head->next)) {
if (fget(left, next) == ) {
head->next = next->next;
return next;
}
head = next;
} return NULL;
} //
// list_next - 获取结点n的下一个结点.
// n : 当前结点
//
#undef list_next
#define list_next(n) ((struct $list *)(n))->next //
// list_add - 链表中添加数据, 从小到大 fadd(left, ) <= 0
// pist : 指向基础的链表结构
// fadd : 插入数据方法
// left : 待插入的链表结点
// return : void
//
void
list_add_(void ** pist, icmp_f fadd, void * left) {
struct $list * head;
if (!pist || !fadd || !left)
return; // 看是否是头结点
head = *pist;
if (!head || fadd(left, head) <= ) {
list_next(left) = head;
*pist = left;
return;
} // 不是头节点, 挨个比对
while (head->next) {
if (fadd(left, head->next) <= )
break;
head = head->next;
} // 添加最终的连接关系
list_next(left) = head->next;
head->next = left;
}
很多代码强烈推荐自己多打几遍. 这是实践派绝招, 可以啥都不懂, 但会写(有思考更好)应该也是及格吧.
其中 list_next 宏设计思路也很洒脱. 对外暴露是读操作, 对内是写操作.
这里不妨赠送个测试接口
//
// node_f - 销毁当前对象节点
// : void list_die(void * node);
//
typedef void (* node_f)(void * node); //
// list_each - 链表循环处理函数, 仅仅测试而已
// list : 基础的链表结构
// feach : 处理每个结点行为函数
// return : void
//
#define list_each(list, feach) \
list_each_((list), (node_f)(feach))
extern void list_each_(void * list, node_f feach); void
list_each_(void * list, node_f feach) {
if (list && feach) {
struct $list * head = list;
while (head) {
struct $list * next = head->next;
feach(head);
head = next;
}
}
}
list 使用 demo 可以参照这下面的写法
#define INT_NAME (64)
struct peoples {
$LIST
double age;
char name[INT_NAME + ];
};
// peoples_add : 默认年龄从小到大排序, 并且获取
inline static int peoples_add(struct peoples * left, struct peoples * node) {
return (int)(left->age - node->age);
}
// peoples_each : 单纯的打印接口信息
inline static void peoples_each(struct peoples * node) {
printf("age = %9.6lf, name = %s\n", node->age, node->name);
}
//
// list test demo
//
void list_test(void) {
void * peops = NULL;
// 这里添加数据
struct peoples peop[];
for (int i = ; i < LEN(peop); ++i) {
peop[i].age = rand() % + rand() * 1.0 / rand();
snprintf(peop[i].name, LEN(peop[i].name), "peop_%d", i);
list_add(peops, peoples_add, peop + i);
}
// 这里打印数据
list_each(peops, peoples_each);
}
到这关于 list 了解的一切都传入糖果中 : ) 更好例子, 基于 list 设计了重复定时器例子
timer - https://github.com/wangzhione/structc/blob/master/structc/base/timer.c
(扯一点, 定时器有很多实现思路. 采用 list, heap, double list, array + list 都有, 看应用领域.) 能够写好 list,
算数据结构结业了吧. 想起朴实的大学数学老师说, 走出学校的时候还记得数学分析, 那数学系就算学合格了.
现在想起来有些心痛, 真实在. 对于大家都懂的需要多练习, 对于都不明白的需要多调研.
顺势而为, 耕田日下.
后记 - 有序展望
错误和成长是难免的, 欢迎指正 ~ :-
小雨中的回忆 - https://music.163.com/#/song?id=119664

:- >
C基础 之 list 库奥义的更多相关文章
- mysql基础之对库表操作
原文:mysql基础之对库表操作 查看一下所有的库,怎么办? Mysql>Show databases; 选库语句: Use 库名 创建一个数据库: create database 数据库名 [ ...
- js基础和工具库
/* * 作者: 胡乐 * 2015/4/18 * js 基础 和 工具库 * * * */ //根据获取对象 function hGetId(id){ return document.getElem ...
- python 3.x 爬虫基础---常用第三方库(requests,BeautifulSoup4,selenium,lxml )
python 3.x 爬虫基础 python 3.x 爬虫基础---http headers详解 python 3.x 爬虫基础---Urllib详解 python 3.x 爬虫基础---常用第三方库 ...
- Python基础面试题库
Python基础面试题库 Python是一门学习曲线较为容易的编程语言,随着人工智能时代的到来,Python迎来了新一轮的高潮.目前,国内知乎.网易(游戏).腾讯(某些网站).搜狐(邮箱).金山. ...
- python基础和编程库
Python编程从入门到实践-------基础入门 1.Python中的变量 2.Python首字母大写使用title()方法,全部大写upper()方法,全部小写lower()方法 3.Python ...
- Robot Framework - 基础关键字 BuiltIn 库(一)
今天给大家分享的是Robot Framework 机器人框架中 BuiltIn 基础库的使用...BuiltIn 库里面提供了很多基础方法助力于我们在自动化测试领域中做的更好!——本系列教程是教会大家 ...
- MySQL数据分析-(8)SQL基础操作之库操作
前面我们讲了学习SQL的两个逻辑框架,jacky说了这样一个逻辑:库是为了存储表的,所以一定是先有库才有表:同样的道理,有表才有表中的数据,是吧,肯定是这个逻辑:那么,今天jacky就捋着这个逻辑从库 ...
- 【Python爬虫】HTTP基础和urllib库、requests库的使用
引言: 一个网络爬虫的编写主要可以分为三个部分: 1.获取网页 2.提取信息 3.分析信息 本文主要介绍第一部分,如何用Python内置的库urllib和第三方库requests库来完成网页的获取.阅 ...
- Python 基础教程 —— Pandas 库常用方法实例说明
目录 1. 常用方法 pandas.Series 2. pandas.DataFrame ([data],[index]) 根据行建立数据 3. pandas.DataFrame ({dic}) ...
随机推荐
- iOS设计模式 - 桥接
iOS设计模式 - 桥接 示意图 说明 1. 桥接模式为把抽象层次结构从实现中分离出来,使其可以独立变更,抽象层定义了供客户端使用的上层抽象接口,实现层次结构定义了供抽象层次使用的底层接口,实现类的引 ...
- GPL & Apache License
Copyleft[编辑] GPL不会授予许可证接受人无限的权利.再发行权的授予需要许可证接受人开放软件的源代码,及所有修改.且复制件.修改版本,都必须以GPL为许可证. 这些要求就是copyleft, ...
- java中形参的可变参数的定义(如String... args) .
如果有下面的一个笔试题: 已知我们有如下的调用关系 logIt(”log message 1 “); logIt(”log message2”, " log message3”); logI ...
- 两天学会css基础(一)
什么是css?css的作用是什么? CSS 指层叠样式表 (Cascading Style Sheets)主要作用就是给HTML结构添加样式,搭建页面结构,比如设置元素的宽高大小,颜色,位置等等. 学 ...
- Who are you, What is the science
Please read: 地球月球有多大? 我们乃至我们赖以生存的地球, 甚至是我们硕大的银河系放到茫茫大宇中真的不过是一粒尘埃, 我们司空见惯的事物,我们习以为常的生活,我们笃定信奉的科学, 是不 ...
- H3C笔试题目
最近这段时间正在找工作,去H3C做了下笔试题,题目出的还不错,比一般公司水平高上一点点,偷偷弄了点回来,分享一下,我把答案和解析全部整理了出来,有需要的可以看看. 1.以下描述正确的有(AD) A.1 ...
- Tomcat是如何将请求一步步传递到我们编写的HttpServlet类中的
我们平常编写好的HttpServlet类后,就可以处理请求了,但是服务器在接收到请求信息以后是如何将这些请求传递到我们编写的Servlet类中的???这个疑问在我心中的已经很久了,现在要来解决它. 我 ...
- mysql 聚集和非聚集索引 解析
一.聚集索引(聚簇索引) 1. 什么是聚集索引? 比如要查找'hello',则直接找内容为hello的行,我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”. 聚集索引的叶子节点 ...
- PHP imagechar() 图形验证码 字体太小问题
bool imagechar ( resource $image , int $font , int $x , int $y , string $c , int$color ) imagechar() ...
- 2018-2019-2 网络对抗技术 20165322 Exp4 恶意代码分析
2018-2019-2 网络对抗技术 20165322 Exp4 恶意代码分析 目录 实验内容与步骤 系统运行监控 恶意软件分析 实验过程中遇到的问题 基础问题回答 实验总结与体会 实验内容与步骤 系 ...