编程思想之迭代器
迭代器(Iterator)是按照一定的顺序对一个或多个容器中的元素从前往遍历的一种机制,比如for循环就是一种最简单的迭代器,对一个数组的遍历也是一种的迭代遍历的过程。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器有时也称为枚举器(Enumerator),其结构图如下:
迭代器结构图
迭代器其实就是维护一个当前的指针,这个指针可以指向当前的元素,可以返回当前所指向的元素,可以移到下一个元素的位置,通过这个指针可以遍历容器的所有元素。迭代器一般至少会有以下几种方法:
First(); //将指针移至第一个位置或获得第一个元素
GetCurrent(); //获得当前所指向的元素
MoveNext(); //移至下一个元素
既然迭代器是封装里面的实现细节,对外提供方便访问容器元素的接口,那我们就先从使用的角度认识迭代器,看看在各种语言下迭代器是如何使用的。
void TestIterator() { vector<int> vec; // 定义一容器 for(int i = 0; i < 5; i++) { vec.push_back(i*2); //添加元素 } //用迭代器访问容器中的每个元素 cout << "iterator vector:" << endl; for(vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr ++) { cout << *itr << " "; //itr是一个指针,指向当前的元素, 所以要解引用获得元素值 } cout << endl; map<int, string> student; //创建一个map,对应学号-姓名的键值对 //添加元素 student.insert(pair<int, string>(1, "张三")); student.insert(pair<int, string>(3, "王五")); student.insert(pair<int, string>(2, "李四")); //遍历容器中的元素 cout << "iterator map:" << endl; for (map<int, string>::iterator itr = student.begin(); itr != student.end(); itr ++) { cout << itr->first << "-->" << itr->second << endl; } }
iterator vector:
0 2 4 6 8
iterator map:
1-->张三
2-->李四
3-->王五
c++中的容器(如vector、map、list、set等)一般会提供四个迭代器:
iterator:正向迭代,从前往后遍历,可修改元素的值
const_iterator:正向常量迭代,但不能修改元素的值,因为指向的是const的引用
reverse_iterator:反向迭代,从后往前遍历,可修改元素的值
const_reverse_iterator:反向常量迭代,但不能修改元素的值,因为指向的是const的引用
每一种迭代器都提供一对首尾位置的标志begin和end,其关系如下:
迭代器类型 |
开始位置标志 |
末尾位置标志 |
说明 |
iterator |
begin() |
end() |
正向迭代 |
const_iterator |
cbegin() |
cend() |
正向常量迭代 |
reverse_iterator |
rbegin() |
rend() |
反向迭代 |
const_reverse_iterator |
crbegin() |
crend() |
反向常量迭代 |
对应的示意图如下:
图1:正常的迭代
图2:常量值的迭代
public static void testIterator() { //创建一个列表 List<Integer> list = new ArrayList<Integer>(); list.add(4); //添加元素 list.add(3); list.add(7); //返回一个迭代器,并遍历列表中的元素 Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer value = iterator.next(); System.out.print(value + " "); } System.out.println(); //返回ListIterator迭代器并从后往前遍历列表的元素 ListIterator<Integer> listIterator = list.listIterator(list.size()); System.out.println("ListIterator:"); while (listIterator.hasPrevious()) { Integer value = listIterator.previous(); System.out.print(value + " "); } System.out.println(); }
Iterator begin to end:
4 3 7
ListIterator end to begin:
7 3 4
Java中的List接口及其实现类可以通过iterator()返回Iterator,或通过listIterator()和listIterator(int index) 返回ListIterator。
Iterator和ListIterator都是迭代器,ListIterator继承自Iterator。Iterator只能对列表进行遍历,且只能从前往后遍历,ListIterator可以修改列表,且可以选择往前或往后遍历。关于Iterator和ListIterator更详细的说明请参见官方API:Iterator和ListIterator
JavaScript中表示容器的是Array类型,标准并没有给出对应的迭代器的说明和实现,但我们可以自己实现一个简单的迭代器的功能:
<script type="text/javascript"> //创建一个迭代器,传入的必须是Array类型的数据 function makeIterator(array) { var index = 0; return { hasNext: function () { return index < array.length; }, next: function () { return this.hasNext ? array[index++] : null; }, current: function () { return array[index]; } }; } //创建一个数组并赋值 var mycars=new Array(); mycars[0]="Saab"; mycars[1]="Volvo"; mycars[2]="BMW"; //将数组mycars生成一个迭代器,并通过迭代器遍历数据元素 var iterator = makeIterator(mycars); while (iterator.hasNext()) { document.write(iterator.next() + '<br>'); } </script>
Saab
Volvo
BMW
mozilla提供的JavaScript 1.7已经添加了迭代器的功能,但现在只能在Firefox浏览器上才有用。如:
<script type="application/javascript;version=1.7"> var lang = { name: 'JavaScript', birthYear: 1995, age: 19 }; //生成一个迭代器 var itr = Iterator(lang); document.write('key-value:' + '<br>'); for(var key in itr){ document.write(key + '<br>'); } //这个迭代器遍历每一个key值 var itr2 = Iterator(lang, false); document.write('key:' + '<br>'); for(var key in itr2){ document.write(key + '<br>'); } //这个迭代器遍历每一个索引和值 var arrs = ['JavaScript', 'Python', 'Haskell']; var itr3 = Iterator(arrs, false); document.write('index-value:' + '<br>'); for(let [i, value] in itr3){ document.write(i + ':' + value + '<br>'); } </script>
key-value:
name,JavaScript
birthYear,1995
age,19
key:
name,JavaScript
birthYear,1995
age,19
index-value:
0:JavaScript
1:Python
2:Haskell
更多关于JavaScript 1.7的迭代器请参见:Iterators and Generators
在上面一小节“JavaScript中的迭代器”中,已经对Array数组实现了自己定义的迭代器。以上讲述的迭代器基本都是集合内部的元素具有相同的数据类型,但实际的开发过程中可能会有更复杂的容器结构,假设有如下的需要:
一个公司有多个部门,每个部门有多个人组成,这些人中有开发人员,有测试人员,和与项目相关的其它人员,其结构如下:
现在要遍历这个公司的所有开发人员,遍历这个公司的所有测试人员。
针对这个需求,我们可以创建一个定制化的迭代器来遍历一个公司所有人员,也可以传入员工类型来遍历指定类型的员工,其类的结构图如下:
对应的实现代码如下:
[email protected]:luoweifu/iteratortest.git
对应的调用代码如下:
#include "stdafx.h" #include <string> #include <iostream> #include "Person.h" #include "Department.h" #include "Company.h" #include "Enumerator.h" int _tmain(int argc, _TCHAR* argv[]) { Company company("Apabi"); Department* pDepartMent1 = new Department("开发1部"); Department* pDepartMent2 = new Department("开发2部"); Department* pDepartMent3 = new Department("内核研发部"); company.AddDepartment(pDepartMent1); company.AddDepartment(pDepartMent2); company.AddDepartment(pDepartMent3); int empId = 1; Person* pPerson11 = new Developer(empId++, "Developer11", "C++", "智慧城市"); Person* pPerson12 = new Developer(empId++, "Developer12", "Java", "智慧城市"); Person* pPerson13 = new Developer(empId++, "Developer13", "Java", "智慧城市"); Person* pPerson14 = new Developer(empId++, "Developer14", "JavaScript", "智慧城市"); Person* pPerson15 = new Tester(empId++, "Tester15", "LoadRunner"); Person* pPerson16 = new Tester(empId++, "Tester16", "黑盒测试"); cout << pPerson16->GetPersonType() << endl; pDepartMent1->AddPerson(pPerson11); pDepartMent1->AddPerson(pPerson12); pDepartMent1->AddPerson(pPerson13); pDepartMent1->AddPerson(pPerson14); pDepartMent1->AddPerson(pPerson15); pDepartMent1->AddPerson(pPerson16); Person* pPerson21 = new Developer(empId++, "Developer21", "IOS", "Mobile"); Person* pPerson22 = new Developer(empId++, "Developer22", "Android", "Mobile"); Person* pPerson23 = new Tester(empId++, "Tester23", "LoadRunner"); Person* pPerson24 = new Tester(empId++, "Tester24", "TestIn"); pDepartMent2->AddPerson(pPerson21); pDepartMent2->AddPerson(pPerson22); pDepartMent2->AddPerson(pPerson23); pDepartMent2->AddPerson(pPerson24); Person* pPerson31 = new Developer(empId++, "Developer31", "C++", "CEBX内核"); Person* pPerson32 = new Developer(empId++, "Developer32", "C++", "CEBX内核"); Person* pPerson33 = new Developer(empId++, "Developer33", "C++", "CEBX内核"); Person* pPerson34 = new Developer(empId++, "Developer34", "C++", "CEBX内核"); Person* pPerson35 = new Tester(empId++, "Tester35", "LoadRunner"); pDepartMent3->AddPerson(pPerson31); pDepartMent3->AddPerson(pPerson32); pDepartMent3->AddPerson(pPerson33); pDepartMent3->AddPerson(pPerson34); pDepartMent3->AddPerson(pPerson35); //遍历所有开发者 cout << "遍历所有开发者:" << endl; Enumerator* pEnumerator1 = company.GetEnumerator(PERSON_DEVELOPER); while(pEnumerator1->MoveNext()) { Person* pPerson = pEnumerator1->Current(); if (pPerson) { pPerson->showInfo(); } } delete pEnumerator1; //遍历所有测试人员 cout << "遍历所有测试人员:" << endl; Enumerator* pEnumerator2 = company.GetEnumerator(PERSON_TESTER); while(pEnumerator2->MoveNext()) { Person* pPerson = pEnumerator2->Current(); if (pPerson) { pPerson->showInfo(); } } delete pEnumerator2; //遍历公司所有员工 cout << "遍历公司所有员工:" << endl; Enumerator* pEnumerator3 = company.GetEnumerator(PERSON_TYPE_NONE); while(pEnumerator3->MoveNext()) { Person* pPerson = pEnumerator3->Current(); if (pPerson) { pPerson->showInfo(); } } delete pEnumerator3; return 0; }
1.集合的内部结构复杂,不想暴露对象的内部细节,只提供精简的访问方式;
2.需要提供统一的访问接口,从而对不同的集合使用同一的算法。
=====================编程思想系列文章回顾=====================
编程思想之递归
编程思想之回调