glib 队列
原文地址: http://hi.baidu.com/study_together/blog/item/b92d822ef2589e39349bf79c.html
編譯:gcc -g -Wall -O0 fuck.c -o fuck `pkg-config --libs --cflags glib-2.0`
概念
隊列是另一個便利的數據結構。一個 隊列 會保存一列條目,而且訪問形式通常是向最后添加條目,從最前刪除條目。
當需要按到達順序進行處理時,這很有實用。標準隊列的一個變種是“雙端隊列(double-ended queue)”,或者說是 dequeue,
它支持在隊列的兩端進行添加或者刪除。
不過,在很多情況下最好避免使用隊列。隊列搜索不是特別快(是 O(n) 操作),所以,如果需要經常進行搜索,那么散列表或者樹 可能更實用。
這同樣適用于需要訪問隊列中隨機元素的情形;如果是那樣,那么將會對隊列進行很多次線性掃描。
GLib 提供了一個使用 GQueue 的 dequeue 實現;它支持標準隊列操作。它的基礎是雙向鏈表(GList), 所以它也支持很多其他操作,
比如在隊列之中進行插入和刪除。不過,如果您發現自己經常要使用這些功能,那么可能需要重新考慮容器的選擇; 或許另一個容器更為合適。
1
基本操作
這里是以“排隊買票(ticket line)”為模型的一些基本的 GQueue 操作:
#include <stdio.h>
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
printf("Is the queue empty? %s, adding folks\n", g_queue_is_empty(q) ? "Yes" : "No");
g_queue_push_tail(q, "Alice");
g_queue_push_tail(q, "Bob");
g_queue_push_tail(q, "Fred");
printf("First in line is %s\n", g_queue_peek_head(q));
printf("Last in line is %s\n", g_queue_peek_tail(q));
printf("The queue is %d people long\n", g_queue_get_length(q));
printf("%s just bought a ticket\n", g_queue_pop_head(q));
printf("Now %s is first in line\n", g_queue_peek_head(q));
printf("Someone's cutting to the front of the line\n");
g_queue_push_head(q, "Big Jim");
printf("Now %s is first in line\n", g_queue_peek_head(q));
g_queue_free(q);
return 0;
}
***** Output *****
Is the queue empty?? Yes, adding folks
First in line is Alice
Last in line is Fred
The queue is 3 people long
Alice just bought a ticket
Now Bob is first in line
Someone's cutting to the front of the line
Now Big Jim is first in line
大部分方法名稱都是完全自我描述的,不過有一些更細致之處:
* 向隊列壓入和取出條目的各種操作不返回任何內容,所以,為了使用隊列,您需要保持 g_queue_new 返回的 指針。
* 隊列的兩端都可以用于添加和刪除。如果要模擬排隊買票時排在后面的人離開轉到另一個隊列去購買,也是完全可行的。
* 有非破壞性的 peek 操作可以檢查隊列頭或尾的條目。
* g_queue_free 不接受幫助釋放每個條目的函數,所以需要手工去完成;這與 GSList 相同。
2
刪除和插入條目
雖然通常只通過在隊列的末端 添加/刪除 條目來修改它,但 GQueue 允許刪除任意條目以及在任意位置插入條目。這里是其示例:
#include <stdio.h>
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
g_queue_push_tail(q, "Alice");
g_queue_push_tail(q, "Bob");
g_queue_push_tail(q, "Fred");
printf("Queue is Alice, Bob, and Fred; removing Bob\n");
int fred_pos = g_queue_index(q, "Fred");
g_queue_remove(q, "Bob");
printf("Fred moved from %d to %d\n", fred_pos, g_queue_index(q, "Fred"));
printf("Bill is cutting in line\n");
GList* fred_ptr = g_queue_peek_tail_link(q);
g_queue_insert_before(q, fred_ptr, "Bill");
printf("Middle person is now %s\n", g_queue_peek_nth(q, 1));
printf("%s is still at the end\n", g_queue_peek_tail(q));
g_queue_free(q);
return 0;
}
***** Output *****
Queue is Alice, Bob, and Fred; removing Bob
Fred moved from 2 to 1
Bill is cutting in line
Middle person is now Bill
Fred is still at the end
有很多新函數:
* g_queue_index 在隊列中掃描某個條目并返回其索引;如果它不能找到那個條目,則返回 -1。
* 為了向隊列的中間插入一個新條目,需要一個指向希望插入位置的指針。如您所見,通過調用一個“peek link”函數,
就可以進行此處理; 這些函數包括:g_queue_peek_tail_link、g_queue_peek_head_link 以及 g_queue_peek_nth_link,它們會返回一個 GList。
然后可以將一個條目插入到 GList 之前或者之后。
* g_queue_remove 允許從隊列中的任何位置刪除某個條目。繼續使用“排隊買票”模型,這表示人們可以離開隊列; 他們組成隊列后并不固定在其中。
3
查找條目
在先前的示例中已經看到,在擁有一個指向條目數據的指針或者知道其索引的條件下如何去得到它。
不過,類似其他 GLib 容器, GQueue 也包括一些查找函數:g_queue_find 和 g_queue_find_custom:
#include <glib.h>#include <stdio.h>
gint finder(gpointer a, gpointer b) {
return strcmp(a,b);
}
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
g_queue_push_tail(q, "Alice");
g_queue_push_tail(q, "Bob");
g_queue_push_tail(q, "Fred");
g_queue_push_tail(q, "Jim");
GList* fred_link = g_queue_find(q, "Fred");
printf("The fred node indeed contains %s\n", fred_link->data);
GList* joe_link = g_queue_find(q, "Joe");
printf("Finding 'Joe' yields a %s link\n", joe_link ? "good" : "null");
GList* bob = g_queue_find_custom(q, "Bob", (GCompareFunc)finder);
printf("Custom finder found %s\n", bob->data);
bob = g_queue_find_custom(q, "Bob", (GCompareFunc)g_ascii_strcasecmp);
printf("g_ascii_strcasecmp also found %s\n", bob->data);
g_queue_free(q);
return 0;
}
***** Output *****
The fred node indeed contains Fred
Finding 'Joe' yields a null link
Custom finder found Bob
g_ascii_strcasecmp also found Bob
注意,如果 g_queue_find 找不到條目,則它會返回 null。并且可以在上面的示例中傳遞一個庫函數(比如 g_ascii_strcasecmp)
或者一個定制的函數(比如 finder)作為 g_queue_find_custom 的 GCompareFunc 參數。
4
使用隊列:拷貝、反轉和遍歷每一個(foreach) ?需要調試
由于 GQueue 的基礎是 GList,所以它支持一些列表處理操作。這里是如何使用 g_queue_copy、 g_queue_reverse 和 g_queue_foreach 的示例:
#include <stdio.h>
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
g_queue_push_tail(q, "Alice ");
g_queue_push_tail(q, "Bob ");
g_queue_push_tail(q, "Fred ");
printf("Starting out, the queue is: ");
g_queue_foreach(q, (GFunc)printf, NULL);
g_queue_reverse(q);
printf("\nAfter reversal, it's: ");
g_queue_foreach(q, (GFunc)printf, NULL);
GQueue* new_q = g_queue_copy(q);
g_queue_reverse(new_q);
printf("\nNewly copied and re-reversed queue is: ");
g_queue_foreach(new_q, (GFunc)printf, NULL);
g_queue_free(q);
g_queue_free(new_q);
return 0;
}
***** Output *****
Starting out, the queue is: Alice Bob Fred
After reversal, it's: Fred Bob Alice
Newly copied and re-reversed queue is: Alice Bob Fred
g_queue_reverse 和 g_queue_foreach 很直觀; 您已經看到它們在各種其他有序集合中得到了應用。
不過,使用 g_queue_copy 時需要稍加留心, 因為拷貝的是指針而不是數據。所以,當釋放數據時,一定不要進行重復釋放。
5
使用鏈接的更多樂趣
已經了解了鏈接的一些示例;這里是一些便利的鏈接刪除函數。不要忘記 GQueue 中的每個條目實際上是都是一個 GList 結構體
, 數據存儲在“data”成員中:
#include <stdio.h>
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
g_queue_push_tail(q, "Alice ");
g_queue_push_tail(q, "Bob ");
g_queue_push_tail(q, "Fred ");
g_queue_push_tail(q, "Jim ");
printf("Starting out, the queue is: ");
g_queue_foreach(q, (GFunc)printf, NULL);
GList* fred_link = g_queue_peek_nth_link(q, 2);
printf("\nThe link at index 2 contains %s\n", fred_link->data);
g_queue_unlink(q, fred_link);
g_list_free(fred_link);
GList* jim_link = g_queue_peek_nth_link(q, 2);
printf("Now index 2 contains %s\n", jim_link->data);
g_queue_delete_link(q, jim_link);
printf("Now the queue is: ");
g_queue_foreach(q, (GFunc)printf, NULL);
g_queue_free(q);
return 0;
}
***** Output *****
Starting out, the queue is: Alice Bob Fred Jim
The link at index 2 contains Fred
Now index 2 contains Jim
Now the queue is: Alice Bob
注意,g_queue_unlink 并不釋放沒有被鏈接的 GList 結構體,所以需要自己去完成。 并且,由于它是一個 GList 結構體,
所以需要使用 g_list_free 函數來釋放它 —— 而不是 簡單的 g_free 函數。當然,更簡單的是調用 g_queue_delete_link 并讓它為您釋放內存。
6
排序
隊列排序好像不太常見,不過由于各種其他鏈表操作都得到了支持(比如 insert 和 remove),所以此操作也得到了支持。
如果恰巧您希望重新對隊列進行排序,將高優先級 的條目移動到前端,那么這也會很便利。這里是一個示例:
#include <stdio.h>
typedef struct {
char* name;
int priority;
} Task;
Task* make_task(char* name, int priority) {
Task* t = g_new(Task, 1);
t->name = name;
t->priority = priority;
return t;
}
void prt(gpointer item) {
printf("%s ", ((Task*)item)->name);
}
gint sorter(gconstpointer a, gconstpointer b, gpointer data) {
return ((Task*)a)->priority - ((Task*)b)->priority;
}
int main(int argc, char** argv) {
GQueue* q = g_queue_new();
g_queue_push_tail(q, make_task("Reboot server", 2));
g_queue_push_tail(q, make_task("Pull cable", 2));
g_queue_push_tail(q, make_task("Nethack", 1));
g_queue_push_tail(q, make_task("New monitor", 3));
printf("Original queue: ");
g_queue_foreach(q, (GFunc)prt, NULL);
g_queue_sort(q, (GCompareDataFunc)sorter, NULL);
printf("\nSorted queue: ");
g_queue_foreach(q, (GFunc)prt, NULL);
g_queue_free(q);
return 0;
}
***** Output *****
Original queue: Reboot server?? Pull cable?? Nethack?? New monitor
Sorted queue: Nethack?? Reboot server?? Pull cable?? New monitor
現在您就擁有了一個模擬您的工作的 GQueue,偶爾還可以對它進行排序,可以欣喜地發現,Nethack 被提升到了其正確的位置,到了隊列的 最前端!
實際應用
GQueue 沒有在 Evolution 中得到應用,但是 GIMP 和 Gaim 用到了它。
GIMP:
* gimp-2.2.4/app/core/gimpimage-contiguous-region.c 在一個查找相鄰片段的工具函數中使用 GQueue 存儲一系列坐標。
只要片段保存鄰接,新的點就會被壓入到隊列末端,然后在下一個循環迭代中取出并被檢查。
* gimp-2.2.4/app/vectors/gimpvectors-import.c 使用 GQueue 作為 Scalable Vector Graphics(SVG)解析器的一部分。
它被當做棧使用,條目的壓入和取出都在隊列的頭上進行。
Gaim:
* gaim-1.2.1/src/protocols/msn/switchboard.c 使用 GQueue 來追蹤發出的消息。新的消息壓入到隊列的尾部,當發送后從頭部取出。
* gaim-1.2.1/src/proxy.c 使用 GQueue 追蹤 DNS 查找請求。它使用隊列作為應用程序代碼與 DNS 子進程之間的臨時保存區域。
總結
- 上一篇: FileSystemObject和Fol
- 下一篇: Domino的压缩数据库的Load Co