0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

周立功教授谈迭代器模式设计

UtFs_Zlgmcu7890 来源:互联网 作者:佚名 2017-09-26 13:51 次阅读

近日周立功教授公开了数年的心血之作《程序设计与数据结构》,电子版已无偿性分享到电子工程师与高校群体下载,经周立功教授授权,特对本书内容进行连载。

>>>>1.1 迭代器模式

>>>1.1.1 迭代器与容器

在初始化数组中的元素时,通常使用下面这样的for循环语句遍历数组

int i, a[10];

for(i = 0; i < 10; i++) 

a[i] = i;

这段代码中的循环变量i该变量的初始值为0然后递增为123...程序在每次i递增后都将值赋给a[i]。数组中保存了许多元素,通过指定数组下标,即可从中选择任意一个元素。for语句中的i++的作用是让i的值在每次循环后自增1,这样就可以访问数组中的下一个元素,从而实现了从头到尾逐一遍历数组元素的功能。

由此可见,常用的循环结构就是一种迭代操作,在每一次迭代操作中,对迭代器的修改即等价于修改循环控制的标志或计数器。而容器是一种保存值的集合的数据结构,C有两种内建的容器:数组和结构体。虽然C没有提供更多的容器,但用户可以按需编写自己的容器,比如,链表、哈希表等。

i的作用抽象化、通用化后形成的模式在设计模式中i称为迭代器(Iterator)模式,Iterate的字面意思是重复、反复声明其实就是重复做某件事,Iterator模式用于遍历数组中的元素。迭代器的基本思想是迭代器变量存储了容器的某个元素的位置,因此能够遍历该位置的元素。通过迭代器提供的方法,可以继续遍历容器的下一个元素。

显而易见,迭代器是一种抽象的设计概念,因为在程序设计语言中并没有直接对应于这个概念的实物。《设计模式》一书提供了23种设计模式的完整描述,其中iterator模式的定义为:在遍历一个容器对象时,提供一种方法顺序访问一个容器对象中的各个元素,而又不暴露该对象的内部表示方式。其中心思想是将数据容器和算法分开且彼此独立,最后再用黏合剂将它们撮合在一起。

>>>1.1.2 迭代器接口

为什么一定要考虑引入Iterator这种复杂的设计模式呢?如果是数组,直接使用for循环语句进行遍历处理不就可以了吗?为什么要在集合之外引入Iterator这个角色呢?一个重要的理由是,引入Iterator后可以将遍历与实现分离。

实际上无论是单向链表还是双向链表,其查找算法与遍历算法的实现没有多少差别,基本上都是重复劳动。如果代码中有bug,则需要修改所有相关的代码。为什么会出现这样的情况呢?主要是接口设计不合理所造成的,其最大的问题就是将容器和算法放在了一起,且算法的实现又依赖于容器的实现,因而必须为每一个容器开发一套与之匹配的算法。

假设要在2种容器(双向链表、动态数组)中分别实现6种算法(交换、排序、求最大值、求最小值、遍历、查找),显然需要2×6=12个接口函数才能实现目标。随着算法数量的不断增多,势必导致函数的数量成倍增加,重复劳动的工作量也越大。如果将容器和算法单独设计,则只需要实现6个算法函数就行了。即算法不依赖容器的特定实现,算法不会直接在容器中进行操作。比如,排序算法无需关心元素是存放在数组或线性表中。

在正式引入迭代器之前,不妨分析一下如程序清单3.49所示的冒泡排序算法。

程序清单3.49冒泡排序算法

1 #include

2 #include "swap.h"

3

4 void bubbleSort(int *begin, int *end)

5 {

6 int flag = 1; // flag = 1表示指针的内容未交换

7 int *p1 = begin; // p1指向数组的首元素

8 int *p2 = end; // p2指向数组的尾元素

9 int *pNext; // pNext指向p1所指向的元素的下一个元素

10

11 while(p2 != begin){

12 p1 = begin;

13 flag = 1;

14 while(p1 != p2){

15 pNext = p1+1;

16 if(*p1>*pNext) //比较指针所指向的值的大小

17 {

18 swap(p1, pNext); //交换2个指针的内容

19 flag = 0; // flag = 0,表示2个指针的内容交换

20 }

21 p1++; // p1指针后移

22 }

23 if(flag) return; // 没有交换,表示已经有序,则直接返回

24 p2--; // p2指针前移

25 }

26 }

