前言 - 关于 list 思考

  list 是最基础的数据结构也是数据结构的基础. 高级 C 代码纽带也是 list.

扯一点, 当你走进了 C 的殿堂, 那么你和 list 增删改查那就是一辈子丫 ~
这里不妨分享一下作者对于 list 认知经历的几个阶段 (比心)
i) 原生链表

  1. struct list {
  2. struct list * next;
  3. ...
  4. }

链表结构和业务数据绑定在一起. 朴实无华丽, 重剑可破军

ii) 万能链表

  1. struct list {
  2. struct list * next;
  3. void * node;
  4. }

所有业务结点抽象为 void * 万能指针. 瑕疵是存在 sizeof (void *) 内存浪费.

像一杯甜酒喝起来还挺爽, 只是热量有点高.
iii) 内核链表

  1. struct $list {
  2. struct $list * next;
  3. };
  4.  
  5. #define $LIST_HEAD struct $list $node

$LIST_HEAD 宏放在需要实现的链式结构的头位置. 有点继承味道, 例如下面这样

  1. struct list {
  2. $LIST_HEAD;
  3. ...
  4. }

住用利用隐含条件 &list = &(list.$node) => list->next = &(list.$node)->next.

链表结点首地址和业务结点首地址值相等. 瞟内核的时候看挺常见.
四两拨千斤 ~
 
iv) 注册链表

  1. struct $list {
  2. struct $list * next;
  3. };
  4.  
  5. #define $LIST struct $list $node;
  6.  
  7. typedef struct {
  8. struct $list * root; // 存储链表的头节点
  9.  
  10. icmp_f fadd; // 链表中插入数据执行的方法
  11. icmp_f fget; // 链表中查找数据执行的方法
  12. node_f fdie; // 链表中删除数据执行的方法
  13. } * list_t;
  14.  
  15. //
  16. // list_next - 获取结点n的下一个结点.
  17. // n : 当前结点
  18. //
  19. #define list_next(n) ((void *)((struct $list *)(n))->next)
  20.  
  21. //
  22. // list_create - 构建 list 对象
  23. // fadd : 插入数据方法
  24. // fget : 获取数据方法
  25. // return : 创建好的链表对象
  26. //
  27. #define list_create(fadd, fget) \
  28. list_create_((icmp_f)fadd, (icmp_f)fget)
  29.  
  30. inline list_t list_create_(icmp_f fadd, icmp_f fget) {
  31. list_t list = malloc(sizeof *list);
  32. list->root = NULL;
  33. list->fadd = fadd;
  34. list->fget = fget;
  35. list->fdie = NULL;
  36. return list;
  37. }

注册行为定义如下

  1. //
  2. // icmp_f - 比较行为的类型
  3. // : int add_cmp(const void * now, const void * node)
  4. //
  5. typedef int (* icmp_f)();
  6.  
  7. //
  8. // node_f - 销毁当前对象节点
  9. // : void list_die(void * node);
  10. //
  11. typedef void (* node_f)(void * node);

当时产生这个想法是太迷恋基于函数注册的方式. 希望一次注册终身受用.

但在实战中发现, C 很多时候只用到局部部分. 功能越强大, 考虑的越全面,
代码写起来就越难受. 等同于衣服太多, 搬家就会很麻烦.
领证还要房子车子票子, 这么麻烦, 那结个 pi 呀. 必须要整改 :)
v) 取舍链表

  1. struct list {
  2. struct list * next;
  3. ...
  4. }
  5.  
  6. or
  7.  
  8. //
  9. // list.h 通用的单链表库
  10. // void * list = NULL;
  11. //
  12. struct $list {
  13. struct $list * next;
  14. };
  15.  
  16. #define $LIST struct $list $node;

简单业务上使用第一个原生链表, 在特定场合(顺序有要求)使用内核链表.

成熟在于取舍, 渣往往是抉择的时候不定, 遇到的时候不克制.
有感情那 OK, 有票子 那 OK, else 自己玩毛 ~
  说了这么多没用的, 希望读者能够理解作者关于链表结构的思考心路.
本文后续重点就是讲解 $LIST ~
 

