如何使用GLib 工具集管理C 数据
glib不是一个学院派的东西,也不是凭空想出来的,完全是在开发gtk+的过程中,慢慢总结和完善的结果。如果你是一个工作3年以上的C语言程序员,现在让你讲讲写程序的苦恼,你可能有很多话要说,但如果你有时间研究一下glib,你会发现,很多苦恼已不再成其为苦恼,glib里很多东西正是你期望已经久的。
gobject是glib的精粹,glib是用C实现的,但在很大程序是基于面向对象思想设计的,gobject是所有类的基类。signal在其中也是一大特,signal与操作系统中的signal并不一样,它是类似消息一样的东西,让消息在各个对象间传递,但尽量降低对象间的耦合。仔细读一下它的代码,唯一想说的话就是“绝!”。
动态数组、链表、哈希表等通用容器,在不同的公司,在不同的时期,在不同的情况下,我们每个人对每一种容器,可能都实现过N次以上。甚至在同一个项目里,出现几份链表的实现,也并非罕见。一直在抱怨,标准C中为什么没有类似于STL的标准容器,让全世界的程序员在数以万次的重复实现它们。不过,还算走运,有了glib,恶梦在此终结了。glib 提供了动态数组、单/双向链表、哈希表、多叉树、平衡二叉树、字符串等常用容器,完全是面向对象设计的,实现得非常精致。不用白不用,别客气了。
组织数据
GLib 的范畴
首先研究GLib 的范畴。
GLib 是一个提供了很多实用定义和函数的底层程序库,包括基本类型及其限定的定义、标准宏、类型转化、字节次序、内存分配、警告与断言、消息日志、计时器、字符工具、钩子函数、词法扫描器、模块的动态加载,以及自动的字符串补齐。
GLib 还定义了很多数据结构(以及它们相关的操作),包括:
内存块(Memory chunks)
双向链表(Doubly-linked lists)
单向链表(Singly-linked lists)
散列表(Hash tables)
字符串(Strings,可以动态增长)
字符块(String chunks,成组的字符串)
数组(Arrays,当增加元素时其大小能够增长)
平衡二叉树(Balanced binary trees)
N-叉树(N-ary trees)
Quarks(字符串和唯一整型标识符的双向关联)
有关键字的数据列表(Keyed data lists,通过字符串或者整型id 访问其数据元素的列表)关系(Relations)和元组(tuples)(可以由任意数目的域进行索引的数据表)
缓存
组织数据
GLib 的范畴
首先研究GLib 的范畴。
GLib 是一个提供了很多实用定义和函数的底层程序库,包括基本
类型及其限定的定义、标准宏、类型转化、字节次序、内存分配、警告与断言、消息日志、计时器、字符工具、钩子函数、词法扫描器、模块的动态加载,以及自动的字符串补齐。GLib 还定义了很多数据结
构(以及它们相关的操作),包括:
内存块(Memory chunks)
双向链表(Doubly-linked lists)
单向链表(Singly-linked lists)
散列表(Hash tables)
字符串(Strings,可以动态增长)
字符块(String chunks,成组的字符串)
enum类型如何使用数组(Arrays,当增加元素时其大小能够增长)
平衡二叉树(Balanced binary trees)
N-叉树(N-ary trees)
Quarks(字符串和唯一整型标识符的双向关联)
有关键字的数据列表(Keyed data lists,通过字符串或者整型id 访问其数据元素的列表)关系(Relations)和元组(tuples)(可以由任意数目的域进行索引的数据表)
缓存
每个程序都必须管理数据
编写程序是为了处理数据。程序可能会从文件中读出一个名字列表、通过一个图形用户界面向用户询问某些数据,或者从一个外部的硬件设备加载数据。但数据一旦到了程序中,就需要保持对它的追踪。用来管理数据的函数和变量称为数据结构或容器。
如果使用 C 编写代码,那么您会发现它极其缺乏复杂的数据结构。当然,有很多存储数据的简单方法:
基本类型—— int、float、char 等等。
枚举(enum),可以保存整数的一系列符号名称。
数组(array),这是C 中最灵活的数据结构。
数组可以保存基本类型的数据,或者一系列任意类型的数据,或者指向任意类型数据的指针。但是数组也有很多局限性。它们的大小不能改变,所以,如果为一个拥有十个元素的数组分配了内存,却发现需要向其中放置十一个元素,那么就得创建一个新数组,将旧的元素拷贝过来,然后添加并新的元素。如果要遍历数组中的每一个元素,那么,或者需要始终知道数组中有多少个元素,或者要确保在数组的末尾处有某种“数组结束”标记,以使得您能够知道何时停止。
通过使用链表和二叉树等标准容器,在 C 中保持对数据的追踪的问题已经多次得到解决。每一位刚开始学习计算机科学专业的学生都会使用一个数据结构类;教师肯定会布置一系列编写那些容器的实现的练习。在编写这些数据结构时,学生会意识到它们是多么难以处理;粗心的学生经常会犯诸如悬空指针(dangling pointer)和重复释放内存(free)的错误。编写单元测试会有很大帮助,但是,总的来说,为每个新程序重新编写相同的数据结构是一个费力不讨好的任务。
内置的数据结构
简言之就是,在什么地方内置数据结构会有所帮助。有一些语言会内置附带这些容器。C++ 包含标准模板库(Standard Template Library,STL),它有一个容器类工具集,比如列表、优先队列、集合(set)和映射。另外,这些容器是类型-安全(type-safe)的,也就是说,您
只能向创建的每一个容器对象中放入一种类型的元素。这就使得它们的使用更为安全,并避免了C 所要
求的很多冗长的强制类型转换。并且,STL 包含了很多遍历工具和排序工具,这样就使得容器的使用更为简单。
Java 编程环境也附带了一组容器类。The java.util 程序包包含ArrayList、HashMap、TreeSet 以及其他各种标准结构。它还包括有用于对数据进行一般排序、创建不变集合的工具,以及其他各种便利工具。
不过,C 并没有内置容器支持;您或者只能自己编写,或者使用其他人的数据结构程序库。幸运的是,GLib 是一个能够满足此需要的优秀的、免费的、开放源代码的程序库。它包含了大部分标准数据结构,以及在程序中有效处理数据的很多实用工具。它诞生于1996 年,从而经过了彻底地测试,并在发展过程中增加了很多实用功能。
100 字以内(或者更少)的算法分析
对容器的不同操作需要不同长度的时间。例如,在一个长的列表中,访问第一个元素要快于对同一列表进行排序。用来描述完成这些操作所需时间的符号称为O-notation。这个话题需要计算机科学专业的学生用一个学期的时间去研究,但是,简要地讲,O-notation 是对操作的最坏情况的分析。换句话说,它对完成某个操作所需要的最长时间进行度量。它被证明是度量数据结构操作的有效途径,因为经常会遇到最坏情况的操作(比如当您搜索某个列表而却没有到所查的条目时)。
接下来论述了一些O-notation 示例,可以将一副扑克牌在桌子上排列一行:
O(1) —— 选择第一张牌。O(1) 也称为“线性时间(constant time)”,因为不管在列表中有多少张牌,取得第一张牌的时间都是相同的。
O(n) —— 翻转每一张牌。O(n) 被称为“线性时间(linear time)”,因为随着牌的数目增加,完成操作所需时间线性增加。
O(n!) —— 创建一个全部扑克牌所有可能排列的列表。每向列表添加一张新牌,排列的数目都会阶乘式增加。
您会发现,在整个教程都提及关于不同数据结构操作的O-notation。了解特定数据结构上特定操作的开销,能够帮助您更明智地选择容器在,最优化应用程序的性能。
编译GLib 程序
如果在研究那些示例时编译并运行它们,那么您将通过本文学到更多。由于它们要使用GLib,所以编译器需要知道GLib 头文件和程序库在哪里,以使其能够解析GLib 定义的类型。这个简单的程序初始化一个双向链表并向其中添加一个字符串:
//ex-compile.c
#include <glib.h>
int main(int argc, char** argv) {
GList* list = NULL;
list = g_list_append(list, "Hello world!");
printf("The first item is '%s'\n", g_list_first(list)->data);
return 0;
}
可以通过调用GCC 来编译这个程序,如下:
$ gcc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -lglib-2.0 -o ex-compile ex-compile.c
运行它,查看期望的输出:
$ ./ex-compile
The first item is 'Hello world!'
$
不过,那是一个非常难用的GCC 调用。更简单的方式是让GCC 指向GLib 程序库。
使用pkg-config
手工指定程序库的位置容易出错而且很冗长,所以大部分现代的Linux 发行版本都附带了pkgconfig 工具来帮助简化此问题。可以使用pkgconfig 来编译上面的程序,如下:
$ gcc 'pkg-config --cflags --libs glib-2.0' -o ex-compile ex-compile.c
输出与先前相同:
$ ./ex-compile
The first item is 'Hello world!'
$
注意,现在不必再指定GLib 头文件的路径;pkgconfig 的--cflags 选项会完成此任务。使用--libs 选项指定的程序库也是如此。当然,没有任何神密之处;pkgconfig 只是通过一个配置文件读取出程序库和头
文件的位置。在Fedora Core 3 系统中,pkgconfig 文件位于/usr/lib/pkgconfig 目录下,glib-2.0.pc 文件类似如下:
$ cat /usr/lib/pkgconfig/glib-2.0.pc
prefix=/usr
exec_prefix=/usr
libdir=/usr/lib
includedir=/usr/include
glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums
Name: GLib
Description: C Utility Library
Version: 2.4.7
Libs: -L${libdir} -lglib-2.0
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include
这样,所有信息都由一个中间层隐藏起来。如果恰巧使用了一个不支持pkgconfig 的Linux 发行版本,那么随时可以重新直接为GCC 指定头文件和程序库。
GLib 的现实应用
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论