经常使用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 只是定义了一些规则,具体实现由各个容器自己来实现,如果容器要实现正向遍历,则需要遵守如下原则:
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() 提前退出。
至于反向遍历容器中元素,规则约束下正向遍历类似,在此不再详述。
下面,以一个大家常用的字符串分隔功能的函数例子来结束本文:
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/ 下的例子进行查看.
ACL 库下载位置:http://acl.sourceforge.net/
QQ 群:242722074
相关推荐
在此“基于迭代器的一元多项式计算”项目中,我们探讨的是如何使用C语言实现一个一元多项式计算的迭代器,同时利用分层结构来组织代码,提高代码的可读性和可维护性。 首先,一元多项式是数学中的基本概念,由常数...
这个压缩包包含了C语言课程设计与游戏开发的实践代码,为学习者提供了一个宝贵的实践平台。 首先,我们需要了解C语言的基本概念。C语言是一种结构化编程语言,它的语法简洁,允许程序员直接操作内存,因此特别适合...
在C语言中,迭代器通常是通过一个结构体来实现,该结构体包含当前元素和遍历函数的指针。 总结来说,尽管C语言没有面向对象编程语言的语法特性,但通过良好的设计和编程技巧,依然可以灵活地在C语言中实现各种设计...
C语言中,可以使用结构体指针和迭代器函数实现。 21. **访问者模式**:在不改变对象结构的前提下,增加新的操作和行为。在C语言中,可以使用双态设计模式或组合模式的扩展实现。 22. **备忘录模式**:在不破坏封装...
《C语言设计模式》是一份深入探讨如何在C语言中应用设计模式的宝贵资源,它涵盖了23种经典设计模式的实现。设计模式是软件工程中的最佳实践,它们是为了解决常见问题而形成的可复用解决方案。这些模式在面向对象编程...
1. **游戏窗口**:C语言中可以使用`ncurses`库创建一个控制台窗口,它提供了一系列函数用于在终端上进行文本输出和输入控制,使得我们可以动态更新游戏画面。 2. **蛇**:蛇由一系列坐标点组成,每个坐标点表示蛇的...
- **结构化程序设计**:强调使用顺序、选择(条件)、循环(迭代)三种基本结构,避免了复杂的嵌套和全局变量,提高程序的可读性和可维护性。 在学习C语言时,掌握这些基础概念至关重要。通过实例练习和上机操作,...
### 谭浩强C语言程序设计知识点梳理 #### C语言概述 - **发展过程**:C语言由Dennis Ritchie于1972年在贝尔实验室开发完成,最初是为了移植Unix操作系统而设计的。 - **当代最优秀的程序设计语言**:C语言因其简洁...
在本次"C语言课程设计-学生籍贯信息记录簿"项目中,主要目标是设计一个能够管理学生籍贯信息的系统,旨在巩固和提升学生在C语言编程方面的技能。以下是该项目涉及的主要知识点及其详细说明: 1. **C语言基础**: -...
在C语言程序设计中,累加器和累乘器是两种常用的编程技巧,它们在处理序列求和或求积的问题时非常有效。本文将详细解释累加器和累乘器的概念、初始化、操作以及如何在实际编程中应用。 首先,让我们理解什么是累加...
1. **教学课件2012级1-4章.rar**:这部分内容通常涵盖了C语言的基础知识,包括基本语法、数据类型、运算符、控制结构(如if语句、switch语句、循环)、函数的定义与调用等。通过这四个章节的学习,你可以掌握C语言的...
7. **STL(标准模板库)**:包含容器(如vector,list,map),算法和迭代器,提供了丰富的数据结构和函数库。 8. **输入/输出流**:iostream库使得与标准输入输出设备交互变得简单,如cout和cin。 通过谭浩强的这...
此外,还提到了`foreach`这样的迭代器循环结构,这种结构更适用于遍历数组或集合,使代码更加简洁易读。 ### 知识点六:高级编程语言特性 文件中还涉及了一些高级编程语言特性,如Haskell的纯函数式编程风格,以及...
语法分析是编译器或解释器设计中的关键步骤,它将源代码解析成抽象语法树(AST),这是一个表示程序结构的数据结构。对于C语言子集,解析器需要识别关键字、标识符、运算符和表达式,确保它们符合C语言的语法规则。 ...
《C语言实现的n后问题算法设计与分析》 在计算机科学中,"n后问题"(也称为八皇后问题)是一个经典的回溯法求解问题。这个问题源于19世纪的国际象棋,由数学家马克斯·贝瑟尔提出,要求在8×8的棋盘上摆放8个皇后,...
7. **迭代器**:虽然C语言没有内置迭代器,但可以模拟迭代器的概念,定义一对函数(如`begin`和`end`)返回指向`vector`起始和结束的指针。 8. **错误处理**:在处理动态内存分配和边界检查时,需要考虑错误处理,...
22. **迭代器模式**:提供一种方法顺序访问一个聚合对象的元素,而又不暴露其底层表示。C语言中可以通过结构体和迭代函数实现。 23. **责任链模式**:将请求沿着处理者链进行传递,直到某个处理者处理请求。C语言中...
在本实践项目中,我们将深入探索"C语言程序设计实训"的相关知识。C语言是一种强大的、低级别的编程语言,被广泛用于系统开发、嵌入式编程、软件开发等多个领域。实训是学习C语言的重要环节,通过编写和理解源代码,...