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`
概念
隊(duì)列是另一個(gè)便利的數(shù)據(jù)結(jié)構(gòu)。一個(gè) 隊(duì)列 會(huì)保存一列條目,而且訪問(wèn)形式通常是向最后添加條目,從最前刪除條目。
當(dāng)需要按到達(dá)順序進(jìn)行處理時(shí),這很有實(shí)用。標(biāo)準(zhǔn)隊(duì)列的一個(gè)變種是“雙端隊(duì)列(double-ended queue)”,或者說(shuō)是 dequeue,
它支持在隊(duì)列的兩端進(jìn)行添加或者刪除。
不過(guò),在很多情況下最好避免使用隊(duì)列。隊(duì)列搜索不是特別快(是 O(n) 操作),所以,如果需要經(jīng)常進(jìn)行搜索,那么散列表或者樹(shù) 可能更實(shí)用。
這同樣適用于需要訪問(wèn)隊(duì)列中隨機(jī)元素的情形;如果是那樣,那么將會(huì)對(duì)隊(duì)列進(jìn)行很多次線性掃描。
GLib 提供了一個(gè)使用 GQueue 的 dequeue 實(shí)現(xiàn);它支持標(biāo)準(zhǔn)隊(duì)列操作。它的基礎(chǔ)是雙向鏈表(GList), 所以它也支持很多其他操作,
比如在隊(duì)列之中進(jìn)行插入和刪除。不過(guò),如果您發(fā)現(xiàn)自己經(jīng)常要使用這些功能,那么可能需要重新考慮容器的選擇; 或許另一個(gè)容器更為合適。
1
基本操作
這里是以“排隊(duì)買票(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
大部分方法名稱都是完全自我描述的,不過(guò)有一些更細(xì)致之處:
* 向隊(duì)列壓入和取出條目的各種操作不返回任何內(nèi)容,所以,為了使用隊(duì)列,您需要保持 g_queue_new 返回的 指針。
* 隊(duì)列的兩端都可以用于添加和刪除。如果要模擬排隊(duì)買票時(shí)排在后面的人離開(kāi)轉(zhuǎn)到另一個(gè)隊(duì)列去購(gòu)買,也是完全可行的。
* 有非破壞性的 peek 操作可以檢查隊(duì)列頭或尾的條目。
* g_queue_free 不接受幫助釋放每個(gè)條目的函數(shù),所以需要手工去完成;這與 GSList 相同。
2
刪除和插入條目
雖然通常只通過(guò)在隊(duì)列的末端 添加/刪除 條目來(lái)修改它,但 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
有很多新函數(shù):
* g_queue_index 在隊(duì)列中掃描某個(gè)條目并返回其索引;如果它不能找到那個(gè)條目,則返回 -1。
* 為了向隊(duì)列的中間插入一個(gè)新條目,需要一個(gè)指向希望插入位置的指針。如您所見(jiàn),通過(guò)調(diào)用一個(gè)“peek link”函數(shù),
就可以進(jìn)行此處理; 這些函數(shù)包括:g_queue_peek_tail_link、g_queue_peek_head_link 以及 g_queue_peek_nth_link,它們會(huì)返回一個(gè) GList。
然后可以將一個(gè)條目插入到 GList 之前或者之后。
* g_queue_remove 允許從隊(duì)列中的任何位置刪除某個(gè)條目。繼續(xù)使用“排隊(duì)買票”模型,這表示人們可以離開(kāi)隊(duì)列; 他們組成隊(duì)列后并不固定在其中。
3
查找條目
在先前的示例中已經(jīng)看到,在擁有一個(gè)指向條目數(shù)據(jù)的指針或者知道其索引的條件下如何去得到它。
不過(guò),類似其他 GLib 容器, GQueue 也包括一些查找函數(shù):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 找不到條目,則它會(huì)返回 null。并且可以在上面的示例中傳遞一個(gè)庫(kù)函數(shù)(比如 g_ascii_strcasecmp)
或者一個(gè)定制的函數(shù)(比如 finder)作為 g_queue_find_custom 的 GCompareFunc 參數(shù)。
4
使用隊(duì)列:拷貝、反轉(zhuǎn)和遍歷每一個(gè)(foreach) ?需要調(diào)試
由于 GQueue 的基礎(chǔ)是 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 很直觀; 您已經(jīng)看到它們?cè)诟鞣N其他有序集合中得到了應(yīng)用。
不過(guò),使用 g_queue_copy 時(shí)需要稍加留心, 因?yàn)榭截惖氖侵羔樁皇菙?shù)據(jù)。所以,當(dāng)釋放數(shù)據(jù)時(shí),一定不要進(jìn)行重復(fù)釋放。
5
使用鏈接的更多樂(lè)趣
已經(jīng)了解了鏈接的一些示例;這里是一些便利的鏈接刪除函數(shù)。不要忘記 GQueue 中的每個(gè)條目實(shí)際上是都是一個(gè) GList 結(jié)構(gòu)體
, 數(shù)據(jù)存儲(chǔ)在“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 并不釋放沒(méi)有被鏈接的 GList 結(jié)構(gòu)體,所以需要自己去完成。 并且,由于它是一個(gè) GList 結(jié)構(gòu)體,
所以需要使用 g_list_free 函數(shù)來(lái)釋放它 —— 而不是 簡(jiǎn)單的 g_free 函數(shù)。當(dāng)然,更簡(jiǎn)單的是調(diào)用 g_queue_delete_link 并讓它為您釋放內(nèi)存。
6
排序
隊(duì)列排序好像不太常見(jiàn),不過(guò)由于各種其他鏈表操作都得到了支持(比如 insert 和 remove),所以此操作也得到了支持。
如果恰巧您希望重新對(duì)隊(duì)列進(jìn)行排序,將高優(yōu)先級(jí) 的條目移動(dòng)到前端,那么這也會(huì)很便利。這里是一個(gè)示例:
#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
現(xiàn)在您就擁有了一個(gè)模擬您的工作的 GQueue,偶爾還可以對(duì)它進(jìn)行排序,可以欣喜地發(fā)現(xiàn),Nethack 被提升到了其正確的位置,到了隊(duì)列的 最前端!
實(shí)際應(yīng)用
GQueue 沒(méi)有在 Evolution 中得到應(yīng)用,但是 GIMP 和 Gaim 用到了它。
GIMP:
* gimp-2.2.4/app/core/gimpimage-contiguous-region.c 在一個(gè)查找相鄰片段的工具函數(shù)中使用 GQueue 存儲(chǔ)一系列坐標(biāo)。
只要片段保存鄰接,新的點(diǎn)就會(huì)被壓入到隊(duì)列末端,然后在下一個(gè)循環(huán)迭代中取出并被檢查。
* gimp-2.2.4/app/vectors/gimpvectors-import.c 使用 GQueue 作為 Scalable Vector Graphics(SVG)解析器的一部分。
它被當(dāng)做棧使用,條目的壓入和取出都在隊(duì)列的頭上進(jìn)行。
Gaim:
* gaim-1.2.1/src/protocols/msn/switchboard.c 使用 GQueue 來(lái)追蹤發(fā)出的消息。新的消息壓入到隊(duì)列的尾部,當(dāng)發(fā)送后從頭部取出。
* gaim-1.2.1/src/proxy.c 使用 GQueue 追蹤 DNS 查找請(qǐng)求。它使用隊(duì)列作為應(yīng)用程序代碼與 DNS 子進(jìn)程之間的臨時(shí)保存區(qū)域。
總結(jié)
- 上一篇: FileSystemObject和Fol
- 下一篇: 会员按天统计、日分时统计