1.概念
到目前为止我们已经介绍了两类有序集合:GSList 和 GList。它们非常相似,因为都依赖于指针来从一个元素链接到下一个条目,或者,在 GList 中,链接 到前一个条目。不过,有另外一类不使用链接的有序集合;它的功能与 C 数组多少有些类似。
它叫做 GArray,提供一个具备索引的单一类型的有序集合,能够为了容纳新条目而增加大小。
相对于链表,数组有什么优势?一方面,索引访问。也就是说,如果想获得数组中的第十五个元素,只需要调用一个能够在常数时间内获取它的函数; 不需要手工地遍历到那个位置,那将是一个 O(n) 操作。数组知道自己的大小,所以查询其大小是一个 O(1) 操作而不是 O(n) 操作。
2.基本操作
这里是向数组添加和删除数据的一些主要方法:
//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);
printf("There are now %d items in the array\n", a->len);
printf("The first item is '%s'\n", g_array_index(a, char*, 0));
printf("The third item is '%s'\n", g_array_index(a, char*, 2));
g_array_remove_index(a, 1);
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 时 预分配期望的大小会更有效。
3.更多 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);
printf("Array preallocation is hidden, so array size == %d\n", a->len);
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 以二幂次系数增长,所有,将数组的大小设置为接近二的某次幂(比如十四)的值会令效率较低。不要那样,而是 直接使用最接近的二的某次幂。
4.添加数据的更多方法
到目前为止您已经看到如何使用 g_array_append_val 将数据添加到数组。 不过,有其他的方式可以将数据置入数组,如下所示:
//ex-garray-3.c
#include <glib.h>
void prt(GArray* a) {
printf("Array holds: ");
int i;
for (i = 0; i < a->len; i++)
printf("%d ", g_array_index(a, int, i));
printf("\n");
}
int main(int argc, char** argv) {
GArray* a = g_array_new(FALSE, FALSE, sizeof(int));
printf("Array is empty, so appending some values\n");
int x[2] = {4,5};
g_array_append_vals(a, &x, 2);
prt(a);
printf("Now to prepend some values\n");
int y[2] = {2,3};
g_array_prepend_vals(a, &y, 2);
prt(a);
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 必须要将当前所有值向后移动,为新的数据腾出空间。在附加或者向最前添加多个值时,还 需要使用变量,不过这是相当直观的,因为可以附加或者向最前添加整个数组。
5.插入数据
也可以向数组中各个不同的位置插入数据;不是限于只能附加或者向最前添加条目。 这里是其工作方式:
//ex-garray-4.c
#include <glib.h>
void prt(GArray* a) {
printf("Array holds: ");
int i;
for (i = 0; i < a->len; i++)
printf("%d ", g_array_index(a, int, i));
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);
printf("Inserting a '2'\n");
int b = 2;
g_array_insert_val(a, 1, b);
prt(a);
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 更好。
6.删除数据
有三种方法可以从 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;
printf("Array holds: ");
for (i = 0; i < a->len; i++)
printf("%d ", g_array_index(a, int, i));
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);
printf("Removing the first item\n");
g_array_remove_index(a, 0);
prt(a);
printf("Removing the first two items\n");
g_array_remove_range(a, 0, 2);
prt(a);
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 的使用情形感到奇怪; 三个开放源代码的应用程序都没有使用这个函数。
7.排序
对 GArray 排序很直观;它使用的是在 GList 和 GSList 部分已经出现过的 GCompareFunc:
//ex-garray-6.c
#include <glib.h>
void prt(GArray* a) {
int i;
printf("Array holds: ");
for (i = 0; i < a->len; i++)
printf("%d ", g_array_index(a, int, i));
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);
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。 不过,无论如何,知道它们是可用的,都会有所帮助。
8.指针数组
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"));
printf(">Here are the GPtrArray contents\n");
g_ptr_array_foreach(a, (GFunc)printf, NULL);
printf(">Removing the third item\n");
g_ptr_array_remove_index(a, 2);
g_ptr_array_foreach(a, (GFunc)printf, NULL);
printf(">Removing the second and third item\n");
g_ptr_array_remove_range(a, 1, 2);
g_ptr_array_foreach(a, (GFunc)printf, NULL);
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 次。大部分情况下只支持指针的数组就足够了!
9.字节数组
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));
printf("The first byte value (in decimal) is %d\n", a->data[0]);
x = 0x01;
g_byte_array_prepend(a, &x, sizeof(x));
printf("After prepending, the first value is %d\n", a->data[0]);
g_byte_array_remove_index(a, 0);
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));
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 那样。
10.实际应用
在示例应用程序中使用了各种不同的 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)例程的一部分。