正文 - 接口设计

   list 首先从总体接口设计感受此中气息

  1. //
  2. // list.h 通用的单链表库
  3. // void * list = NULL;
  4. //
  5. struct $list {
  6. struct $list * next;
  7. };
  8.  
  9. #define $LIST struct $list $node;
  10.  
  11. //
  12. // list_next - 获取结点n的下一个结点.
  13. // n : 当前结点
  14. //
  15. #define list_next(n) ((void *)((struct $list *)(n))->next)
  16.  
  17. //
  18. // list_delete - 链表数据销毁操作
  19. // list : 基础的链表结构
  20. // pist : 指向基础的链表结构
  21. // fdie : 链表中删除数据执行的方法
  22. // return : void
  23. //
  24. #define list_delete(list, fdie) \
  25. list_delete_((void **)&(list), (node_f)(fdie))
  26. extern void list_delete_(void ** pist, node_f fdie);
  27.  
  28. //
  29. // list_get - 匹配得到链表中指定值
  30. // list : 基础的链表结构
  31. // fget : 链表中查找数据执行的方法
  32. // left : 待查找的结点内容
  33. // return : 查找到的节点, NULL 表示没有查到
  34. //
  35. #define list_get(list, fget, left) \
  36. list_get_((list), (icmp_f)(fget), (const void *)(intptr_t)(left))
  37. extern void * list_get_(void * list, icmp_f fget, const void * left);
  38.  
  39. //
  40. // list_pop - 匹配弹出链表中指定值
  41. // list : 基础的链表结构
  42. // pist : 指向基础的链表结构
  43. // fget : 链表中查找数据执行的方法
  44. // left : 待查找的结点内容
  45. // return : 查找到的节点, NULL 表示没有查到
  46. //
  47. #define list_pop(list, fget, left) \
  48. list_pop_((void **)&(list), (icmp_f)(fget), (const void *)(intptr_t)(left))
  49. extern void * list_pop_(void ** pist, icmp_f fget, const void * left);
  50.  
  51. //
  52. // list_add - 链表中添加数据, 从小到大 fadd(left, ) <= 0
  53. // list : 基础的链表结构
  54. // pist : 指向基础的链表结构
  55. // fadd : 插入数据方法
  56. // left : 待插入的链表结点
  57. // return : void
  58. //
  59. #define list_add(list, fadd, left) \
  60. list_add_((void **)&(list), (icmp_f)(fadd), (void *)(intptr_t)(left))
  61. extern void list_add_(void ** pist, icmp_f fadd, void * left);

大量用到一个宏技巧

  1. // list : 基础的链表结构
  2. // pist : 指向基础的链表结构
  1. #define list_delete(list, fdie) \
  2. list_delete_((void **)&(list), (node_f)(fdie))

通过宏将一维指针转成二维指针来使用. 缺点是指针不可复制. 或者复制后不能再使用上一个指针.

(等同于破坏型智能指针)优势在于潇洒, 宁可 BUG 不断, 也要帅气到底 ~

接口实现部分

开头从 delete 讲起. C 可以没有 create(alloc) , 但一定要有 delete(free). 来不及销毁证据那就不用出去嗨了.

  1. //
  2. // list_delete - 链表数据销毁操作
  3. // pist : 指向基础的链表结构
  4. // fdie : 链表中删除数据执行的方法
  5. // return : void
  6. //
  7. void
  8. list_delete_(void ** pist, node_f fdie) {
  9. if (pist && fdie) {
  10. // 详细处理链表数据变化
  11. struct $list * head = *pist;
  12. while (head) {
  13. struct $list * next = head->next;
  14. fdie(head);
  15. head = next;
  16. }
  17. *pist = NULL;
  18. }
  19. }

核心招式在于 *pist = NULL; 希望置空. (虽然没有卵用, 因为指针可复制, 存在多个引用)

如果场景不允许复制的话, 可以一用.

