3.1 什么是迭代器
迭代器即指针,可以是所需要的任意类型,它的最大好处是可以使容器和算法分离。
为了提高C++编程的效率,STL中提供了许多容器,包括vector、list、map等。为统一访问方式,STL为每种容器在实现的时候设计了一个内嵌的iterator类,不同的容器有自己专属的迭代器,使用迭代器来访问容器中的数据。
迭代器对一些基本操作如*、–、++、==、!=进行了重载,使其具有了遍历复杂数据结构的能力,其遍历机制取决于所遍历的容器,迭代器的使用和指针的使用非常相似。通过begin,end函数获取容器的头部和尾部迭代器,当begin和end返回的迭代器相同时表示容器为空。简之,迭代器就是一个遍历的过程,像使用指针一样使用迭代器就可以访问这个容器,不因容器不同而异。迭代器有三成员,但都是指针。并且一个容器里面会存在两个迭代器,一个是起始迭代器(begin),一个是终止迭代器(end)
例如,有两个容器类:MyArray 是某类型数组集合;MyLink 是某类型链表集合。
它们都有显示、查询和排序等功能,常规思维是每个容器类中有自己的显示、查询和排序等函数,不同容器中完成相同功能代码的思路大体是相同的。把它们抽象出来,多个容器仅对应一个显示,一个查询、一个排序函数
为数组容器、链表容器编制共同显示函数
这段代码实现了一个简单的动态数组类,通过模板参数 T 可以使用不同的数据类型。它提供了向数组中添加数据、获取数组长度和获取指定位置元素值的功能。
MyArray 单项链表类初始代码如下所示
#include<stdio.h>
template<class T>//模板参数 T 表示数组元素的类型
class MyArray{//定义了一个模板类 MyArray
private:
int m nTotalsize;//数组总长度
int m nValidSize;//数组有效长度
T* m pData;//数据
public:
MyArray(int nSize= 3) {//数组默认总长度是 3
m_pData=new TInSize];
m_nTotalSize=nSize;
m_nValidSize=0;
}
void Add(T value) {//向m_pData添加数据
// 书上例二
}
int GetSize(){ //返回数组有效长度
return m_nValidsize;//即返回实际存储的元素个数。
}
T Get(int pos)//Get 函数用于获取指定位置 pos 处的元素值,并将其作为返回值返回
{
return m_pData[pos];
}
virtual~MyArray() {//析构函数 ~MyArray 负责释放动态分配的数组内存
if(m_pData!=NULL){
delete []m_pData;//如果已分配内存,则使用 delete[] 关键字释放内存
m_pData=NULL;//m_pData 设置为 NULL,表示释放完成
}}
};
MyLink 单项链表类初始代码如下所示
template <class T>
//这里定义了一个结构体 Unit,表示链表中的单元。
//它包含了一个模板参数 T 表示单元的值类型
struct Unit // 链表单元
{
T value; // value表示单元值
Unit* next; // 下一个单元的指针
};
template <class T>
//定义了一个链表类 MyLink,使用模板参数 T 表示链表单元的值类型
class MyLink{
Unit<T>* head; // 链表头指针
Unit<T>* tail; // 链表尾指针
Unit<T>* prev; // 前一个单元的指针
public:
//构造函数 MyLink() 用于初始化链表的头、尾和前一个单元指针,将它们都设为 NULL
MyLink(){
head = tail = prev = NULL; }
void Add(T& value) //Add函数用来向链表中添加元素
{
Unit<T>* u = new Unit<T>(); // 创建一个新的链表单元
u->value = value; // 将值赋给新的单元
u->next = NULL; // 新的单元的下一个指针为空
if (head == NULL) // 链表为空
{
head = u; // 将头指针指向新的单元
prev = u; // 将前一个单元指针指向新的单元
}
else {
prev->next = u; // 将前一个单元的下一个指针指向新的单元
prev = u; // 更新前一个单元指针为新的单元
}
tail = u; // 更新尾指针tail 为新的单元
}
//析构函数 ~MyLink 负责释放链表中的内存
virtual ~MyLink() {
if (head != NULL) {
//这两行代码声明并初始化两个指针变量 prev 和 next。
//prev 指向链表的头指针 head,next 初始化为 NULL。
Unit<T>* prev = head;
Unit<T>* next = NULL;
while (prev != tail) { // 循环的目的是释放链表中每个单元所占用的内存。
//循环会遍历链表中的每个单元,直到 prev 指针指向尾指针 tail。
next = prev->next; // 将当前单元的下一个指针赋值给 next。
//将 prev 指针指向的单元的下一个单元的地址赋值给 next
delete prev; // 删除当前单元prev 所占用的内存。
prev = next; // 更新 prev 指针为下一个单元的地址,
//即将 next 的值赋给 prev,使 prev 指针指向链表中的下一个单元
}}}
};
MyLink 是模板元素 T的链表类,以 struct Unit 为一个个链表单元。
下面是以MyArray、MyLink 为基础完成一个共同显示函数:
从需要出发,逆向考虑,写一个泛型显示函数。
其中display 函数与具体的容器无直接关联,间接关联是必需的。
对于 MyArray 来Init 相当于对 T* 的操作;对于 MyLink 来说,Init 相当于对 Unit<T> * 的操作。因此引出了迭代器类,它仍然是一个模板类,对于本示例而言,模板参数是 T*或 Unit<T>*
template<class Init>//模板参数类型 Init 是一个指针
//函数名为 display,接受两个参数 start 和 end,它们都是类型为 Init 的指针
void display(Init start, Init end)//start 是起始指针,end 是结束指针
{
cout<<end1;
for(Init mid=start; mid!=end; mid++){
cout<< *mid<<"\t";}//输出指针 mid 所指向的值。*mid 表示解引用指针 mid,获取它所指向的值
cout<<end;//输出end的值
}
MyArray 对应的迭代器 ArrayIterator 类为下述代码:
template<class Init>
class ArrayIterator{
Init*init;
public:
ArrayIterator(Init *init){
this->init=init;
}
bool operator!=(ArrayIterator& it)
{
return this->initl!=it.init;}
void operator++(int){
init++;}
Init operator * ()
{
return *init;}};
ArrayIterator确实是对 Init * 的再封装,必须重载!=十十、* 操作符,这是由于 display 泛型显示函数用到这些操作符
需要在 MyArray 类中增加两个函数 Begin,End,用以获得起止送代指针
如下所示,过程可描述为:定义一个MyArray 对象 ary,向其中添加了 5个数据(1~5),获得起止。
迭代指针对象 startend,最后调用泛型显示函数 display 完成数据的输出。
创建了一个整数数组对象 ary,并使用 Begin 和 End 函数获取数组的起始和结束位置指针。
通过迭代器对象 start 和 end 指定了要遍历的范围,并调用 display 函数输出数组元素
//这是一个成员函数 Begin 的定义,返回类型为 T*,即指向类型 T 的指针。
//该函数用于返回数组的起始位置指针 m_pData
T* Begin() //起始选代指针
{
return m_pData;
}
//该函数用于返回数组的结束位置指针,起始位置指针 m_pData 加上有效元素数量 m_nValidSize
T*End()//结束选代指针
{
return m_pData+m_nValidsize;
}
int main(){
MyArray<int>ary;//创建了一个名为 ary 的 MyArray 类型对象,表示一个整数数组
for(int i=0;i<5;i++)
{
ary.Add(i+1):
}
//创建了两个名为 start 和 end 的 ArrayIterator<int> 类型对象,分别使用 ary.Begin() 和 ary.End() 初始化。
//这些对象用于指定迭代的起始和结束位置
ArrayIterator<int>start(ary.Begin()):
ArrayIterator<int>end(ary.End()):
cout<<"数组元素为:";
//使用 display(start, end) 函数来输出数组元素
display(start,end);
return 0;
}
同理,可知完成MyLink 选代功能步骤
(1)在MyLink 类中增加 Begin()End(),用以获取起止选代指针
Unit<T>*Begin(){
return head;//链表头指针
}
Unit<T>* End(){//链表尾指针
return tail;
}
(2)增加链表选代器类 LinkIterator,重载!=++、* 运算符
template<class Init>//模板参数为 Init,可以是任意类型
class LinkIterator{
Init * init;//这行代码声明了一个指针成员变量 init,类型为 Init*,即指向 Init 类型的指针。
public:
LinkIterator(Init *init){//这是构造函数的定义,接受一个 Init* 类型的指针作为参数,
//并将其赋值给成员变量 init
this->init=init;}
bool operator!= (LinkIterator& it){//重载了 != 运算符
return this->init!=it.init;}//接受一个 LinkIterator 类型的引用 it 作为参数,并比较成员变量 init 和 it.init 的值是否不相等
void operator++(int){//重载了后置递增运算符 ++ 的函数定义
init=init->next;}
Init operator * (){//重载了解引用运算符 * 的函数
return * init;}
};
可以看出operator!=operator * 重截内容与 ArrayIterator 中的内容是相同的,只operator十十中的内容不同。
对于链表而言,不像数组那样内存是连续的,是指针的转向因此绝对不能写成init=init十十,只能写成 init-init一>next
(3)重载全局函数 operator <<。
template<class T>
ostreams operator<<(ostream& os,Unit<T>& s){//这是函数的定义,它接受两个参数:一个输出流对象 os 和一个 Unit<T> 类型的引用 s
os<<s.value;//将 s.value 的值输出到输出流 os 中
return os;}//返回支持链式输出
这是因为当执行 display 函数中的 cut<<*mid 指时,对链表而言表意形式相当cout<<* Unit<int>,即输出 Unit<int>对象。
由于 Unit 是复合数据类型,不能直接出,因此必须重载上述函数,在其中输出简单数据类型 svalue
(4)链表类测试函数如下:
int main(){
int m=0;
MyLink<int>ml;//创建一个 MyLink<int> 类型的对象 ml,表示一个整数链表
for(int i=0;i<5;i++){//将整数 1 到 5 添加到链表中,通过调用 ml.Add(m) 实现
m=i+1;
ml.Add(m);}
使用 LinkIterator<Unit<int>> 类模板创建了两个迭代器对象 start 和 end,分别使用 ml.Begin() 和 ml.End() 初始化。
这些对象用于指定链表的迭代范围
LinkIterator<Unit<int>>start (ml,Begin());
LinkIterator<Unit<int>>end(ml.End());
display(start,end);//调用 display(start, end) 函数来输出链表元素
retrun 0;
}