sys/queue.h分析

[轉自:@astrotycoon](http://blog.csdn.net/astrotycoon/article/details/42917367)

這兩天有興趣學習使用了下系統頭文件sys/queue.h中的鏈表/隊列的實現,感覺實現的很是優美,關鍵是以後再也不需要自己實現這些基本的數據結構了,哈哈!

我的系統環境是

正好需要使用隊列,那麼本篇就以其中的尾隊列(tail queue)爲例,結合實際的測試程序和示意圖(億圖軟件)來說明。

測試程序tailq.c如下:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/queue.h>  
  4.   
  5. struct _Data {  
  6.     int                 value;  
  7.     TAILQ_ENTRY(_Data)  tailq_entry;  
  8. };  
  9.   
  10. int main(int argc, const char *argv[])  
  11. {  
  12.     /* 1. 初始化隊列 */  
  13. #if 0  
  14.     TAILQ_HEAD(tailq_head, _Data)   head = TAILQ_HEAD_INITIALIZER(head);  
  15. #else  
  16.     TAILQ_HEAD(tailq_head, _Data)   head;  
  17.     TAILQ_INIT(&head);  
  18. #endif  
  19.     int i;  
  20.     struct _Data *pdata = NULL;  
  21.   
  22.     /* 2. 在隊列末尾插入data1 */  
  23.     struct _Data *data1 = (struct _Data *)calloc(1, sizeof(struct _Data));  
  24.     data1->value = 1;  
  25.     TAILQ_INSERT_TAIL(&head, data1, tailq_entry);  
  26.     /* 3. 在隊列末尾插入data2 */  
  27.     struct _Data *data2 = (struct _Data *)calloc(1, sizeof(struct _Data));  
  28.     data2->value = 2;  
  29.     TAILQ_INSERT_TAIL(&head, data2, tailq_entry);  
  30.     /* 4. 在data1之後插入data3 */  
  31.     struct _Data *data3 = (struct _Data *)calloc(1, sizeof(struct _Data));  
  32.     data3->value = 3;  
  33.     TAILQ_INSERT_AFTER(&head, data1, data3, tailq_entry);  
  34.     /* 5. 在data2之前插入data4 */  
  35.     struct _Data *data4 = (struct _Data *)calloc(1, sizeof(struct _Data));  
  36.     data4->value = 4;  
  37.     TAILQ_INSERT_BEFORE(data2, data4, tailq_entry);  
  38.     /* 6. 在隊列首部插入data5 */  
  39.     struct _Data *data5 = (struct _Data *)calloc(1, sizeof(struct _Data));  
  40.     data5->value = 5;  
  41.     TAILQ_INSERT_HEAD(&head, data5, tailq_entry);  
  42.     /* 遍歷隊列 */  
  43.     TAILQ_FOREACH(pdata, &head, tailq_entry) {  
  44.         printf("pdata->value1 = %d\n", pdata->value);       
  45.     }  
  46.     puts("");  
  47.     /* 7. 刪除data5 */  
  48.     TAILQ_REMOVE(&head, data5, tailq_entry);  
  49.   
  50.     TAILQ_FOREACH(pdata, &head, tailq_entry) {  
  51.         printf("pdata->value1 = %d\n", pdata->value);       
  52.     }  
  53.     puts("");  
  54.   
  55.     /* 正序遍歷尾隊列 */  
  56.     /* 方法一 */  
  57.     TAILQ_FOREACH(pdata, &head, tailq_entry) {  
  58.         printf("pdata->value1 = %d\n", pdata->value);       
  59.     }  
  60.     puts("");  
  61.     /* 方法二 */  
  62.     for (pdata = TAILQ_FIRST(&head); pdata;   
  63.                     pdata = TAILQ_NEXT(pdata, tailq_entry)) {  
  64.         printf("pdata->value1 = %d\n", pdata->value);       
  65.     }  
  66.   
  67.     puts("");  
  68.   
  69.     /* 逆序遍歷尾隊列 */  
  70.     /* 方法一 */  
  71.     TAILQ_FOREACH_REVERSE(pdata, &head, tailq_head, tailq_entry) {  
  72.         printf("pdata->value1 = %d\n", pdata->value);       
  73.     }  
  74.     puts("");  
  75.     /* 方法二 */  
  76.     for (pdata = TAILQ_LAST(&head, tailq_head); pdata;   
  77.             pdata = TAILQ_PREV(pdata, tailq_head, tailq_entry)) {  
  78.         printf("pdata->value1 = %d\n", pdata->value);       
  79.         TAILQ_REMOVE(&head, pdata, tailq_entry);  
  80.         free(pdata);  
  81.     }  
  82.   
  83.     if (TAILQ_EMPTY(&head)) {  
  84.         printf("the tail queue is empty now.\n");     
  85.     }  
  86.   
  87.     exit(EXIT_SUCCESS);  
  88. }  

我們首先來看一下這個尾隊列的定義:


注意,其中的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,也可以使用上述的幾個訪問方法來遍歷。


好了,其實本文沒啥內容,對我個人而言就主要是想熟悉下億圖這個軟件,哈哈




參考鏈接:

queue.h之尾隊列

關於libevent與FreeBSD內核中TAILQ隊列的理解

深入理解FreeBSD中的TAILQ

libevent源碼分析-- queue.h中TAILQ_QUEUE的理解

queue.h(Circular queue 循環隊列) 

libevent源碼之TAILQ詳解

《 QEMU代碼中的QLIST

Libevent源碼分析-----TAILQ_QUEUE隊列


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章