嵌入式大杂烩周记 | 第 3 期:sys/queue.h

大家好,我是杂烩君。

嵌入式大杂烩周记主要是一些实用项目学习分享,每周一篇,每篇一个主题。

内容主要来源于我们之前收集的资料:

https://gitee.com/zhengnianli/EmbedSummary

本期主角:sys/queue.h

queue.h是Linux、FreeBSD中的一个头文件。

FreeBSD:FreeBSD 是一种类 UNIX操作系统。

这是一个很实用的头文件,因为这个头文件里全是宏定义操作,所以其不仅可以使用在Linux/嵌入式Linux项目中,也可以使用在单片机项目中,我也是因为在我们的单片机项目中看到,才知道有这么一个头文件的。我觉得挺实用的,与大家分享。

它使用宏实现了如下数据结构:

  • SLIST:单向无尾链表

  • LIST:双向无尾链表

  • STAILQ:单向有尾链表(可作队列使用)

  • TAILQ:双向有尾链表(可作队列使用)

所有的数据结构都支持如下功能:

  • 在链表头插入节点

  • 在任意节点后插入节点

  • 删除节点

  • 遍历节点

我们可以在Linux系统的如下路径中找到这个头文件:

/usr/include/sys/queue.h

也可以通过如下网址查看:

https://code.woboq.org/userspace/glibc/misc/sys/queue.h.html

sys/queue.h的使用

下面我们基于SLIST来演示其使用。

SLIST相关宏定义:

d4480679b091686dcef7afa7b87b5349.png
/*
 * Singly-linked List definitions.
 */
#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 */     \
}

/*
 * Singly-linked List functions.
 */
#define SLIST_INIT(head) do {         \
 (head)->slh_first = NULL;         \
} while (/*CONSTCOND*/0)

#define SLIST_INSERT_AFTER(slistelm, elm, field) do {   \
 (elm)->field.sle_next = (slistelm)->field.sle_next;   \
 (slistelm)->field.sle_next = (elm);       \
} while (/*CONSTCOND*/0)

#define SLIST_INSERT_HEAD(head, elm, field) do {    \
 (elm)->field.sle_next = (head)->slh_first;     \
 (head)->slh_first = (elm);         \
} while (/*CONSTCOND*/0)

#define SLIST_REMOVE_HEAD(head, field) do {      \
 (head)->slh_first = (head)->slh_first->field.sle_next;  \
} while (/*CONSTCOND*/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;    \
 }               \
} while (/*CONSTCOND*/0)

#define SLIST_FOREACH(var, head, field)       \
 for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next)

/*
 * Singly-linked List access methods.
 */
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)

下面我们通过实例来操作。

首先,创建链表头节点、其它节点结构体,用到SLIST_HEAD与SLIST_ENTRY这两个宏定义:

#define ELEM_TYPE int

/* 链表节点 */
typedef struct node 
{
    ELEM_TYPE data;
    SLIST_ENTRY(node) field; 
}node_st;

/* 链表头 */
typedef SLIST_HEAD(head, node) head_st;

链表数据域类型我们定义为int,field表示的是指针域。

① 创建一个头结点:

/* 创建链表头节点并初始化 */
head_st *head = (head_st *)malloc(sizeof(head_st));
SLIST_INIT(head);

头节点:不存任何数据的空节点,通常作为链表的第一个节点。

② 在链表头部分别插入节点node1、node2:

/* 头插法插入一个节点node1 */
node_st *node1 = (node_st *)malloc(sizeof(node_st));
node1->data = 1;
SLIST_INSERT_HEAD(head, node1, field);

/* 头插法插入一个节点node2 */
node_st *node2 = (node_st *)malloc(sizeof(node_st));
node2->data = 2;
SLIST_INSERT_HEAD(head, node2, field);
930fb5987495839cee6dd1828956aa05.png

头指针:永远指向链表第一个节点的位置。