27

28 int main(int argc, char *argv[])

29 {

30 int a[]={5, 3, 2, 4, 1};

31 int i = 0;

3233 bubbleSort(a, a+4);

34 for(i = 0; i < sizeof(a) / sizeof(a[0]); i++){

35 printf("%d ", a[i]);

36 }

37 return 0;

38 }

如果任何一次遍历没有执行任何交换,则说明记录是有序的且终止排序。其中,p1指向数组的首元素,pNext指向p1所指向的元素的下一个元素,p2指向数组的尾元素(图 3.21(a))。如果*p1>*pNext,则交换指针所指向的内容,p1与pNext后移(图 3.21(b)),反之指针所指向的内容不变,p1与pNext后移,经过一轮排序之后,直到p1 = p2为止,最大元素移到数组尾部。

图 3.21 内部循环执行过程示意图

当最大元素移到数组的尾部时,则退出内部循环。p2前移后程序跳转到程序清单3.49(15),p1再次指向数组的首元素,pNext指向p1所指向的元素的下一个元素(图 3.22(a))。此时,图 3.22(a)与图 3.21(a)的差别在于p2指向a[3]。经过一轮循环之后,直到p1 = p2,此时整数4移到a[3]所在的位置,剩余的排序详见图 3.22。当p1与p2重合在数组首元素所在的位置时,表示排序结束(图 3.22(d))。

图 3.22 外部循环执行过程示意图

由此可见,冒泡排序算法的核心是指针的操作,其主要行为如下:

比较指针所指向的值的大小;

交换指针所指向的内容;

指针后移,即指针指向下一个元素;

●指针前移,即指针指向前面一个元素。

由于这里是以int类型数据为例实现冒泡排序的,因此用户知道如何比较数据和如何交换指针所指向的内容,以及指针的前后移动。当使用支持任意类型数据的void *时,虽然算法程序不知道传入什么类型的数据,但调用者知道,因此在调用排序算法函数时,可以由用户传递参数通过回调函数实现。修改后的冒泡排序函数原型如下:

void iter_sort (void *begin, void *end, compare_t compare, swap_t swap);

其中,compare用于比较两个指针所指向的值的大小,compare_t类型定义如下

typedef int (*compare_t) (void *p1, void *p2);

swap函数用于交换两个指针指向的内容,swap_t类型定义如下:

typedef void (*swap_t) (void *p1, void *p2);

显然无法通过++或--移动指针,因为不知道传入的是什么类型的数据。如果知道数据占用4个字节,则可以通过指针的值加4或减4实现指针的移动。虽然使用这种方式可以实现指针的移动,但始终要求数据必须以数组的形式存储,一旦离开了这个特定的容器,则无法确定指针的行为。如果将算法与链表结合起来使用显然代码中的p1++p2--不适合链表

基于此,“不妨对指针进行抽象,让它针对不同的容器有不同的实现,而算法只关心它的指针接口”。显然,需要容器提供相应的接口函数,才能实现指针前移和后移,通常将这样的指针称为“迭代器”。从某种意义上来说,迭代器作为算法的接口是广义指针,而指针满足所有迭代器的要求。其优势在于对任何种类的容器都可以用同样的方法顺序遍历容器中的元素,而又不暴露容器的内部细节,迭代器接口的声明详见程序清单3.50

程序清单3.50 迭代器接口的声明

1 typedef void *iterator_t; //定义迭代器类型

2 typedef void (*iterator_next_t)(iterator_t *p_iter);

3 typedef void (*iterator_prev_t)(iterator_t *p_iter);

4

5 //迭代器接口(if表示interface由具体容器实现比如链表、数组等

