[轉自:@astrotycoon](http://blog.csdn.net/astrotycoon/article/details/42917367)
這兩天有興趣學習使用了下系統頭文件sys/queue.h中的鏈表/隊列的實現,感覺實現的很是優美,關鍵是以後再也不需要自己實現這些基本的數據結構了,哈哈!
我的系統環境是
正好需要使用隊列,那麼本篇就以其中的尾隊列(tail queue)爲例,結合實際的測試程序和示意圖(億圖軟件)來說明。
測試程序tailq.c如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/queue.h>
- struct _Data {
- int value;
- TAILQ_ENTRY(_Data) tailq_entry;
- };
- int main(int argc, const char *argv[])
- {
- /* 1. 初始化隊列 */
- #if 0
- TAILQ_HEAD(tailq_head, _Data) head = TAILQ_HEAD_INITIALIZER(head);
- #else
- TAILQ_HEAD(tailq_head, _Data) head;
- TAILQ_INIT(&head);
- #endif
- int i;
- struct _Data *pdata = NULL;
- /* 2. 在隊列末尾插入data1 */
- struct _Data *data1 = (struct _Data *)calloc(1, sizeof(struct _Data));
- data1->value = 1;
- TAILQ_INSERT_TAIL(&head, data1, tailq_entry);
- /* 3. 在隊列末尾插入data2 */
- struct _Data *data2 = (struct _Data *)calloc(1, sizeof(struct _Data));
- data2->value = 2;
- TAILQ_INSERT_TAIL(&head, data2, tailq_entry);
- /* 4. 在data1之後插入data3 */
- struct _Data *data3 = (struct _Data *)calloc(1, sizeof(struct _Data));
- data3->value = 3;
- TAILQ_INSERT_AFTER(&head, data1, data3, tailq_entry);
- /* 5. 在data2之前插入data4 */
- struct _Data *data4 = (struct _Data *)calloc(1, sizeof(struct _Data));
- data4->value = 4;
- TAILQ_INSERT_BEFORE(data2, data4, tailq_entry);
- /* 6. 在隊列首部插入data5 */
- struct _Data *data5 = (struct _Data *)calloc(1, sizeof(struct _Data));
- data5->value = 5;
- TAILQ_INSERT_HEAD(&head, data5, tailq_entry);
- /* 遍歷隊列 */
- TAILQ_FOREACH(pdata, &head, tailq_entry) {
- printf("pdata->value1 = %d\n", pdata->value);
- }
- puts("");
- /* 7. 刪除data5 */
- TAILQ_REMOVE(&head, data5, tailq_entry);
- TAILQ_FOREACH(pdata, &head, tailq_entry) {
- printf("pdata->value1 = %d\n", pdata->value);
- }
- puts("");
- /* 正序遍歷尾隊列 */
- /* 方法一 */
- TAILQ_FOREACH(pdata, &head, tailq_entry) {
- printf("pdata->value1 = %d\n", pdata->value);
- }
- puts("");
- /* 方法二 */
- for (pdata = TAILQ_FIRST(&head); pdata;
- pdata = TAILQ_NEXT(pdata, tailq_entry)) {
- printf("pdata->value1 = %d\n", pdata->value);
- }
- puts("");
- /* 逆序遍歷尾隊列 */
- /* 方法一 */
- TAILQ_FOREACH_REVERSE(pdata, &head, tailq_head, tailq_entry) {
- printf("pdata->value1 = %d\n", pdata->value);
- }
- puts("");
- /* 方法二 */
- for (pdata = TAILQ_LAST(&head, tailq_head); pdata;
- pdata = TAILQ_PREV(pdata, tailq_head, tailq_entry)) {
- printf("pdata->value1 = %d\n", pdata->value);
- TAILQ_REMOVE(&head, pdata, tailq_entry);
- free(pdata);
- }
- if (TAILQ_EMPTY(&head)) {
- printf("the tail queue is empty now.\n");
- }
- exit(EXIT_SUCCESS);
- }
我們首先來看一下這個尾隊列的定義:
注意,其中的tqe_prev指向的不是前一個元素,而是前一個元素中的tqe_next,這樣定義的一個好處就是*tqe_prev就是自身的地址,**tqe_prev就是自身。
好,現在就順着我的測試程序來一步步看如何使用這個尾隊列吧!
第一步是初始化步驟。關於初始化我們有兩種方法:使用宏TAILQ_HEAD_INITIALIZER或者使用宏TAILQ_INIT,這兩者都是可以的,唯一的區別是傳遞給宏TAILQ_INIT的是地址,而傳遞給宏TAILQ_HEAD_INITIALIZER的不是,這點需要引起我們的注意。
初始化後的數據結構怎樣的呢? 我們看下示意圖:
接下來的兩個步驟(步奏2和步奏3)都是在這個隊列的尾部追加元素(data1和data2),使用的是宏TAILQ_INSERT_TAIL:
那麼隊列的變化過程是這樣的,請看示意圖:
接下來的操作是在data1之前插入data3,使用的是宏TAILQ_INSERT_AFTER:
形象的示意圖如下:
整理後的示意圖如下:
緊接着的操作是在data2之前插入data4,使用的是宏TAILQ_INSERT_BEFORE:
形象的示意圖如下:
整理後的示意圖如下:
現在在隊列首部插入data5,使用的是宏TAILQ_INSERT_HEAD:
形象的示意圖如下:
整理後的示意圖如下:
刪除數據data5使用是宏TAILQ_REMOVE:
現在的隊列布局如下:
好了,基本的操作就這麼多,接下來我們看看提供的幾個數據元素訪問方法:
前三個很簡單,一看就懂,我們重點分析下TAILQ_LAST和TAILQ_PREV。
TAILQ_LAST的目的是獲取隊列中的最後一個元素的地址,注意是地址哦!(head)->tqh_last代表的是最後一個元素中tqe_next的地址,通過強轉之後,((struct headname *)((head)->tqh_last))->tqh_last實際上就是最後一個元素中的tqe_prev,而文章一開始介紹定義的時候就說過,*tqe_prev代表的是自身元素的地址,所以TAILQ_LAST最後獲取的就是最後一個元素的地址,宏TAILQ_PREV的道理是一樣的。
OK,測試程序接下來就是遍歷整個隊列,並打印出數據,可以使用提供的宏TAILQ_FOREACH,也可以使用上述的幾個訪問方法來遍歷。
好了,其實本文沒啥內容,對我個人而言就主要是想熟悉下億圖這個軟件,哈哈
參考鏈接:
《關於libevent與FreeBSD內核中TAILQ隊列的理解》
《libevent源碼分析-- queue.h中TAILQ_QUEUE的理解》
《queue.h(Circular queue 循環隊列) 》
《Libevent源碼分析-----TAILQ_QUEUE隊列》