对于后面几个函数核心设计围绕头结点处理上, 如果处理的对象是头结点, 需要重新设置.

  1. //
  2. // list_get - 匹配得到链表中指定值
  3. // list : 基础的链表结构
  4. // fget : 链表中查找数据执行的方法
  5. // left : 待查找的结点内容
  6. // return : 查找到的节点, NULL 表示没有查到
  7. //
  8. void *
  9. list_get_(void * list, icmp_f fget, const void * left) {
  10. if (fget) {
  11. struct $list * head = list;
  12. while (head) {
  13. if (fget(left, head) == )
  14. return head;
  15. head = head->next;
  16. }
  17. }
  18. return NULL;
  19. }
  20.  
  21. //
  22. // list_pop - 匹配弹出链表中指定值
  23. // pist : 指向基础的链表结构
  24. // fget : 链表中查找数据执行的方法
  25. // left : 待查找的结点内容
  26. // return : 查找到的节点, NULL 表示没有查到
  27. //
  28. void *
  29. list_pop_(void ** pist, icmp_f fget, const void * left) {
  30. struct $list * head, * next;
  31. if (!pist || fget)
  32. return NULL;
  33.  
  34. // 看是否是头节点
  35. head = *pist;
  36. if (fget(left, head) == ) {
  37. *pist = head->next;
  38. return head;
  39. }
  40.  
  41. // 不是头节点挨个处理
  42. while (!!(next = head->next)) {
  43. if (fget(left, next) == ) {
  44. head->next = next->next;
  45. return next;
  46. }
  47. head = next;
  48. }
  49.  
  50. return NULL;
  51. }
  52.  
  53. //
  54. // list_next - 获取结点n的下一个结点.
  55. // n : 当前结点
  56. //
  57. #undef list_next
  58. #define list_next(n) ((struct $list *)(n))->next
  59.  
  60. //
  61. // list_add - 链表中添加数据, 从小到大 fadd(left, ) <= 0
  62. // pist : 指向基础的链表结构
  63. // fadd : 插入数据方法
  64. // left : 待插入的链表结点
  65. // return : void
  66. //
  67. void
  68. list_add_(void ** pist, icmp_f fadd, void * left) {
  69. struct $list * head;
  70. if (!pist || !fadd || !left)
  71. return;
  72.  
  73. // 看是否是头结点
  74. head = *pist;
  75. if (!head || fadd(left, head) <= ) {
  76. list_next(left) = head;
  77. *pist = left;
  78. return;
  79. }
  80.  
  81. // 不是头节点, 挨个比对
  82. while (head->next) {
  83. if (fadd(left, head->next) <= )
  84. break;
  85. head = head->next;
  86. }
  87.  
  88. // 添加最终的连接关系
  89. list_next(left) = head->next;
  90. head->next = left;
  91. }

很多代码强烈推荐自己多打几遍. 这是实践派绝招, 可以啥都不懂, 但会写(有思考更好)应该也是及格吧.

其中 list_next 宏设计思路也很洒脱. 对外暴露是读操作, 对内是写操作.

这里不妨赠送个测试接口

  1. //
  2. // node_f - 销毁当前对象节点
  3. // : void list_die(void * node);
  4. //
  5. typedef void (* node_f)(void * node);
  6.  
  7. //
  8. // list_each - 链表循环处理函数, 仅仅测试而已
  9. // list : 基础的链表结构
  10. // feach : 处理每个结点行为函数
  11. // return : void
  12. //
  13. #define list_each(list, feach) \
  14. list_each_((list), (node_f)(feach))
  15. extern void list_each_(void * list, node_f feach);
  16.  
  17. void
  18. list_each_(void * list, node_f feach) {
  19. if (list && feach) {
  20. struct $list * head = list;
  21. while (head) {
  22. struct $list * next = head->next;
  23. feach(head);
  24. head = next;
  25. }
  26. }
  27. }

list 使用 demo 可以参照这下面的写法

  1. #define INT_NAME (64)
  2.  
  3. struct peoples {
  4. $LIST
  5.  
  6. double age;
  7. char name[INT_NAME + ];
  8. };
  9.  
  10. // peoples_add : 默认年龄从小到大排序, 并且获取
  11. inline static int peoples_add(struct peoples * left, struct peoples * node) {
  12. return (int)(left->age - node->age);
  13. }
  14.  
  15. // peoples_each : 单纯的打印接口信息
  16. inline static void peoples_each(struct peoples * node) {
  17. printf("age = %9.6lf, name = %s\n", node->age, node->name);
  18. }
  19.  
  20. //
  21. // list test demo
  22. //
  23. void list_test(void) {
  24. void * peops = NULL;
  25.  
  26. // 这里添加数据
  27. struct peoples peop[];
  28. for (int i = ; i < LEN(peop); ++i) {
  29. peop[i].age = rand() % + rand() * 1.0 / rand();
  30. snprintf(peop[i].name, LEN(peop[i].name), "peop_%d", i);
  31. list_add(peops, peoples_add, peop + i);
  32. }
  33.  
  34. // 这里打印数据
  35. list_each(peops, peoples_each);
  36. }

