Pine Blog

GLIB用户指南-数组

1.概念

到目前为止我们已经介绍了两类有序集合:GSList 和 GList。它们非常相似,因为都依赖于指针来从一个元素链接到下一个条目,或者,在 GList 中,链接 到前一个条目。不过,有另外一类不使用链接的有序集合;它的功能与 C 数组多少有些类似。
它叫做 GArray,提供一个具备索引的单一类型的有序集合,能够为了容纳新条目而增加大小。
相对于链表,数组有什么优势?一方面,索引访问。也就是说,如果想获得数组中的第十五个元素,只需要调用一个能够在常数时间内获取它的函数; 不需要手工地遍历到那个位置,那将是一个 O(n) 操作。数组知道自己的大小,所以查询其大小是一个 O(1) 操作而不是 O(n) 操作。
glib

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)例程的一部分。

文章转载自:https://blog.csdn.net/l197803/article/details/138244885?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-138244885-blog-52932358.235%5Ev43%5Epc_blog_bottom_relevance_base2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-138244885-blog-52932358.235%5Ev43%5Epc_blog_bottom_relevance_base2&utm_relevant_index=5,如有问题请联系删除。

未经允许不得转载:Pine Blog » GLIB用户指南-数组

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Pine Blog
Anywhere, Anytime
E-mail:59054872@qq.com
苏ICP备15059480号-1