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を扱う場合 の考慮などを行うと、次のプログラムができました。どうでしょうか