概要

ここではglibの使い方を例と共に紹介します。

ハッシュ

glibにはgenericなhash操作のための関数群があります。内容的にはこれと言った特徴はなさそうですが、ちょこっとhashが欲しい時に一々コーディングする必要がなくて楽ちんです。

hashの生成

hashを生成するには、まず

  • hash関数
  • 比較関数

を用意します。

hash関数

hash関数は、キーから整数への写像を作るものです。hash関数は以下の ような形式になっています。

typedef guint   (*GHashFunc)(gconstpointer  key);

ですから、実際の関数の定義は以下の例のようになります。

hashの例
static  guint
Hash(
    gconstpointer   key)
{
    char    *p;
    guint   ret;

    ret = 0;
    if      (  key  !=  NULL  ) {
        for ( p = (char *)key ; *p != 0 ; p ++ ) {
            ret = ( ret << 4 ) | *p;
        }
    }
    return  (ret);
}

この例では、単にシフトしながら文字を加算するだけですが、必要ならより colisionの発生の少ない関数を考えます。

比較関数

比較関数とは、keyが一致したことを判定するための関数です。つまり、hashテーブル上のkeyと、検索時に指定したキーが一致するということを判定するために使われます。

理想的なhash関数なら、キー値が異なればhash値も異なるということに なり、一々比較をするまでもないはずですが、そのような写像を作るのは困難であるため、

とりあえず適当でいーじゃん

ということでhash関数を作り、キー値が異なっても同じhash関数を返してしまうということは、「あること」として扱うようにします。具体的にはhash値でバケツのようなものを作っておき、同じhash値のものは同じバケツの中に放り込んでおくようにするわけです。

このバケツの中を探すために、実際のキー値の比較を行います。このための関数が比較関数になります。

現在のglibでは、バケツの中を探すためには、あまり複雑なやり方をしていないため(単純な馬鹿サーチをしています)、単にキー値が等しいかどうかの判定だけを行えば良いようです。比較関数は以下のような形式です。

比較関数の形式
typedef gint    (*GCompareFunc)(gconstpointer a,
                                gconstpointer b);

ですから、実際の関数の定義は以下の例のようになります。

比較関数の例
static  gint
Compare(
    gconstpointer   s1,
    gconstpointer   s2)
{
    return  (!strcmp((char *)s1,(char *)s2));
}

ここで注意しなくてはならないことは、この関数は一致していたら、真を返さないといけないということです。普通にstrcmpすると、「一致したら偽」になってしまいますから、注意しましょう。この比較関数の形式は他の表管理での比較にも使われますので、それらに流用することを考えれば、単に等しいかどうかだけではなくそうかと思ってしまうわけですが、他の表管理では< = >の意味として、負 0 正を使うようになっているものが多いですから、流用は出来ません。

hashの生成

関数の用意が出来たら、hashを生成します。hashの生成するための関数はg_hash_table_newで、以下のような定義です。

hash生成の形式
GHashTable* g_hash_table_new(GHashFunc hash_func,
                             GCompareFunc key_compare_func);

ですから、実際のhashの生成は以下の例のようになります。

static  GHashTable  *HashTable = NULL;
.....
if      (  HashTable  !=  NULL  )
    g_hash_table_destroy(HashTable);
    HashTable = NULL;
}
HashTable = g_hash_table_new((GHashFunc)Hash,
                             (GCompareFunc)Compare);    /*  ※  */

この例で最初のif文は、hashを何度も作ることになった時のための処理ですから、不要なこともあると思います。実際にhashを生成しているのは※の部分です。

これでhashの生成が出来ました。

hashの破壊

hashの生成の例で破壊の話も出て来たので、ここで説明し ておきます。

hashを破壊するための関数はg_hash_destroyで、以下のような定義です。

hash破壊の定義
void    g_hash_table_destroy(GHashTable *hash_table);

この関数は、hashの破壊のみを行い、格納されたデータには影響を与えません。また、後で述べるデータの格納の時には、単にhashのデータ構造には、keyや実体へのポインタのみを格納しています。つまり、hashのデータ構造は、格納したデータの「糊」としてのみ働いています。そして、この関数ではその「糊」の部分だけを破壊します。このことに留意して下さい。

