C語言中迭代器的設計與使用

 

  常常使用C++、JAVA等面嚮對象語言開發的程序員都會比較喜歡容器的迭代器功能,用起來方便簡潔。象一些經常使用的數據結構,如:哈希表、動態數組、鏈表等,在這些面嚮對象語言中均可以很是方便地使用迭代器。固然,在C語言中也有對這些經常使用數據結構的函數封裝,但要對容器中元素的遍歷,則通常會經過註冊回調函數的方式。以下:程序員

 

/* 以C語言中很是流行的 glib 庫中的哈希表操做爲例 */

static void print_record(gpointer key, gpointer val, gpointer ctx)
{
    printf("%s: key(%s), value(%s)\n", (char*) ctx, (char*) key, (char*) val));
}

static void free_record(gpointer key, gpointer val, gpointer ctx)
{
    printf("%s: free(%s) now\n", (char*) ctx, (char*) key);
    free(val);
}

static void htable_test(void)
{
    char  *myname = "hash_test";
    char  key[32], *value;
    GHashTable *table;
    int   i;

    /* 建立哈希表 */
    table = g_hash_table_new(g_str_hash, g_str_equal);

    /* 依次向哈希表中添加數據 */
    for (i = 0; i < 10; i++) {
        snprintf(key, sizeof(key), "key:%d", i);
        value = malloc(64);
        snprintf(value, 64, "value:%d", i);
        g_hash_table_insert(table, key, value);
    }

    /* 遍歷並輸出哈希表中的數據 */
    g_hash_table_foreach(table, print_record, myname);

    /* 依次釋放哈希表中的數據 */
    g_hash_table_foreach(table, free_record, myname);

    /* 銷燬哈希表 */
    g_hash_table_destroy(table);
}

 

   這是C函數庫中比較經常使用的回調函數方式,它主要有兩個缺點:多寫了一些代碼,使用不太直觀。下面介紹一下ACL庫中的設計與實現是如何克服這兩個缺點的。首先先請看一個ACL庫中使用哈希表的例子:數組

void htable_test(void)
{
	ACL_HTABLE *table = acl_htable_create(10, 0);  /* 建立哈希表 */
	ACL_ITER iter;  /* 通用迭代器對象 */
	char  key[32], *value;
	int   i;

	/* 依次向哈希表中添加數據 */
	for (i = 0; i < 20; i++) {
		snprintf(key, sizeof(key), "key: %d", i);
		value = acl_mymalloc(32);
		snprintf(value, 32, "value: %d", i);
		assert(acl_htable_enter(table, key, value));
	}

	printf("\n>>>acl_foreach for htable:\n");
	/* 正向遍歷哈希表中數據 */
	acl_foreach(iter, table) {
		printf("hash i=%d, [%s]\n", iter.i, (char*) iter.data);
	}

	/* 釋放哈希表中數據 */
	acl_foreach(iter, table) {
		acl_myfree(iter.data);
	}

	/* 銷燬哈希表 */
	acl_htable_free(table, NULL);
}

 

  由以上例子能夠明顯看出ACL庫中的哈希表遍歷更加簡單直觀,不須要回調函數方式即可以遍歷哈希表中的全部元素。ACL庫不只哈希表能夠用 "ACL_ITER iter; acl_foreach(iter, hash_table) {}" 的方式進行遍歷,其它的通用數據結構容器均可以如此使用,如ACL庫中的先進先出隊列:ACL_FIFO 使用迭代器的例子:數據結構

static void fifo_iter(void)
{
	ACL_FIFO fifo;
	ACL_ITER iter;
	ACL_FIFO_INFO *info;
	int   i;
	char *data;

        /* 初始化堆棧隊列 */
	acl_fifo_init(&fifo);

	/* 向隊列中添加數據 */
	for (i = 0; i < 10; i++) {
		data = acl_mymalloc(32);
		snprintf(data, 32, "data: %d", i);
		acl_fifo_push(&fifo, data);
	}

	printf("\n>>> acl_foreach for fifo:\n");
	/* 正向遍歷隊列中數據 */
	acl_foreach(iter, &fifo) {
		printf("i: %d, value: %s\n", iter.i, (char*) iter.data);
	}

	printf("\n>>> acl_foreach_reverse for fifo:\n");
	/* 反向遍歷隊列中數據 */
	acl_foreach_reverse(iter, &fifo) {
		printf("i: %d, value: %s\n", iter.i, (char*) iter.data);
	}

	/* 彈出並釋放隊列中數據 */
	while (1) {
		data = acl_fifo_pop(&fifo);
		if (data == NULL)
			break;
		acl_myfree(data);
	}
}

 

  能夠看出,ACL庫中的迭代器都是一樣的東東 ACL_ITER, 遍歷方式也都同樣,這是如何作到的?下面請先看一下ACL庫中 ACL_ITER 結構的定義:函數

