温故而知新
也许很多人将孔子所说的“温故而知新”理解为:温习学过的知识,从中获得新的领悟。但是,我更倾向于另一种解释:温习学过的知识,继续学习新的知识。本文借助一个更加平易和现实的示例用以温习文档 [1-3] 中的 GObject 知识,并进一步学习类与对象的方法。
Bibtex 文献数据格式
使用 TeX 写论文,免不了要用 Bibtex 处理参考文献,像 google scholar 这样的网站,直接提供 bibtex 格式的文献数据,比如 Knuth 所写的《The TeXbook》这本书,其 Bibtex 文献数据格式如下:
@book {texbook, title={{The {\TeX}book}}, author={Knuth, D. E.}, year={1984}, publisher={Addison-Wesley Professional} }
有了 Bibtex 文献数据,在 TeX 源文档中便可以进行文献引用了,例如:
Knuth 写的《The {\TeX}book》\cite[texbook]这本书非常有趣!
TeX 源文档的排版结果大致像下面这个样子:
本文不关心 TeX 是如何处理参考文献的,只关注 Bibtex 文献数据格式。
Bibtex 文献数据格式非常简单,可描述如下:
@文献类型 {引用名 标识1 = {字符串1}, 标识2 = {字符串2}, ... = { ... }, 标示 n = {字符串 n} }
Bibtex 文献类的设计,代号Kill Bill
这个才是本文的主题。我们将此次行动(或者称为项目也可)命名为“Kill Bill”,缩写“Kb”,其中并无深意,只是因为我正在看这部电影。
1. 类的声明
建立 kb-bibtex.h 头文件,以 GObject 类作为基类,声明 KbBibtex 类:
#ifndef KB_BIBTEX_H #define KB_BIBTEX_H #include <glib-object.h> #define KB_TYPE_BIBTEX (kb_bibtex_get_type ()) #define KB_BIBTEX(object) \ G_TYPE_CHECK_INSTANCE_CAST ((object), KB_TYPE_BIBTEX, KbBibtex) typedef struct _KbBibtex KbBibtex; struct _KbBibtex { GObject parent; }; typedef struct _KbBibtexClass KbBibtexClass; struct _KbBibtexClass { GObjectClass parent_class; }; GType kb_bibtex_get_type (void); #endif
2. 类型注册
建立 kb-bibtex.c 源文件,对 KbBibtex 类进行定义,首先向 GObject 库的类型系统注册 KbBibtex 类型,使之成为 GObject 库认可的一类公民:
#include "kb-bibtex.h" G_DEFINE_TYPE (KbBibtex, kb_bibtex, G_TYPE_OBJECT);
3. 对象的私有属性
在此,必须要澄清两个概念,它们在文档 [1-3] 中的描述有些混淆:
- 首先是对象属性,它指代隶属于实例结构体的数据,文档 [2] 主要讲述的是对象属性的隐藏。
- 其次是类属性,它指代隶属于类结构体的数据,可被所有的对象所共享,在文档 [3] 中我们利用了这一点,实现了通过类属性来访问对象属性。
下面定义 KbBibtex 对象的私有属性:
#define KB_BIBTEX_GET_PRIVATE(object) (\ G_TYPE_INSTANCE_GET_PRIVATE ((object), KB_TYPE_BIBTEX, KbBibtexPrivate)) typedef struct _KbBibtexPrivate KbBibtexPrivate; struct _KbBibtexPrivate { GString *title; GString *author; GString *publisher; guint year; };
4. 类结构体与实例结构体的构造函数(初始化函数)
在 KbBibtex 类结构体构造函数中安装对象的私有属性:
static void kb_bibtex_class_init (KbBibtexClass *klass) { g_type_class_add_private (klass, sizeof (KbBibtexPrivate)); }
kb_bibtex_class_init 参数名之所以使用 klass 而不是 class,是因为 class 是 c++ 语言的关键字,如果使用 class,那么 KbBibtex 类如是被 C++ 程序调用,那么程序编译时就杯具了。
KbBibtex 实例结构体的构造函数则什么也不做:
static void kb_bibtex_init (KbBibtex *self) { }
因为我们打算采用文档 [3] 中的方式,通过类属性来访问对象属性。
5. 对象私有属性的外部访问
首先要实现类属性与对象属性之间的映射,即 kb_bibtex_set_property 与 kb_bibtex_get_property 函数:
static void kb_bibtex_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { KbBibtex *self = KB_BIBTEX (object); KbBibtexPrivate *priv = KB_BIBTEX_GET_PRIVATE (self); switch (property_id) { case PROPERTY_TITLE: if (priv->title) g_string_free (priv->title, TRUE); priv->title = g_string_new (g_value_get_string (value)); break; case PROPERTY_AUTHOR: if (priv->author) g_string_free (priv->author, TRUE); priv->author = g_string_new (g_value_get_string (value)); break; case PROPERTY_PUBLISHER: if (priv->publisher) g_string_free (priv->publisher, TRUE); priv->publisher = g_string_new (g_value_get_string (value)); break; case PROPERTY_YEAR: priv->year = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void kb_bibtex_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { KbBibtex *self = KB_BIBTEX (object); KbBibtexPrivate *priv = KB_BIBTEX_GET_PRIVATE (self); GString *similar = NULL; switch (property_id) { case PROPERTY_TITLE: g_value_set_string (value, priv->title->str); break; case PROPERTY_AUTHOR: g_value_set_string (value, priv->author->str); break; case PROPERTY_PUBLISHER: g_value_set_string (value, priv->publisher->str); break; case PROPERTY_YEAR: g_value_set_uint (value, priv->year); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } }
为了是上述代码能够工作,需要在 kb-bibtex.c 文件中定义 KbBibtex 各个类属性的 ID:
enum PROPERTY_BIBTEX { PROPERTY_0, PROPERTY_TITLE, PROPERTY_AUTHOR, PROPERTY_PUBLISHER, PROPERTY_YEAR, N_PROPERTIES };
注意,enum 类型中,第一个成员 PROPERTY_0 仅仅是个占位符,它不会被使用,而最后一个成员 N_PROPERTIES 则用于表示类属性的个数,它在向类中安装属性的时候可以用到,详见下文。
6. 类属性的构造与安装
一旦完成了 KbBibtex 类属性与 KbBibtex 对象属性之间的映射,即 kb_bibtex_set_property 与 kb_bibtex_get_property 函数,那么便可以在 KbBibtex 类结构体构造函数中使用 GObject 类的 setter 与 getter 指针指向它们:
static void kb_bibtex_class_init (KbBibtexClass *klass) { g_type_class_add_private (klass, sizeof (KbBibtexPrivate)); GObjectClass *base_class = G_OBJECT_CLASS (klass); base_class->set_property = kb_bibtex_set_property; base_class->get_property = kb_bibtex_get_property; /* 未完 */
然后,构造类属性,与文档 [3] 有所区别,本文采用的是 ParamSpec 类型的指针数组,一遍在类属性构造完毕后,使用 g_object_class_install_properties 函数一并安装到类结构体中,而不是像文档 [3] 那样使用 g_object_class_install_property 函数一个一个的安装。
类属性的构造代码如下:
/* 接上面的代码 */ GParamSpec *properties[N_PROPERTIES] = {NULL,}; properties[PROPERTY_TITLE] = g_param_spec_string ("title", "Title", "Bibliography title", NULL, G_PARAM_READWRITE); properties[PROPERTY_AUTHOR] = g_param_spec_string ("author", "Author", "Bibliography author", NULL, G_PARAM_READWRITE); properties[PROPERTY_PUBLISHER] = g_param_spec_string ("publisher", "Publisher", "Bibliography Publisher", NULL, G_PARAM_READWRITE); properties[PROPERTY_YEAR] = g_param_spec_uint ("year", "Year", "Bibliography year", 0, G_MAXUINT, 0, G_PARAM_READWRITE); /* 未完 */
最后,安装类属性:
/* 接上面代码 */ g_object_class_install_properties (base_class, N_PROPERTIES, properties); }
使用 KbBibtex 类
建立 main.c 源文件,内容为:
#include "kb-bibtex.h" int main (void) { g_type_init (); KbBibtex *entry = g_object_new (KB_TYPE_BIBTEX, "title", "The {\\TeX}Book", "author", "Knuth, D. E.", "publisher", "Addison-Wesley Professional", "year", 1984, NULL); gchar *title, *author, *publisher; guint year; g_object_get (G_OBJECT (entry), "title", &title, "author", &author, "publisher", &publisher, "year", &year, NULL); g_printf (" Title: %s\n" " Author: %s\n" "Publisher: %s\n" " Year: %d\n", title, author, publisher, year); g_free (title); g_free (author); g_free (publisher); g_object_unref (entry); return 0; }
编译这个程序的命令为:
$ gcc $(pkg-config --cflags --libs gobject-2.0) kb-bibtex.c main.c -o test
程序运行结果:
$ ./test Title: The {\TeX}Book Author: Knuth, D. E. Publisher: Addison-Wesley Professional Year: 1984
为类和对象添加方法
在面向对象程序设计方法中,类和对象皆由“属性”与“方法“构成。文档 [1-3] 所讲的均是基于 GObject 子类化的数据抽象与封装,而未有涉及到类和对象的方法。其实,这样说并不正确,因为我们已经接触到了 GObject 子类的类结构体与实例结构体的构造函数,它们分别是类的方法和对象的方法。
类的方法,形式如下:
返回值 函数名 (参数, ...) { }
对象的方法,形式如下:
返回值 函数名 (参数, ...) { }
#@¥%……开玩笑呢吧?这两种方法有什么区别?它们不就是普通的 C 函数么?
嗯,你以为呢?就是普通的 C 函数。
下面为 KbBibtex 类增加一个对象方法,这个函数的功能是可以在终端输出文献信息。
首先,在 kb-bibtex.h 头文件的底部增加函数声明:
/* 对象的方法 */ void kb_bibtex_printf (KbBibtex *self);
然后在 kb-bibtex.c 源文件中实现该函数,如下:
void kb_bibtex_printf (KbBibtex *self) { gchar *title, *author, *publisher; guint year; g_object_get (G_OBJECT (self), "title", &title, "author", &author, "publisher", &publisher, "year", &year, NULL); g_printf (" Title: %s\n" " Author: %s\n" "Publisher: %s\n" " Year: %d\n", title, author, publisher, year); g_free (title); g_free (author); g_free (publisher); }
这样,main.c 源文件便可以被简化为:
#include "kb-bibtex.h" int main (void) { g_type_init (); KbBibtex *entry = g_object_new (KB_TYPE_BIBTEX, "title", "The {\\TeX}Book", "author", "Knuth, D. E.", "publisher", "Addison-Wesley Professional", "year", 1984, NULL); kb_bibtex_printf (entry); g_object_unref (entry); return 0; }
~ End ~
参考文档
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com
2011年3月07日 18:11
Hello yanrui, 你博客里的“参考文献”功能是如何实现的?is-programmer有参考文献引用的插件吗?
2011年3月07日 19:12
手写的 :(
2011年3月08日 00:07
手写如何实现
http://garfileo.is-programmer.com/2011/3/6/reviewing-the-old-and-understand-the-new.25017.html#ref-3
这种ref连接呢?
2011年3月08日 00:12
我引用了文档 [<a href="#ref-1">1</a>]。
参考文档:
[<a name="ref-1">1</a>] 这是一篇文档
2011年3月08日 00:14
@visionfans: blog 的编辑器也有建立锚点和插入锚点的按钮,不过那个建立锚点的按钮不是很好用。
2011年3月08日 09:30
我可不可以这样理解:
类和对象的方法都只是名字跟它所属的类或对象有关,而实际上是没有跟类或对象绑定的普通c函数?
2011年3月08日 10:42
@houqp:
是啊。
例如 C++ 中定义成员函数的方式是这样:
void class KbBibtex::printf ()
{
}
如果这个可以叫绑定,那么在 C 中:
void kb_bibtex_printf (KbBibtex *self);
为啥不能称为“绑定”?
当然,前者是从语法上提供了支持,程序员默认就接受了那种统一的风格。而后者,需要程序员自己形成编程规范,经常会出现彼此风格不一致的情况。
2011年3月08日 10:49
@houqp: 另外,C++ 类的成员函数的参数中,还有一个隐含的 this 指针,等价于 kb_bibtex_printf 函数的那个 self 参数。不过,这个隐含的 this 似乎并没有被 c++ 程序员所接受,很多 c++ 程序员都以发掘 this 指针的妙用为乐。也许只有用了 GObject 才能领会 C++ 语言设计者的煞费心机 :)
2015年3月20日 14:37
@Garfileo: 打印信息的函数,本质上来说,就是你把main函数里面的打印,重构了一下而已。本质上来说和类函数其实一毛钱关系没有.仅仅是一个普通的C函数而已。你所说的绑定,只是入参是一个类指针而已。这里没有任何动态绑定出现。我觉得这里可以改一下。
2015年9月20日 08:00
@benoit: 您说的很对,但是这就是 GObject 的类函数啊……C 语言又没有『类』这种东西,只是用这种方式从字面上模拟了『类』的方法。