Javaなどのオブジェクト指向の言語での開発に慣れてくると、C言語で プログラムを書こうとしても、あれもない、これもない、でコードを書くのが いやになる事があります。というか、最近なりました。 たとえばqueueなんかはJavaだとList(ArrayListとか)を使うだけですが、 Cでは一々コードを書かなければなりません。生産性が下がるだけでなく、 コードの品質を保つのもなかなか面倒です。
そこで、JavaやC++のクラスライブラリのノリで使えるライブラリを志向して、 まずは汎用のqueueを作ってみました。
queueというのは説明するまでも無く、FIFO(First In, First Out)を実現する データ構造です。チューブの右からデータを入れ、左から取り出すような イメージですね。チューブは空かもしれませんし、既に沢山データが入っている かもしれません。左から取り出す毎にチューブの中のデータは少しずつ左に 移動してゆきます。データを入れるタイミングと取り出すタイミングは同期して いるとは限らないので、チューブ内のデータの量は増減します。queueのデータ 構造はシステムのあらゆる局面でしばしば登場するものです。
queueを実現する典型的な方法は二重リンクリストを使うものです。queue内の 個々のデータの構造を、
typedef struct general_queue_item {
struct general_queue_item *prev;
struct general_queue_item *next;
void *data;
} general_queue_item_t;
という風に定義します。nextでgeneral_queue_item_tの前方向リンクリストを、
prevでgeneral_queue_item_tの後方向リンクリストを作るようにします。
汎用的に使うためにデータの型はvoid*にしています。
これで作るqueueの先頭と末尾を管理するデータ構造を定義します。 queueの先頭からnextをたどってゆくと末尾に、末尾からprevをたどって ゆくと先頭に達するようになればよいわけです。
typedef struct general_queue_info {
general_queue_item_t *head;
general_queue_item_t *tail;
} general_queue_info_t;
てな感じ。
では関数はどうなるでしょう。queueにデータを入れる方は、
void push_item(general_queue_info_t *info, void *data)
{
general_queue_item_t *q;
/* ここでqにgeneral_queue_item_t型のデータを一つ用意 */
// queueの末尾につける
q->data = data;
q->next = NULL;
q->prev = info->tail;
if (info->tail != NULL) {
info->tail->next = q;
}
info->tail = q;
// queueが空だったら、先頭にも設定してあげる
if (info->head == NULL) {
info->head = q;
}
}
となります。末尾に要素を一つつけるのですが、つける要素だけ
でなく、現在の末尾の要素の中も書き換えないと二重リンクを維持
できません。また初期状態(queueが空)の処理も注意が必要です。
取り出すほうはどうすればよいでしょうか。
void *get_item(general_queue_info_t *info)
{
general_queue_item_t *q;
void *data;
// 先頭がNULL(==queueが空)ならNULLを返す
if (info->head == NULL) {
return NULL;
}
// リンクの付け直しをする
q = info->head;
data = q->data;
info->head = q->next;
if (info->tail == q) {
info->tail = NULL;
}
return data;
}
てな感じです。こっちも先頭から取り出したとき、残ったqueue
が空になる場合に注意が必要です。
queueの各要素のデータ領域をどこから持ってくるのか、複数のqueueを扱う場合 の考慮などを行うと、次のプログラムができました。どうでしょうか