现在的位置: 首页 > 综合 > 正文

GLIB 常用数据结构介绍 (3)

2012年08月22日 ⁄ 综合 ⁄ 共 15977字 ⁄ 字号 评论关闭

数组

概念

到目前为止我们已经介绍了两类有序集合:GSList 和 GList。它们非常相似,因为都依赖于指针来从一个元素链接到下一个条目,或者,在 GList 中,链接到前一个条目。不过,有另外一类不使用链接的有序集合;它的功能与 C 数组多少有些类似。

它叫做 GArray,提供一个具备索引的单一类型的有序集合,能够为了容纳新条目而增加大小。

相对于链表,数组有什么优势?一方面,索引访问。也就是说,如果想获得数组中的第十五个元素,只需要调用一个能够在常数时间内获取它的函数;不需要手工地遍历到那个位置,那将是一个 O(n) 操作。数组知道自己的大小,所以查询其大小是一个 O(1) 操作而不是 O(n) 操作。

基本操作

这里是向数组添加和删除数据的一些主要方法:

//ex-garray-1.c
#include <glib.h>
int main(int argc, char** argv) {
 GArray* a = g_array_new(FALSE, FALSE, sizeof(char*));
 char* first = "hello", *second = "there", *third = "world";
 g_array_append_val(a, first);
 g_array_append_val(a, second);
 g_array_append_val(a, third);
 g_printf("There are now %d items in the array/n", a->len);
 g_printf("The first item is '%s'/n", g_array_index(a, char*, 0));
 g_printf("The third item is '%s'/n", g_array_index(a, char*, 2));
 g_array_remove_index(a, 1);
 g_printf("There are now %d items in the array/n", a->len);
 g_array_free(a, FALSE);
 return 0;
}

***** Output *****

There are now 3 items in the array
The first item is 'hello'
The third item is 'world'
There are now 2 items in the array

需要考虑的几点:

    * 在创建一个 GArray 时需要考虑一些选项。在上面的示例中,g_array_new 的前两个参数表明了数组是否要以零元素作为终止符,是否数组中的新元素自动设置为零。第三个参数告诉数组它将要保存哪种类型的数据。在这个示例中要创建一个保存 char* 类型数据的数组;在数组中放入任何其他东西都会导致段错误(segfaults)。
    * g_array_append_val 是一个设计不能接受字面值(literal value)的宏,所以不能调用 g_array_append_val(a, 42)。而是那个值需要放入一个变量吕,然后将那个变量传递到 g_array_append_val 中。作为对这种不方便之处的一种慰藉,g_array_append_val 的速度非常快。
    * GArray 是具有一个成员变量 len 的结构体,所以为了获得数组的大小,只需要直接引用那个变量;不需要函数调用。
    * GArray 的大小以二幂次系数增长。也就是说,如果某个 GArray 包含四个条目,而且您又增加了另一个,那么在内部它会创建另一个拥有八个元素的 GArray,将四个现有元素拷贝到它,然后添加新的元素。这个改变大小的过程需要时间,所以,如果知道将要有大量的元素,那么创建 GArray 时预分配期望的大小会更有效。

更多 new/free 选项

本示例中包含创建和销毁 GArray 的一些不同方法:

//ex-garray-2.c
#include <glib.h>
int main(int argc, char** argv) {
 GArray* a = g_array_sized_new(TRUE, TRUE, sizeof(int), 16);
 g_printf("Array preallocation is hidden, so array size == %d/n", a->len);
 g_printf("Array was init'd to zeros, so 3rd item is = %d/n", g_array_index(a, int, 2));
 g_array_free(a, FALSE);

 // this creates an empty array, then resizes it to 16 elements
 a = g_array_new(FALSE, FALSE, sizeof(char));
 g_array_set_size(a, 16);
 g_array_free(a, FALSE);

 a = g_array_new(FALSE, FALSE, sizeof(char));
 char* x = g_strdup("hello world");
 g_array_append_val(a, x);
 g_array_free(a, TRUE);

 return 0;
}

***** Output *****

