常常使用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/
QQ 羣:242722074