一、前言
相信有一定编程经验的你,在项目中经常会遇到队列、链表、ringbuffer等问题。刚开始的时候,都有过自己编写代码(造轮子)的经验。但随着工作经验的提升,经常会发现:
- 以前自己造的轮子还有很多漏洞,并不能满足现在或将来的产品需求;甚至已经给现有产品埋下了坑;
- 接触的越来越多,发现有很多经典开源的库(GitHub),已经实现了以前自己造的轮子,而且开源库做的更好(应该是非常的好)。
那在这种情况下,我们应该怎么办呢?我的建议是:
- 如果产品才开始设计,那就直接加入到产品,减轻自己的工作量还可以加速开发进度;
- 如果对现有系统改动不大,那就立马行动,将开源的/现成的轮子装到我们的产品里面;
- 如果对现有系统改动太大,项目进度又很紧迫,那就根据严重和紧急程度与领导协商解决吧。
我这里讲解的queue.h就是一个非常经典的文件。它定义了一系列宏操作,实现了队列、尾队列、单向链表、双向链表以及树形结构的封装,效率非常高。更关键的是它仅仅只是一个头文件,加入项目也是非常的简单。你可以在很多地方见到queue.h的身影:
- Linux:sys/queue.h
- FreeBSD:sys/queue.h
- libevent开源库:compat/sys/queue.h
二、使用
queue.h定义了5个基本的数据类型:
- 单向链表
- 双向链表
- 队列
- 尾队列
- 循环队列
引入的方法非常简单,只需要在你的源码里面加入:
#include <sys/queue.h>
如果不是在Linux系统下,也可以字节拷贝Linux系统/usr/include/sys/queue.h文件到你的项目。
queue相关链表/队列的使用流程为:
- 定义自己的结构体;
- 在结构体中使用XXXX_ENTRY定义链表/队列成员变量;
- 使用XXXX_HEAD定义一个链表/队列头;
- 使用XXXX_INIT初始化链表/队列头(也可在定义时初始化);
- 使用相关的INSERT、REMOVE、FOREACH、REPLACE方法操作队列。
注意:如果遇到多个线程同时操作链表/队列时,需要对队列加锁以保证正确性;且应该是只对队列加锁,而不对结构体的数据内容加锁(浅锁),以便提高运行效率;
2.1 单向链表
2.1.1 概述
A singly-linked list is headed by a single forward pointer. The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. New elements can be added to the list after an existing element or at the head of the list. Elements being removed from the head of the list should use the explicit macro for this purpose for optimum efficiency. A singly-linked list may only be traversed in the forward direction. Singly-linked lists are ideal for applications with large datasets and few or no removals or for implementing a LIFO queue.
与单向链表相关的宏、方法和函数有:
// definitions
SLIST_HEAD(name, type)
SLIST_HEAD_INITIALIZER(head)
SLIST_ENTRY(type)
// access methods
SLIST_FIRST(head)
SLIST_END(head)
SLIST_EMPTY(head)
SLIST_NEXT(elm, field)
LIST_FOREACH(var, head, field)
SLIST_FOREACH_PREVPTR(var, varp, head, field)
// functions
SLIST_INIT(head)
SLIST_INSERT_AFTER(slistelm, elm, field)
SLIST_INSERT_HEAD(head, elm, field)
SLIST_REMOVE_NEXT(head, elm, field)
SLIST_REMOVE_HEAD(head, field)
SLIST_REMOVE(head, elm, type, field)
2.1.2 宏定义说明
与单向链表相关的宏定义只有三个:
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
通过命名,可以很明确的知道其用法:
- SLIST_HEAD用于定义一个单向链表数据结构体的头变量,该结构体只有一个指针成员slh_first,指向第一个type类型的数据结构;name可以不用(填写);
- SLIST_HEAD_INITIALIZER用于在定义时初始化SLIST_HEAD定义的数据结构体的头变量;head可以不用填写;
- SLIST_ENTRY则用于定义一个(用户)结构体的成员变量,该成员变量只包含一个指向type类型的指针sle_next;
2.1.3 访问方法说明
与单向链表相关的访问方法有6个:
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_END(head) NULL
#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_FOREACH(var, head, field) \
for((var) = SLIST_FIRST(head); \
(var) != SLIST_END(head); \
(var) = SLIST_NEXT(var, field))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != SLIST_END(head); \
(varp) = &SLIST_NEXT((var), field))
通过命名,可以很明确的知道其用法:
- SLIST_FIRST用于获取单向链表的第一个元素;
- SLIST_END定义了尾部的判断标准;
- SLIST_EMPTY用于判断单向链表是否为空:空则返回true,否则返回false;
- SLIST_NEXT用于获取elm元素的下一个元素,field是前面用SLIST_ENTRY定义的成员变量名;
- SLIST_FOREACH用于遍历单向链表,var是临时变量,head是链表头指针(SLIST_HEAD定义的变量),field是SLIST_ENTRY定义的成员变量名;
- SLIST_FOREACH_PREVPTR与SLIST_FOREACH类似,用于遍历单向链表,不过提供更多的一个临时指针变量varp,指向var指向元素的地址;
2.1.4 操作函数说明
与单向链表相关的函数有6个:
#define SLIST_INIT(head) { \
SLIST_FIRST(head) = SLIST_END(head); \
}
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
(elm)->field.sle_next = (slistelm)->field.sle_next; \
(slistelm)->field.sle_next = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
(elm)->field.sle_next = (head)->slh_first; \
(head)->slh_first = (elm); \
} while (0)
#define SLIST_REMOVE_NEXT(head, elm, field) do { \
(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
(head)->slh_first = (head)->slh_first->field.sle_next; \
} while (0)
#define SLIST_REMOVE(head, elm, type, field) do { \
if ((head)->slh_first == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} else { \
struct type *curelm = (head)->slh_first; \
\
while (curelm->field.sle_next != (elm)) \
curelm = curelm->field.sle_next; \
curelm->field.sle_next = \
curelm->field.sle_next->field.sle_next; \
_Q_INVALIDATE((elm)->field.sle_next); \
} \
} while (0)
通过命名,可以很明确的知道其用法:
- SLIST_INIT用于初始化SLIST_HEAD定义的头指针变量;当然也可以在使用SLIST_HEAD定义头指针变量时同时使用SLIST_HEAD_INITIALIZER进行初始化;
- SLIST_INSERT_AFTER用于将元素elm插入到当前链表元素slistelm的后面;
- SLIST_INSERT_HEAD用于将元素elm插入到当前链表head的头部;head是SLIST_HEAD定义的链表头指针;
- SLIST_REMOVE_NEXT用于将elm后面的元素删除,head未使用;注意删除时判断elm后面是否还有元素,否则会崩溃;
- SLIST_REMOVE_HEAD用于删除第一个元素;注意删除时判断head是否为空,否则会崩溃;
- SLIST_REMOVE用于从head链表中删除elm元素;注意首先判断elm元素是否在head链表中,否则会崩溃;
2.1.5 一个简单实例
#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>
struct SLIST_ITEM {
int value;
SLIST_ENTRY(SLIST_ITEM) entry;
};
static SLIST_HEAD(,SLIST_ITEM) slist_head;
//static SLIST_HEAD(,SLIST_ITEM) slist_head = SLIST_HEAD_INITIALIZER();
int main(int argc, char *argv[])
{
int i = 0;
struct SLIST_ITEM *item;
struct SLIST_ITEM *tmp_item;
SLIST_INIT(&slist_head);
if (SLIST_EMPTY(&slist_head))
printf("single list is empty\n");
for( i = 0; i < 10; i += 1)
{
item = (struct SLIST_ITEM *)malloc(sizeof(struct SLIST_ITEM));
item->value = i;
SLIST_INSERT_HEAD(&slist_head, item, entry);
}
printf("after insert 10 item to single list:\n");
SLIST_FOREACH(item, &slist_head, entry)
printf("item value = %d\n", item->value);
SLIST_FOREACH(tmp_item, &slist_head, entry)
{
if(tmp_item->value % 2 != 0)
SLIST_REMOVE(&slist_head, tmp_item,