温故而知新[转]

原文链接

参考文档

也许很多人将孔子所说的“温故而知新”理解为:温习学过的知识,从中获得新的领悟。但是,我更倾向于另一种解释:温习学过的知识,继续学习新的知识。本文借助一个更加平易和现实的示例用以温习文档 [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 源文档的排版结果大致像下面这个样子:

bibtex.png

本文不关心 TeX 是如何处理参考文献的,只关注 Bibtex 文献数据格式。

Bibtex 文献数据格式非常简单,可描述如下:

@文献类型 {引用名
    标识1 = {字符串1},
    标识2 = {字符串2},
    ...   = { ... },
    标示 n = {字符串 n}
}

Bibtex 文献类的设计,代号Kill Bill

这个才是本文的主题。我们将此次行动(或者称为项目也可)命名为“Kill Bill”,缩写“Kb”,其中并无深意,只是因为我正在看这部电影。

类的声明

建立 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

类型注册

建立 kb-bibtex.c 源文件,对 KbBibtex 类进行定义,首先向 GObject 库的类型系统注册 KbBibtex 类型,使之成为 GObject 库认可的一类公民:

#include "kb-bibtex.h"
 
G_DEFINE_TYPE (KbBibtex, kb_bibtex, G_TYPE_OBJECT);

对象的私有属性

在此,必须要澄清两个概念,它们在文档 [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;
};

类结构体与实例结构体的构造函数(初始化函数)

在 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] 中的方式,通过类属性来访问对象属性。

对象私有属性的外部访问

首先要实现类属性与对象属性之间的映射,即 kb_bibtex_set_propertykb_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 则用于表示类属性的个数,它在向类中安装属性的时候可以用到,详见下文。

类属性的构造与安装

一旦完成了 KbBibtex 类属性与 KbBibtex 对象属性之间的映射,即 kb_bibtex_set_propertykb_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;
}