SLIST_INSERT_HEAD是从链表头部插入节点,新节点总是从头结点之后插入。

③ 在链表节点node2之后插入节点node3:

node_st *node3 = (node_st *)malloc(sizeof(node_st));
node3->data = 3;
SLIST_INSERT_AFTER(node2, node3, field);
fd8b8414ecc6d2a94767f8ed85047cef.png

SLIST_INSERT_AFTER是从指定节点slistelm之后插入新节点elm。

④ 遍历链表:

node_st *tmp_elm;
SLIST_FOREACH(tmp_elm, head, field)
{
 printf("%d ", tmp_elm->data);
}

输出为tmp_elm,访问tmp_elm即可。

⑤ 删除某个节点node2

SLIST_REMOVE(head, node2, node, field);
free(node2);
node2 = NULL;

⑥ 销毁整个链表

while (!SLIST_EMPTY(head)) 
{    
    node_st *p = SLIST_FIRST(head);
    SLIST_REMOVE_HEAD(head, field);
    free(p);
    p = NULL;
}
free(head);
head = NULL;

完整测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>

#define ELEM_TYPE int

/* 链表节点 */
typedef struct node 
{
    ELEM_TYPE data;
    SLIST_ENTRY(node) field; 
}node_st;

/* 链表头 */
typedef SLIST_HEAD(head, node) head_st;

int main(void)
{
    /* 创建链表头节点并初始化 */
    head_st *head = (head_st *)malloc(sizeof(head_st));
    SLIST_INIT(head);

    /* 头插法插入一个节点node1 */
    node_st *node1 = (node_st *)malloc(sizeof(node_st));
    node1->data = 1;
    SLIST_INSERT_HEAD(head, node1, field);

    /* 头插法插入一个节点node2 */
    node_st *node2 = (node_st *)malloc(sizeof(node_st));
    node2->data = 2;
    SLIST_INSERT_HEAD(head, node2, field);

    /* 遍历打印当前链表节点 */
    printf("list:\n");
    node_st *tmp_elm;
    SLIST_FOREACH(tmp_elm, head, field)
    {
        printf("%d ", tmp_elm->data);
    }
    printf("\n");

    /* 尾插法插入一个节点node3 */
    printf("insert node3:\n");
    node_st *node3 = (node_st *)malloc(sizeof(node_st));
    node3->data = 3;
    SLIST_INSERT_AFTER(node2, node3, field);
    SLIST_FOREACH(tmp_elm, head, field)
    {
        printf("%d ", tmp_elm->data);
    }
    printf("\n");

    /* 删除node2 */
    printf("delete node2:\n");
    SLIST_REMOVE(head, node2, node, field);
    free(node2);
    node2 = NULL;
    SLIST_FOREACH(tmp_elm, head, field)
    {
        printf("%d ", tmp_elm->data);
    }
    printf("\n");

    /* 销毁链表 */
    while (!SLIST_EMPTY(head)) 
    {    
        node_st *p = SLIST_FIRST(head);
        SLIST_REMOVE_HEAD(head, field);
        free(p);
        p = NULL;
    }
    free(head);
    head = NULL;

    return 0;
}

编译、运行:

45bf2a43f6c2bc5e3ac5e0474c3cc0d1.png

运行结果与我们上面分析的一致。

本次我们只分享queue.h里最简单的数据结构。其它几种数据结构的使用例子及相关宏说明可以通过man命令查看。

man是Linux下的帮助命令。

我们输入 man queue 即可查到queue.h的相关说明:

6003427fab8459bb4029b8ea8cdd4a56.png 110fbcac08cbf14144310254e4a8141a.png 7d3cfb568a0a29f789189f05f1adade9.png

可以看到,man命令很强大,可查到queue的帮助说明很详细,有宏的说明及使用示例等。

以上就是本次的分享,文章如有错误,欢迎支持,谢谢!我们下期见~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式大杂烩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值