Cでイテレータ

また社内研修ネタ。Cでリスト構造を扱うときに、リストを使用する関数側に実装を公開しないやり方。


単純に言うとヘッダファイルlist.hでリスト構造体の宣言だけして、定義はlist.c側で行う。もちろんこれだけではリストの操作ができないのでリスト操作用の関数を用意する。

/* list.h */
#ifndef LIST_HEADER_FILE_INCLUDED
#define LIST_HEADER_FILE_INCLUDED

/* 構造体宣言 */
typedef struct List_tag List;
/* リストイテレータ宣言 */
typedef struct ListIterator_tag ListIterator;

/* マクロ定義 */
#define LIST_SUCCESS (0)        /* 正常終了 */
#define LIST_FAILURE (-1)       /* 異常発生 */
#define LIST_INVALID_INDEX (-1) /* 無効なインデックス */

/***********************************************
 リストを生成しその先頭を指すポインタを返す
 コンストラクタのイメージ 
 ************************************************/
List *ListCreate(void);

/********************
	リストを解放する 
 ********************/
void ListDispose(List *pList);

/*****************************************************
	pListが指すリストの全ノードをを標準出力に表示する 
 *****************************************************/
void ListDump(List *pList);

/*****************************************************
	リストpListの最後にiをvalueとするノードを追加する
    メモリが確保できない場合AbortしてLIST_FAILUREを返す
 ******************************************************/
void ListAddNode(List *pList, int i);

/****************************************
	リストの全要素消去
	Disposeはリストそのものを解放。
	clearはリストの全要素を削除(解放)
	リストそのものは残っている   
 ****************************************/
void ListClear(List *pList);

/****************************************************************** 
	compareValueの要素の次に要素をinsertValueを挿入する
    メモリを獲得できなかった場合はAbortしてLIST_FAILUREを返す
    戻り値:LIST_SUCCESS:正常終了
			LIST_FAILURE:挿入失敗
 *******************************************************************/
int ListInsertAt(List *pList, int compareValue, int insertValue);

/**************************************************
	deleteValueであるノードを削除する
    戻り値:LIST_SUCCESS:正常終了
			LIST_FAILURE:削除対象ノードが無かった
 ***************************************************/
int ListDelete(List *pList, int deleteValue);

/********************************************************************
	listの中でsearchValueが初めに見つかった位置のインデックスを返す
	戻り値: 対象ノードのインデックス
			LIST_INVALID_INDEX:対象ノードが見つからなかった場合
 ********************************************************************/
int ListIndexOf(List *pList, int searchValue);

/********************************************************************
	listの中でindexに対応するノードの持つ値valueが指す領域に設定する
	戻り値:LIST_SUCCESS:正常終了
    		LIST_FAILURE: indexに対応するノードがない
 ********************************************************************/
int ListGetByIndex(List *pList, int index, int *pValue);

/* ---------------------------------------- */
/* 以下 リストイテレータ関数 */
/* ---------------------------------------- */

/* イテレータコンストラクタ */
ListIterator *ListIteratorCreate(List *pListHead);

/* イテレータデストラクタ */
void ListIteratorDispose(ListIterator *pIte);

/* イテレータhasNext。nextできない場合0, next可能な場合!0が返る*/
int ListIteratorHasNext(ListIterator *pIte);

/* イテレータnext リストの値を取り出し次に進める */
int ListIteratorNext(ListIterator *pIte);

#endif

リストの定義はlist.cで行う。これでリストを利用する関数にはリストのメンバが見えない。勝手にリストを壊すような操作を行えないようにして、リスト構造の堅牢性を高めることができる。

/* list.c 抜粋 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "list.h"

/****************
	構造体宣言 
 ****************/
struct List_tag {
    int value;                  /* 値 */
    List *next;                 /* 次のノードを指すポインタ */
};

/* リストイテレータ宣言 */
struct ListIterator_tag {
    List *pList;                /* イテレータが扱うリストノード */
};


/*****************************************************
	headが指すリストの全ノードをを標準出力に表示する 
 *****************************************************/
void ListDump(List *pList)
{
#if 0 /* nextポインタを利用したリストの全舐め表示 */
    List *p;
    
    for (p = pList->next; p != NULL; p = p->next) {
        printf("value: %3d, this: %p, next: %p\n", p->value, p, p->next);
    }
#else /* イテレータを利用したリストの全舐め表示 */
    ListIterator *ite;
    ite = ListIteratorCreate(pList);

    while (ListIteratorHasNext(ite)) {
        printf("value: %3d\n", ListIteratorNext(ite));
    }
    
    ListIteratorDispose(ite);
#endif
}

イテレータはこの構造体用になっていて、汎用なものではない。プログラムにあわせて、データ構造や操作関数を作るのがCのやり方だといえる。もちろんイテレータパターンを適用して、汎用なイテレータにすることも可能。