Array preallocation is hidden, so array size == 0
Array was init'd to zeros, so 3rd item is = 0

注意,由于 GArray 以二幂次系数增长,所有,将数组的大小设置为接近二的某次幂(比如十四)的值会令效率较低。不要那样,而是直接使用最接近的二的某次幂。

添加数据的更多方法

到目前为止您已经看到如何使用 g_array_append_val 将数据添加到数组。不过,有其他的方式可以将数据置入数组,如下所示:

//ex-garray-3.c
#include <glib.h>
void prt(GArray* a) {
 g_printf("Array holds: ");
 int i;
 for (i = 0; i < a->len; i++)
  g_printf("%d ", g_array_index(a, int, i));
 g_printf("/n");
}
int main(int argc, char** argv) {
 GArray* a = g_array_new(FALSE, FALSE, sizeof(int));
 g_printf("Array is empty, so appending some values/n");
 int x[2] = {4,5};
 g_array_append_vals(a, &x, 2);
 prt(a);
 g_printf("Now to prepend some values/n");
 int y[2] = {2,3};
 g_array_prepend_vals(a, &y, 2);
 prt(a);
 g_printf("And one more prepend/n");
 int z = 1;
 g_array_prepend_val(a, z);
 prt(a);
 g_array_free(a, FALSE);
 return 0;
}

***** Output *****

Array is empty, so appending some values
Array holds: 4 5
Now to prepend some values
Array holds: 2 3 4 5
And one more prepend
Array holds: 1 2 3 4 5

可以将多个值附加到数组,可以在最前添加一个值,也可以在最前添加多个值。不过,在向最前添加值的时候要小心;它是一个 O(n) 操作,因为 GArray 必须要将当前所有值向后移动,为新的数据腾出空间。在附加或者向最前添加多个值时,还需要使用变量,不过这是相当直观的,因为可以附加或者向最前添加整个数组。

插入数据

也可以向数组中各个不同的位置插入数据;不是限于只能附加或者向最前添加条目。这里是其工作方式:

//ex-garray-4.c
#include <glib.h>
void prt(GArray* a) {
 g_printf("Array holds: ");
 int i;
 for (i = 0; i < a->len; i++)
  g_printf("%d ", g_array_index(a, int, i));
 g_printf("/n");
}
int main(int argc, char** argv) {
 GArray* a = g_array_new(FALSE, FALSE, sizeof(int));
 int x[2] = {1,5};
 g_array_append_vals(a, &x, 2);
 prt(a);
 g_printf("Inserting a '2'/n");
 int b = 2;
 g_array_insert_val(a, 1, b);
 prt(a);
 g_printf("Inserting multiple values/n");
 int y[2] = {3,4};
 g_array_insert_vals(a, 2, y, 2);
 prt(a);
 g_array_free(a, FALSE);
 return 0;
}

***** Output *****

Array holds: 1 5
Inserting a '2'
Array holds: 1 2 5
Inserting multiple values
Array holds: 1 2 3 4 5

注意,这些插入函数涉及到了向后拷贝列表中当前元素,目的是为新条目准备空间,所以使用 g_array_insert_vals 比反复使用 g_array_insert_val 更好。

删除数据

有三种方法可以从 GArray 删除数据:

    * g_array_remove_index 和 g_array_remove_range,这两个函数会保持现有顺序
    * g_array_remove_index_fast,不保持现有顺序

这里是所有三种方法的示例:

//ex-garray-5.c
#include <glib.h>
void prt(GArray* a) {
 int i;
 g_printf("Array holds: ");
 for (i = 0; i < a->len; i++)
  g_printf("%d ", g_array_index(a, int, i));
 g_printf("/n");
}
int main(int argc, char** argv) {
 GArray* a = g_array_new(FALSE, FALSE, sizeof(int));
 int x[6] = {1,2,3,4,5,6};
 g_array_append_vals(a, &x, 6);
 prt(a);
 g_printf("Removing the first item/n");
 g_array_remove_index(a, 0);
 prt(a);
 g_printf("Removing the first two items/n");
 g_array_remove_range(a, 0, 2);
 prt(a);
 g_printf("Removing the first item very quickly/n");
 g_array_remove_index_fast(a, 0);
 prt(a);
 g_array_free(a, FALSE);
 return 0;
}