6 typedef struct _iterator_if{

7 iterator_next_t pfn_next; //迭代器后移函数相当于p1++

8 iterator_prev_t pfn_prev; //迭代器前移函数相当于p2--

9 }iterator_if_t;

其中,p_iter指向的内容是由容器决定的,它既可以指向结点,也可以指向数据。无论是链表还是其它容器实现的pfn_next函数,其意义是一样的,其它函数同理。如果将迭代器理解为指向数据的指针变量,则pfn_next函数让迭代器指向容器的下一个数据,pfn_prev函数让迭代器指向容器的上一个数据

此时,应该针对接口编写一些获取或设置数值的方法。用于读取变量的方法通常称为“获取方法(getter)”,用于写入变量的方法通常称为“设置方法(setter)”。下面以双向链表为例,使用结构体指针作为dlist_iterator_if_get()的返回值,详见程序清单3.51

程序清单3.51获取双向链表的迭代器接口(1)

1 static void __dlist_iterator_next(iterator_t *p_iter) //让迭代器指向容器的下一个数据

2 {

3 *p_iter = ((dlist_node_t *)*p_iter) -> p_next;

4 }

5

6 static void __dlist_iterator_prev(iterator_t *p_iter) //让迭代器指向容器的上一个数据

7 {

8 *p_iter = ((dlist_node_t *)*p_iter) -> p_prev;

9 }

10

11 iterator_if_t *dlist_iterator_if_get (void)

12 {

13 static iterator_if_t iterator_if;

14 iterator_if.pfn_next = __dlist_iterator_next;

15 iterator_if.pfn_prev = __dlist_iterator_prev;

16 return &iterator_if; //返回结构体变量地址&iterator_if

17 }

其调用形式如下:

iterator_if_t *p_if = dlist_iterator_if_get(); // 获得链表的迭代器接口,即p_if = &iterator_if

注意,如果省略static,则iterator_if就成了一个局部变量。由于它将在函数执行完后失效,因此返回它的地址毫无意义。这里采用了直接访问结构体成员的方式对iterator_if_t类型的结构体赋值,显然不同模块之间应该尽可能避免这种方式,取而代之的是提供相应的接口,详见程序清单 3.52

程序清单3.52获取双向链表的迭代器接口(2)

1 void dlist_iterator_if_get(iterator_if_t *p_if)

2 {

3 p_if -> pfn_next = __dlist_iterator_next;

4 p_if -> pfn_prev = __dlist_iterator_prev;

5 }

其调用形式如下:

iterator_if_t iterator_if;

dlist_iterator_if_get(&iterator_if);

由于iterator_if_t类型的结构体中只有两个函数指针,因此对函数指针的访问仅包含设置和调用,详见程序清单 3.53

程序清单3.53迭代器接口(iterator.h)

1 #pragma once;

2

3 typedef void *iterator_t;

4 typedef void(*iterator_next_t)(iterator_t *p_iter);

5 typedef void(*iterator_prev_t)(iterator_t *p_iter);

6

7 typedef struct _iterator_if{

8 iterator_next_t pfn_next; //调用迭代器后移的函数指针,相当于p1++

9 iterator_prev_t pfn_prev; //调用迭代器前移的函数指针,相当于p2--

10 }iterator_if_t;

11

12 void iterator_if_init(iterator_if_t *p_if, iterator_next_t pfn_next, iterator_prev_t pfn_prev);

13 void iterator_next(iterator_if_t *p_if, iterator_t *p_iter); //迭代器后移函数,相当于++

14 void iterator_prev(iterator_if_t *p_if, iterator_t *p_iter); //迭代器前移函数,相当于--

这些函数的具体实现详见程序清单3.54

程序清单3.54迭代器接口的实现

1 #include "iterator.h"

2

3 void iterator_if_init(iterator_if_t *p_if, iterator_next_t pfn_next, iterator_prev_t pfn_prev)

4 {

5 p_if -> pfn_next = pfn_next;

6 p_if -> pfn_prev = pfn_prev;

7 }

8

9 void iterator_next(iterator_if_t *p_if, iterator_t *p_iter)

10 {

11 p_if -> pfn_next(p_iter);

12 }

13