具体的な使用例については、hashの生成の例を見て下さい。

hashの検索

hashを検索するための関数は、g_hash_table_lookupです。

hash検索の形式
gpointer    g_hash_table_lookup(GHashTable *hash_table,
                                gconstpointer key);

hash_tableは生成しデータを格納したhash、keyは検索のためのキーです。返り値が検索結果へのポインタです。この辺のことは、次の hashへの格納も一緒に見て下さい。

実際のhashの検索は以下の例のようになります。
hashの検索の例
if      (  ( body = g_hash_table_lookup(HashTable,name) )
           ==  NULL  ) {

この例でわかるように、発見出来なかった場合はNULLが返ります。

hashへの格納

hashの格納のための関数は、g_hash_table_insertです。

hash格納の形式
void    g_hash_table_insert(GHashTable *hash_table,
                            gpointer key,
                            gpointer value);

hash_tableは生成したhash、keyは検索のためのキー、valueが実際に格納される実態です。なお、gpointerはvoid *と同じ意味なので、keyもvalueも好きなものが入れられます。逆に言えば、glib側では何の処理もしないで、単なるポインタとしてのみ操作するということでもあります。

破壊のところでも述べたように、格納では一切のデータの複製を行っていません。特に注意しなくてはならないのは、keyです。hashにはキーのポインタのみが格納されますから、

同じデータ領域使い回すことは出来ません。

注意しましょう。

実際のhashへの格納は以下の例のようになります。

hashへの格納の例
if      (  g_hash_table_lookup(HashTable,name)  ==  NULL  ) {
    g_hash_table_insert(HashTable,name,entry);  /*  ※  */
}

この例では重複登録を避けるために、一度検索して存在しないことを確認してから格納を行っています。

hash全データへの処理

glibではhashの全データに対する処理の方法が提供されています。このための関数が、g_hash_table_foreachです。

hash全データへの処理の形式
guint   g_hash_table_foreach_remove(
            GHashTable *hash_table,
            GHRFunc func,
            gpointer user_data);

ここで、実際に処理を行う手続きは、funcで与えられます。funcの形式は、

処理関数の形式
typedef gboolean    (*GHRFunc)(gpointer key,
                               gpointer    value,
                               gpointer    user_data);

となります。この手続きの引数のkeyは処理中のデータのキー値、valueには処理 するべきデータ(へのポインタ)、user_dataはg_hash_table_foreach_removeを呼び出す時のuser_dateです。この関数はgbooleanの返り値があることになっていますが、実際に使用されていません。以下が処理関数の例です。

処理関数の例
static  gboolean
PrintEntry(
    gpointer    key,
    gpointer    value,
    gpointer    user_data)
{
    GtkWidget   *widget;
    gchar       *entry_text;

    widget = glade_xml_get_widget(CurrentXML,key);
    entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
    printf("Entry contents: %s = [%s]n",
           (char *)key,entry_text);

    return(0);
}

実際に処理をする時には、

g_hash_table_foreachの例
g_hash_table_foreach(HashTable,(GHFunc)PrintEntry,NULL);

のように呼び出します。

hashからの削除

hashからエントリを削除するための関数は、g_hash_table_removeです。hashは要素の削除は結構面倒だったりするので、この関数はかなり便利です。

hashからの削除の形式
void    g_hash_table_remove(GHashTable *hash_table,
                            gconstpointer key);

hash_tableは生成したhash、keyは削除する要素のキーです。

破壊のところでも述べたように、格納では一切のデータの複製を行っていませんので、削除処理で消えるのは、hashのデータ構造のみです。

実際のhashからの削除は以下の例のようになります。

hashからの削除の例
g_hash_table_remove(HashTable, key);

なお、g_hash_table_removeは一回の呼び出しで削除される要素は1つだけです。キーを重複して登録してしまった場合は、複数回呼び出す必要があります。また、返り値は持ちません。キーに該当するエントリがない時も、特に通知はありません。