***** Output *****

Array holds: 1 2 3 4 5 6
Removing the first item
Array holds: 2 3 4 5 6
Removing the first two items
Array holds: 4 5 6
Removing the first item very quickly
Array holds: 6 5

不只是您可能会对 g_array_remove_fast 的使用情形感到奇怪;三个开放源代码的应用程序都没有使用这个函数。

排序

对 GArray 排序很直观;它使用的是在 GList 和 GSList 部分已经出现过的 GCompareFunc:

//ex-garray-6.c
#include <glib.h>
void prt(GArray* a) {
 int i;
 g_printf("Array holds: ");
 for (i = 0; i < a->len; i++)
  g_printf("%d ", g_array_index(a, int, i));
 g_printf("/n");
}
int compare_ints(gpointer a, gpointer b) {
 int* x = (int*)a;
 int* y = (int*)b;
 return *x - *y;
}
int main(int argc, char** argv) {
 GArray* a = g_array_new(FALSE, FALSE, sizeof(int));
 int x[6] = {2,1,6,5,4,3};
 g_array_append_vals(a, &x, 6);
 prt(a);
 g_printf("Sorting/n");
 g_array_sort(a, (GCompareFunc)compare_ints);
 prt(a);
 g_array_free(a, FALSE);
 return 0;
}

***** Output *****

Array holds: 2 1 6 5 4 3
Sorting
Array holds: 1 2 3 4 5 6

注意,比较函数会得到指向数据条目的一个指针,所以在这种情况下,需要将它们强制类型转换为指向正确类型的指针,然后反引用那个指针,得到实际的数据条目。GArray 还包括另一个排序函数,即 g_array_sort_with_data,它会接受指向另外一段数据的一个指针。

顺便提一句,这三个示例应用程序都没有使用 g_array_sort 和 g_array_sort_with_data。不过,无论如何,知道它们是可用的,都会有所帮助。

指针数组

GLib 还提供了 GPtrArray,这是一个为保存指针专门设计的数组。使用它比使用基本的 GArray 更简单,因为在创建或者添加、索引元素时不需要指定具体类型。它与 GArray 非常类似,所以我们将只是回顾基本操作的一些示例:

//ex-garray-7.c
#include <glib.h>
#include <stdio.h>
int main(int argc, char** argv) {
 GPtrArray* a = g_ptr_array_new();
 g_ptr_array_add(a, g_strdup("hello "));
 g_ptr_array_add(a, g_strdup("again "));
 g_ptr_array_add(a, g_strdup("there "));
 g_ptr_array_add(a, g_strdup("world "));
 g_ptr_array_add(a, g_strdup("/n"));
 g_printf(">Here are the GPtrArray contents/n");
 g_ptr_array_foreach(a, (GFunc)g_printf, NULL);
 g_printf(">Removing the third item/n");
 g_ptr_array_remove_index(a, 2);
 g_ptr_array_foreach(a, (GFunc)g_printf, NULL);
 g_printf(">Removing the second and third item/n");
 g_ptr_array_remove_range(a, 1, 2);
 g_ptr_array_foreach(a, (GFunc)g_printf, NULL);
 g_printf("The first item is '%s'/n", g_ptr_array_index(a, 0));
 g_ptr_array_free(a, TRUE);
 return 0;
}

***** Output *****

>Here are the GPtrArray contents
hello again there world
>Removing the third item
hello again world
>Removing the second and third item
hello
The first item is 'hello '

可以了解如何使用只支持指针的数组编写更为直观的 API。这可能能够解释为什么在 Evolution 中使用 g_ptr_array_new 的次数为 178 次,而 g_array_new 只使用了 45 次。大部分情况下只支持指针的数组就足够了!

字节数组

