当前位置: 首页 > news >正文

C语言中迭代器的设计与使用

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

 

  经常使用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/

 

     个人微博:http://weibo.com/zsxxsz

     QQ 群:242722074

转载于:https://my.oschina.net/u/568966/blog/309576

相关文章:

  • 利用ACL库快速创建你的网络程序--ACL_VSTREAM 流的使用
  • 架构反思案例之“分布式”的架构案例
  • HttpClient---------demo
  • SQLSERVER存储过程基本语法
  • 安装redis
  • C#实现一个最简单的HTTP服务器
  • 【特别推荐】14个支持响应式设计的流行前端开发框架
  • 2012毕业找工作记录点滴
  • FAQ_Zabbix:解决模板收集到的数据和真实数据有偏差
  • 使用钩子函数[2]
  • 【从零之三(更)】自定义类中调用讯飞语音包错误解决办法
  • spring SpEL
  • view动画库
  • Submit a form with Ajax 发送邮件参考
  • 应该总结自己了
  • ----------
  • Angular6错误 Service: No provider for Renderer2
  • Electron入门介绍
  • es6--symbol
  • Facebook AccountKit 接入的坑点
  • FastReport在线报表设计器工作原理
  • JavaScript新鲜事·第5期
  • js操作时间(持续更新)
  • Object.assign方法不能实现深复制
  • oschina
  • PAT A1120
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • PHP变量
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • Spark RDD学习: aggregate函数
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • windows下使用nginx调试简介
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 回顾 Swift 多平台移植进度 #2
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 译米田引理
  • 用element的upload组件实现多图片上传和压缩
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • "无招胜有招"nbsp;史上最全的互…
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (二)fiber的基本认识
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (转)视频码率,帧率和分辨率的联系与区别
  • (转载)虚函数剖析
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • 、写入Shellcode到注册表上线
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .gitignore