编译: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 <glib.h>

#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 <glib.h>

#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 <glib.h>

#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 <glib.h>

#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 <glib.h>

#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 子进程之间的临时保存区域。