GLib 提供了另一个特定类型的数组是 GByteArray。它与您已经了解的类型非常类似,不过有一些区别,因为它是为存储二进制数据而设计的。它非常便于在循环中读取二进制数据,因为它隐藏了“read into a buffer-resize buffer-read some more”的周期。这里是一些示例代码:

//ex-garray-8.c
#include <glib.h>
int main(int argc, char** argv) {
 GByteArray* a = g_byte_array_new();
 guint8 x = 0xFF;
 g_byte_array_append(a, &x, sizeof(x));
 g_printf("The first byte value (in decimal) is %d/n", a->data[0]);
 x = 0x01;
 g_byte_array_prepend(a, &x, sizeof(x));
 g_printf("After prepending, the first value is %d/n", a->data[0]);
 g_byte_array_remove_index(a, 0);
 g_printf("After removal, the first value is again %d/n", a->data[0]);
 g_byte_array_append(g_byte_array_append(a, &x, sizeof(x)), &x, sizeof(x));
 g_printf("After two appends, array length is %d/n", a->len);
 g_byte_array_free(a, TRUE);
 return 0;
}

***** Output *****

The first byte value (in decimal) is 255
After prepending, the first value is 1
After removal, the first value is again 255
After two appends, array length is 3

还可以发现,在这里使用了一个新的 GLib 类型:guint8。这是跨平台的 8-位 无符号整数,有益于在本例中准确描述字节。

另外,在这里可以了解到 g_byte_array_append 如何返回 GByteArray。所以,这就使得可以像编写方法链(method chaining)那样将一些附加动作嵌套起来。不过,嵌套多于两个或三个可能并不是一个好办法,除非想让代码变更得 LISP 那样。

实际应用

在示例应用程序中使用了各种不同的 GLib 数组类型,虽然不如已经接触的其他容器那样广泛。

Gaim 只使用了 GPtrArrays,而且只在一两种情形下使用到了。gaim-1.2.1/src/gtkpounce.c 使用一个 GPtrArray 来保持对一些 GUI 窗口小部件的追踪,在发生各种事件(比如好友登录进来)时它们会被触发。

Evolution 所使用的大部分是 GPtrArrays,不过也使用了很多 GArrays 和 GByteArrays:

    * evolution-2.0.2/widgets/misc/e-filter-bar.h 在 GPtrArrays 中保持一些搜索过滤器的类型。
    * evolution-2.0.2/camel/providers/imap4/camel-imap4-store.c 使用 GPtrArray 来追踪 IMAP 文件夹中的条目;它使用 g_ptr_array_sort 以及一个委派给 strcmp 的 GCompareFunc。

GIMP 使用了相当多的 GArray,只使用了很少 GPtrArrays 和 GByteArrays:

    * gimp-2.2.4/app/tools/gimptransformtool.c 使用 GArray 来追踪 GimpCoord 实例列表。
    * gimp-2.2.4/app/base/boundary.c 中,由点(points)填充起来的 GArray 是极好的 simplify_subdivide 函数的一部分;递归传递的指向 GArray 的双重间接指针是边界简化(boundary simplification)例程的一部分。
    *

      树

      概念

      树 是另一个实用的容器。树中有一个可以拥有子节点的根节点,每个子节点可以有更多子节点,依此类推。

      树结构体的示例包括文件系统或者电子邮件客户机;它其中有包含文件夹的文件夹,文件夹中可以有更多文件夹。另外,是否还记得哈希表部分的最后出现的多值示例?(例如,以字符串作为键,GList 作为值。)由于那些 GLish 值可以容纳更多 GHashTables,那就成为 GHashTable 中构造树结构体的示例。相对于付出努力想使用树一样使用其他容器,使用 GTree 简单得多。

      GLib 包括两种树结构:GTree(平衡二叉树 实现)以及 GNode(n-叉 树实现)。

      二叉树的特殊属性是,树中每个节点拥有不超过两个子节点;平衡二叉树 表示元素以特定的次序保持,以进行更快速地搜索。保持元素的平衡意味着删除和插入可能会较慢,因为树本身可能需要进行内部重新平衡,不过寻找某个条目是 O(log n) 操作。
      相反,n-叉 树 节点可以有很多子节点。本教程主要关注二叉树,不过也会有一些 n-叉 树的示例。