14 void iterator_prev(iterator_if_t *p_if, iterator_t *p_iter)

15 {

16 p_if -> pfn_prev(p_iter);

17 }

现在可以直接调用iterator_if_init()实现dlist_iterator_if_get(),详见程序清单 3.55

程序清单3.55获取双向链表的迭代器接口(3)

1 void dlist_iterator_if_get(iterator_if_t *p_if)

2 {

3 iterator_if_init(p_if, __dlist_iterator_next, __dlist_iterator_prev);

4 }

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 周立功
    +关注

    关注

    38

    文章

    130

    浏览量

    37078
  • 迭代器
    +关注

    关注

    0

    文章

    43

    浏览量

    4271
  • 移动指针
    +关注

    关注

    0

    文章

    1

    浏览量

    2016

原文标题:周立功:抽象的设计概念——迭代器

文章出处:【微信号:Zlgmcu7890,微信公众号:周立功单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    立功ARM学习教程(ppt课件)

    立功ARM学习教程(ppt课件)第一章 嵌入式系统概述第二章 嵌入式系统工程设计第三章 ARM7体系结构第四章 ARM7TDMI(-S)指令系统第五章 LPC2000系列ARM硬件结构第六章 接口
    发表于 09-03 09:15

    立功大师EASY FPGA原理图

    本帖最后由 eehome 于 2013-1-5 09:47 编辑 立功EASYFPGA原理图立功大师经典力作,FPGA原理图。欢迎大家下载学习
    发表于 03-16 11:02

    立功的NIOS视频

    立功的NIOS视频
    发表于 07-19 09:55

    立功 C51语言使用技巧及实战

    本帖最后由 eehome 于 2013-1-5 09:56 编辑 立功 C51语言使用技巧及实战
    发表于 08-15 12:53

    立功labview can

    立功 u*** can2 读取动态数据有点慢,一秒钟只有150组数据,测试要求1秒钟1500组数据,太慢了。labview是不是就是慢呀!
    发表于 09-12 10:18

    立功C51教程

    立功C51教程,里面有很多c51使用的技巧以及单片机使用的技巧[hide]http://pan.baidu.com/share/link?shareid=351871&uk=85110142[/hide]
    发表于 12-08 15:32

    立功485隔离模块资料

    立功485隔离模块资料
    发表于 04-05 18:24

    立功的CAN卡Labview通信程序

    立功的CAN卡Labview通信程序,常用
    发表于 07-08 11:00

    立功单片机方案

    读卡ICNFC安全加密芯片PKE/RKE/IMMO读卡模块电源AC-DCDC-DCLDO稳压存储SRAMDRAMMobileDRAMMRAMRLDRAMFlashE2PROM如需了解更多请关注公众号:立功单片机`
    发表于 08-15 11:04

    新书创作立功教授数十年之心血力作《程序设计与数据结构》

    ` 近日,立功教授公开了数十年之心血力作《程序设计与数据结构》,此书在4月28日落笔,电子版已无偿性分享到电子工程师与高校群体,在致远电子公众号后台回复关键字【程序设计】可在线阅读。 在程序设计
    发表于 05-15 18:04

    飞利浦rc522和立功rc522的区别

    打算开始研究RFID技术,发现飞利浦和立功的RC522比较出名,不像是立功用的飞利浦的芯片啊,,这两者有什么区别啊。。。希望有懂的大神,能进来讨论讨论。。。
    发表于 05-16 09:35

    立功CANTest软件

    立功CANTest软件
    发表于 01-15 16:52

    立功CANTest软件

    立功CANTest软件
    发表于 02-27 09:26

    LABVIEW程序接收不到立功CAN卡的CAN消息

    D2P(相当于单片机)和PC通过立功CAN卡连接,D2P上编写的程序发送CAN消息,经立功CAN卡送到PC,希望在LABVIEW程序中处理收到的CAN数据,并把数据在前面板中用量表
    发表于 11-05 21:06

    如何使用立功的Aworks框架进行BSP开发?

    如何使用立功的Aworks框架进行BSP开发?
    发表于 10-13 08:46