#ifndef	__ACL_ITERATOR_INCLUDE_H__
#define	__ACL_ITERATOR_INCLUDE_H__

typedef struct ACL_ITER ACL_ITER;

/**
 * ACL 庫中數據結構用的通用迭代器結構定義
 */
struct ACL_ITER {
	void *ptr;		/**< 迭代器指針, 與容器相關 */
	void *data;		/**< 用戶數據指針 */
	int   dlen;		/**< 用戶數據長度, 實現者可設置此值也可不設置 */
	const char *key;	/**< 若爲哈希表的迭代器, 則爲哈希鍵值地址 */
	int   klen;		/**< 若爲ACL_BINHASH迭代器, 則爲鍵長度 */
	int   i;		/**< 當前迭代器在容器中的位置索引 */
	int   size;		/**< 當前容器中元素總個數 */
};

/**
 * 正向遍歷容器中元素
 * @param iter {ACL_ITER}
 * @param container {void*} 容器地址
 * @examples: samples/iterator/
 */
#define	ACL_FOREACH(iter, container)  \
    if ((container))  \
        for ((container)->iter_head(&(iter), (container));  \
             (iter).ptr;  \
             (container)->iter_next(&(iter), (container)))

/**
 * 反向遍歷容器中元素
 * @param iter {ACL_ITER}
 * @param container {void*} 容器地址
 * @examples: samples/iterator/
 */
#define	ACL_FOREACH_REVERSE(iter, container)  \
    if ((container))  \
        for ((container)->iter_tail(&(iter), (container));  \
             (iter).ptr;  \
             (container)->iter_prev(&(iter), (container)))

/**
 * 得到當前迭代指針與某容器關聯的成員結構類型對象
 * @param iter {ACL_ITER}
 * @param container {void*} 容器地址
 */
#define	ACL_ITER_INFO(iter, container)  \
	((container) ? (container)->iter_info(&(iter), (container)) : NULL)

#define	acl_foreach_reverse	ACL_FOREACH_REVERSE
#define	acl_foreach		ACL_FOREACH
#define	acl_iter_info		ACL_ITER_INFO

#endif

 

  其實,ACL_ITER 只是定義了一些規則,具體實現由各個容器本身來實現,若是容器要實現正向遍歷,則須要遵照以下原則:.net

  1)則容器的結構中必需要有成員變量:iter_head(ACL_ITER* iter, /* 容器自己的對象指針 */), iter_next(ACL_ITER* iter, /* 容器自己的對象指針 */); 若是沒有這兩個成員變量怎麼辦?那在編譯時若是有函數使用該容器的 acl_foreach(){} 則編譯器會報錯,這樣的好處是儘可能讓錯誤發生在編譯階段。設計

  2)同時在容器內部須要實現兩個註冊函數: iter_head()/2, iter_next()/2, 此兩函數內部須要將容器的數據賦值給 iter->data;同時改變容器中下一個對象的位置並賦值給 iter->ptr;若是容器自己是由整數值來標識元素索引位置的,則能夠把索引位置賦值給 iter->i,但別忘記依然須要將 iter->ptr 賦值--能夠賦與iter->data 一樣的值,這樣能夠避免acl_foreach() 提早退出。指針

  至於反向遍歷容器中元素,規則約束下正向遍歷相似,在此再也不詳述。code

 

  下面,以一個你們經常使用的字符串分隔功能的函數例子來結束本文:對象

void argv_iter(void)
{
	const char *s = "hello world, you are welcome!";  /* 源串 */
	ACL_ARGV *argv = acl_argv_split(s, " ,!"); /* 對源串進行分隔 */
	ACL_ITER iter;  /* 通用的迭代器 */

	printf("\nacl_foreach for ACL_ARGV:\n");
	/* 正向遍歷字符串數組 argv 中的全部元素 */
	acl_foreach(iter, argv) {
		printf(">> i: %d, value: %s\n", iter.i, (char*) iter.data);
	}

	printf("\nacl_foreach_reverse for ACL_ARGV:\n");
	/* 反向遍歷字符串數組 argv 中的全部元素 */
	acl_foreach_reverse(iter, argv) {
		printf(">> i: %d, value: %s\n", iter.i, (char*) iter.data);
	}
	/* 釋放字符串數組 argv */
	acl_argv_free(argv);
}

 

  ACL中有哪些常見的容器實現了 ACL_ITER 所要求的功能,能夠經過 samples/iterator/ 下的例子進行查看.blog

ACL 庫下載位置:http://acl.sourceforge.net/

 

     我的微博:http://weibo.com/zsxxsz

     QQ 羣:242722074

相關文章
相關標籤/搜索