中序遍历::左子树->根->右子树 eg::DBGEACHFI
前序遍历::根->左子树->右子树 eg::ABDEGCFHI
前序遍历::左子树->右子树 ->根eg::GEBHIFCA

      树的基本操作

      这里是在树中可以执行的一些基本操作:

      //ex-gtree-1.c
      #include <glib.h>
      int main(int argc, char** argv) {
       GTree* t = g_tree_new((GCompareFunc)g_ascii_strcasecmp);
       g_tree_insert(t, "c", "Chicago");
       g_printf("The tree height is %d because there's only one node/n", g_tree_height(t));
       g_tree_insert(t, "b", "Boston");
       g_tree_insert(t, "d", "Detroit");
       g_printf("Height is %d since c is root; b and d are children/n", g_tree_height(t));
       g_printf("There are %d nodes in the tree/n", g_tree_nnodes(t));
       g_tree_remove(t, "d");
       g_printf("After remove(), there are %d nodes in the tree/n", g_tree_nnodes(t));
       g_tree_destroy(t);
       return 0;
      }

      ***** Output *****

      The tree height is 1 because there's only one node
      Height is 2 since c is root; b and d are children
      There are 3 nodes in the tree
      After remove(), there are 2 nodes in the tree

      关于代码的一些注解:
    * 可以看到,GTree 中的每一个节点都包含一个 键-值 对。键用来确保树的平衡、节点插入到适当的位置,并确保值的指针指向应该追踪的 “payload”。
    * 必须为 g_tree_new 提供一个 GCompareFunc,以使得 GTree 知道如何对键进行比较。这可以是一个内置函数,如上所示,或者可以自己编写。
    * 树“高度(height)”只是从顶到底(包括顶和底)节点的数目。要执行这个函数,GTree 必须从它的根开始向下移动直到到达某个叶子节点。 g_tree_nnodes 更为复杂;它要完全遍历整棵树。

替换和提取

在前面的 GHashTable 部分已经看到了 replace 和 steal 函数名,关于 GTree 的函数也是如此。g_tree_replace 会同时替换一个 GTree 条目的键和值,不同于 g_tree_insert,如果要插入的键是重复的,则它只是将值替换。不需要调用任何 GDestroyNotify 函数,g_tree_steal 就可以删除一个节点。这里是一个示例:

//ex-gtree-2.c
#include <glib.h>
void key_d(gpointer data)
 {
 g_printf("Key %s destroyed/n", data);
}
void value_d(gpointer data)
{
 g_printf("Value %s destroyed/n", data);
}

int main(int argc, char** argv)
{
 GTree* t = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp,  NULL, (GDestroyNotify)key_d, (GDestroyNotify)value_d);
 
 g_tree_insert(t, "c", "Chicago");
 g_tree_insert(t, "b", "Boston");
 g_tree_insert(t, "d", "Detroit");
 
 g_printf(">Replacing 'b', should get destroy callbacks/n");
  g_tree_replace(t, "b", "Billings");
 
 g_printf(">Stealing 'b', no destroy notifications will occur/n");
 g_tree_steal(t, "b");
 g_printf(">Destroying entire tree now/n");
 g_tree_destroy(t);
 return 0;
}

***** Output *****

>Replacing 'b', should get destroy callbacks
Value Boston destroyed
Key b destroyed
>Stealing 'b', no destroy notifications will occur
>Destroying entire tree now
Key d destroyed
Value Detroit destroyed
Key c destroyed
Value Chicago destroyed

在这个示例中,使用 g_tree_new_full 创建了一个 GTree;与 GHashTable 类似,可以注册键或值的销毁的任意组合的通知。g_tree_new_full 的第二个参数可以包含传递给 GCompareFunc 的数据,不过在此并不需要。

查找数据

GTree 具备只查找键或者同时查找键和值的方法。这与在 GHashTable 部分中接触到的非常类似;有一个 lookup 以及一个 lookup_extended。这里是一个示例:

//ex-gtree-3.c
#include <glib.h>

int main(int argc, char** argv)
 {
 GTree* t = g_tree_new((GCompareFunc)g_ascii_strcasecmp);
 
 g_tree_insert(t, "c", "Chicago");
 g_tree_insert(t, "b", "Boston");
 g_tree_insert(t, "d", "Detroit");
 
 g_printf("The data at 'b' is %s/n", g_tree_lookup(t, "b"));
 g_printf("%s/n",  g_tree_lookup(t, "a") ? "My goodness!" : "As expected, couldn't find 'a'");

 gpointer* key = NULL;
 gpointer* value = NULL;
 g_tree_lookup_extended(t, "c", (gpointer*)&key, (gpointer*)&value);
 g_printf("The data at '%s' is %s/n", key, value);
 gboolean found = g_tree_lookup_extended(t, "a", (gpointer*)&key, (gpointer*)&value);
 g_printf("%s/n", found ? "My goodness!" : "As expected, couldn't find 'a'");

 g_tree_destroy(t);
 return 0;
}

***** Output *****

The data at 'b' is Boston
As expected, couldn't find 'a'
The data at 'c' is Chicago
As expected, couldn't find 'a'

这里再次用到了双重间接指针技术。由于 g_tree_lookup_extended 需要提供多个值,所以它接受两个指向指针的指针(一个指向键,另一个指向值)。注意,如果 g_tree_lookup 找不到键,则它返回一个 NULL gpointer,而如果 g_tree_lookup_extended 不能找到目标,则它返回一个 FALSE gboolean 值。

    

使用 foreach 列出树

GTree 提供了一个 g_tree_foreach 函数,用来以有序的顺序遍历整棵对。这里是一个示例:

//ex-gtree-4.c
#include <glib.h>
gboolean iter_all(gpointer key, gpointer value, gpointer data)
{
 g_printf("%s, %s/n", key, value);
 return FALSE;                                                //返回 FALSE,可以一点不差地遍历整棵树。返回TRUE时停止遍历。
}
gboolean iter_some(gpointer key, gpointer value, gpointer data)
 {
 g_printf("%s, %s/n", key, value);
 return g_ascii_strcasecmp(key, "b") == 0;
}
int main(int argc, char** argv)
 {
 GTree* t = g_tree_new((GCompareFunc)g_ascii_strcasecmp);
 
 g_tree_insert(t, "d", "Detroit");
 g_tree_insert(t, "a", "Atlanta");
 g_tree_insert(t, "c", "Chicago");
 g_tree_insert(t, "b", "Boston");
 
 g_printf("Iterating all nodes/n");
 g_tree_foreach(t, (GTraverseFunc)iter_all, NULL);
 
 g_printf("Iterating some of the nodes/n");
 g_tree_foreach(t, (GTraverseFunc)iter_some, NULL);
 
 g_tree_destroy(t);
 return 0;
}

***** Output *****

Iterating all nodes
a, Atlanta
b, Boston
c, Chicago
d, Detroit
Iterating some of the nodes
a, Atlanta
b, Boston

注意,当 iter_some 返回 TRUE 时,遍历停止。这就使得 g_tree_foreach 可用来搜索到某个点,累加匹配某个条件前 10 个条目,或者此类事情。当然,令 GTraverseFunc 返回 FALSE,可以一点不差地遍历整棵树。

另外,要注意在使用 g_tree_foreach 遍历树时不应该修改它。

有一个废弃的函数,即 g_tree_traverse,它本意是提供遍历树的另一种方法。例如,可以 后序(post order) 访问节点,也就是自底向上访问树。不过,这个函数从 2001 年起就废弃了,从而 GTree 文档建议在所有使用它的地方替换为使用 g_tree_foreach 或者 n-叉 树。这里所研究的开放源代码的应用程序都没有使用它,很不错。

搜索

可以使用 g_tree_foreach 搜索条目,如果知道键,可以使用 g_tree_lookup。不过,要进行更复杂地搜索,可以使用 g_tree_search 函数。这里是其工作方式:

//ex-gtree-5.c
#include <glib.h>
gint finder(gpointer key, gpointer user_data)
 {
int len = strlen((char*)key);
 if (len == 3) {
  return 0;
 }
 return (len < 3) ? 1 : -1;
}
int main(int argc, char** argv) {
 GTree* t = g_tree_new((GCompareFunc)g_ascii_strcasecmp);
 g_tree_insert(t, "dddd", "Detroit");
 g_tree_insert(t, "a", "Annandale");
 g_tree_insert(t, "ccc", "Cleveland");
 g_tree_insert(t, "bb", "Boston");
 gpointer value = g_tree_search(t, (GCompareFunc)finder, NULL);
 g_printf("Located value %s; its key is 3 characters long/n", value);
 g_tree_destroy(t);
 return 0;
}

***** Output *****

Located value Cleveland; its key is 3 characters long

注意,传递到 g_tree_search 的 GCompareFunc 实际上决定了搜索如何进行,根据搜索的方式返回 0、1 或者 -1。这个函数甚至可以在搜索进行的时候改变条件;Evolution 在使用 g_tree_search 管理其内存使用时就这样做了。

    

不只是二叉:n-叉 树

GLib n-叉 树实现基于 GNode 数据结构;以前所述,它允许每个父节点有多个子节点。好像很少会用到它,不过,完整起见,这里给出一个用法示例:

//ex-gtree-6.c
#include <glib.h>
gboolean iter(GNode* n, gpointer data) {
 g_printf("%s ", n->data);
 return FALSE;
}
int main(int argc, char** argv)
{
 GNode* root = g_node_new("Atlanta");
 g_node_append(root, g_node_new("Detroit"));
 GNode* portland = g_node_prepend(root, g_node_new("Portland"));
 
 g_printf(">Some cities to start with/n");
 g_node_traverse(root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, iter, NULL);
 g_printf("/n>Inserting Coos Bay before Portland/n");
 g_node_insert_data_before(root, portland, "Coos Bay");
 g_node_traverse(root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, iter, NULL);
 g_printf("/n>Reversing the child nodes/n");
 g_node_reverse_children(root);
 g_node_traverse(root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, iter, NULL);
 g_printf("/n>Root node is %s/n", g_node_get_root(portland)->data);
 g_printf(">Portland node index is %d/n", g_node_child_index(root, "Portland"));
 g_node_destroy(root);
 return 0;
}

***** Output *****

>Some cities to start with
Atlanta Portland Detroit
>Inserting Coos Bay before Portland
Atlanta Coos Bay Portland Detroit
>Reversing the child nodes
Atlanta Detroit Portland Coos Bay
>Root node is Atlanta
>Portland node index is 1

可见,GNode 允许将节点放置几乎任何地方;可以在合适时访问它们。这非常灵活,不过它可能会过于灵活,以至于确定其实际使用情形有些困难。实际上,在此所研究的三个开放源代码的应用程序都没有使用它!

实际应用

GTree 是一个复杂的结构体,其应用不如我们前面研究其他容器那样广泛。Gaim 根本没有使用它。不过,GIMP 和 Evolution 用到了一些。

GIMP:

    * gimp-2.2.4/app/menus/plug-in-menus.c 使用 GTree 来保持插件菜单条目。它使用 g_tree_foreach 和一个定制的 GTraverseFunc 来遍历 GTree,向 GimpUIManager 添加插件程序。它使用标准的 C 程序库函数 strcmp 作为自己的 GCompareData 函数。
    * gimp-2.2.4/plug-ins/script-fu/script-fu-scripts.c 使用 GTree 来保存“script-fu”脚本。GTree 中的每一个值实际上都是由脚本构成的 GList。

Evolution 的 evolution-2.0.2/e-util/e-memory.c 使用 GTree 作为计算未使用内存块的算法的一部分。它使用了一个定制的 GCompareFunc,即 tree_compare,来对 _cleaninfo 结构体进行排序,这些结构体指向的是空闲的内存块。

抱歉!评论已关闭.