到这关于 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 库奥义的更多相关文章

  1. mysql基础之对库表操作

    原文:mysql基础之对库表操作 查看一下所有的库,怎么办? Mysql>Show databases; 选库语句: Use 库名 创建一个数据库: create database 数据库名 [ ...

  2. js基础和工具库

    /* * 作者: 胡乐 * 2015/4/18 * js 基础 和 工具库 * * * */ //根据获取对象 function hGetId(id){ return document.getElem ...

  3. python 3.x 爬虫基础---常用第三方库(requests,BeautifulSoup4,selenium,lxml )

    python 3.x 爬虫基础 python 3.x 爬虫基础---http headers详解 python 3.x 爬虫基础---Urllib详解 python 3.x 爬虫基础---常用第三方库 ...

  4. Python基础面试题库

    Python基础面试题库   Python是一门学习曲线较为容易的编程语言,随着人工智能时代的到来,Python迎来了新一轮的高潮.目前,国内知乎.网易(游戏).腾讯(某些网站).搜狐(邮箱).金山. ...

  5. python基础和编程库

    Python编程从入门到实践-------基础入门 1.Python中的变量 2.Python首字母大写使用title()方法,全部大写upper()方法,全部小写lower()方法 3.Python ...

  6. Robot Framework - 基础关键字 BuiltIn 库(一)

    今天给大家分享的是Robot Framework 机器人框架中 BuiltIn 基础库的使用...BuiltIn 库里面提供了很多基础方法助力于我们在自动化测试领域中做的更好!——本系列教程是教会大家 ...

  7. MySQL数据分析-(8)SQL基础操作之库操作

    前面我们讲了学习SQL的两个逻辑框架,jacky说了这样一个逻辑:库是为了存储表的,所以一定是先有库才有表:同样的道理,有表才有表中的数据,是吧,肯定是这个逻辑:那么,今天jacky就捋着这个逻辑从库 ...

  8. 【Python爬虫】HTTP基础和urllib库、requests库的使用

    引言: 一个网络爬虫的编写主要可以分为三个部分: 1.获取网页 2.提取信息 3.分析信息 本文主要介绍第一部分,如何用Python内置的库urllib和第三方库requests库来完成网页的获取.阅 ...

  9. Python 基础教程 —— Pandas 库常用方法实例说明

    目录 1. 常用方法 pandas.Series 2. pandas.DataFrame ([data],[index])   根据行建立数据 3. pandas.DataFrame ({dic})  ...

随机推荐

  1. [COGS 2064]爬山

    2064. 爬山 ★☆   输入文件:mountain.in   输出文件:mountain.out   简单对比时间限制:1 s   内存限制:256 MB [题目描述] 球有一天走在街上. 一个健 ...

  2. Github的commit规范

    参考链接:GIT写出好的 commit message 基本要求 第一行应该少于50个字. 随后是一个空行 第一行题目也可以写成:Fix issue #8976 永远不在 git commit 上增加 ...

  3. shell编程技巧和陷阱

    先推荐两本经典书籍: 1.advanced bash scripting guide http://www.tldp.org/LDP/abs/abs-guide.pdf 2.Unix Power To ...

  4. 安卓原生与hml交互(WebView基础)

    WebView加载页面 webView有两种加载方式, 加载网络地址 webView.loadUrl("www.xxx.com/index.html"); 加载本地资源 webVi ...

  5. BZOJ 2440 完全平方数 莫比乌斯反演模板题

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=2440 题目大意: 求第k个无平方因子的数 思路: 二分答案x,求1-x中有多少个平方因 ...

  6. git 如何忽略文件以及使用.gitignore 不生效的解决办法

    (1) git 如何忽略文件 在git中如果想忽略掉某个文件,不让这个文件提交到版本库中,可以使用修改根目录中 .gitignore 文件的方法(如无,则需自己手工建立此文件).这个文件每一行保存了一 ...

  7. CNN识别验证码1

    之前学习python的时候,想尝试用requests实现自动登陆,但是现在网站登陆都会有验证码保护,主要是为了防止暴力破解,任意用户注册.最近接触深度学习,cnn能够进行图像识别,能够进行验证码识别. ...

  8. ZooKeeper学习之路 (四)ZooKeeper开发环境eclipse配置

    一.eclipse中配置zookeeper开发环境 1)将zookeeper eclipse plugin中的6个jar包放到eclipse安装目录下的plugins文件中,重启eclipse (2) ...

  9. 【JavaScript】particle

    这是js实现的粒子动画,有两种模式,分别是zoom和line,它们对应的效果不同,但是原理都相同,具体分析如下: 部分程序如下: var p = this; p.originParams = orig ...

  10. week9:Recommender Systems

    Collaborative  filtering 的原理不是很理解? xi   是每一步电影的特征向量,表示浪漫/动作