C++ STL 基础及应用 迭代器

Posted 哈士奇超帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ STL 基础及应用 迭代器相关的知识,希望对你有一定的参考价值。

迭代器(Iterator)是 STL 的核心技术,提供了统一访问容器元素的方法,为编写通用算法提供了坚实的技术基础。

本章将带你编写一个自带迭代器的数组类和一个自带迭代器的链表类,模拟 STL 中的容器,这两个实例能够很清晰地展示 STL 的迭代器思想。并探讨迭代器类应该作为容器类的内部类的原因,然后对 STL 迭代器做一下归纳理解,最后阐述一下 STL 中真正的迭代器概况。

那么什么是迭代器呢?

迭代器即指针,可以是需要的任意类型,它的最大好处是可以使容器和算法分离。例如,有两个容器类,MyArray 是某类数组集合;MyLink 是某类型链表集合。它们都有显示、查询和排序等功能,常规思维是每个容器都有自己的显示、查询和排序等函数。但是细想,不同容器中完成相同功能代码的思路大致是相同的,那么能不能把它们抽象出来,多个容器仅对应一个显示、一个查询、一个排序函数呢?这是泛型思想发展的必然结果,于是迭代器思维就产生了。下面将通过实例来加深对迭代器的理解。

注意!对于下面代码中的模板使用、数组类编写有困惑的童鞋可以先看上一篇博文。

接下来编写带有迭代器的数组类链表类,模拟 STL 中的容器,展示 STL 的迭代器思维。

假设有一个动态数组类:

(比上一章节的数组类仅多了 Begin() 和 End() 方法)

//数组类
template <class T>
class MyArray
{
private:
    int m_nTotalSize;    //数组总长度
    int m_nValidSize;    //数组有效长度,即当前元素数
    T * m_pData;    //数据指针
public:
    //构造函数
    MyArray(int nSize=2)    //假设默认数组长度为2
    {
        m_pData=new T[nSize];
        m_nTotalSize=nSize;
        m_nValidSize=0;
    }
    //获取数组有效长度
    int GetValidSize()
    {
        return m_nValidSize;
    }
    //获取数组总长度
    int GetTotalSize()
    {
        return m_nTotalSize;
    }
    //返回某一位置元素
    T Get(int pos)
    {
        return m_pData[pos];
    }
    //添加一个元素
    void Add(T value)
    {
        if(m_nValidSize<m_nTotalSize)    //若数组未满
        {
            m_pData[m_nValidSize]=value;
            m_nValidSize++;
        }
        else    //数组满时动态增加数组大小
        {
            T * pOldData=m_pData;    //保存当前数据指针
            m_pData=new T[m_nTotalSize*2];    //原先数组空间大小扩大两倍
            for(int i=0;i<m_nTotalSize;i++)    //拷贝原先数据
            {
                m_pData[i]=pOldData[i];
            }
            m_nTotalSize*=2;    //当前数组总长度更新
            delete pOldData;    //释放旧数组占用的内存
            m_pData[m_nValidSize]=value;    //添加新元素
            m_nValidSize++;    //更新数组有效程度
        }
    }
    //返回头指针
    T * Begin()
    {
        return m_pData;
    }
    //返回尾指针
    T * End()
    {
        return m_pData+m_nValidSize;
    }
    virtual ~MyArray()
    {
    if(m_pData!=NULL)
      {
        delete []m_pData;
        m_pData=NULL;
       }
    }
};

然后有一个链表类:

//链表结点数据结构
template <class T>
struct Unit
{
    T value;
    Unit * next;
};

//链表类
template <class T>
class MyLink
{
private:
    Unit<T> * head;    //链表头
    Unit<T> * tail;    //链表尾
    Unit<T> * prev;    //指向最后一个结点
public:
    //构造函数
    MyLink()
    {
        head=tail=prev=NULL;
    }
    //添加一个结点
    void Add(const T &value)
    {
        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->next;
    }
    //返回头指针
    Unit<T>* Begin()
    {
        return head;
    }
    //返回尾指针
    Unit<T>* End()
    {
        return tail;
    }
    //析构函数
    virtual ~MyLink()
    {
        Unit<T> *prev=head;
        Unit<T> *next=NULL;
        while(prev!=tail)
        {
            next=prev->next;
            delete prev;
            prev=next;
        }
    }
};
可以看出,MyArray 是模板元素 T 的数组类,MyLink 是模板元素 T 的链表类,以 struct Unit 为一个链表单元。那么如何以 MyArray 和 MyLink 为基础,完成一个共同的显示函数呢?首先从需求出发,逆向考虑,

先写一个泛型显示函数,如下所示:

//泛型显示函数
template <class T>
void Display(T Start,T End)
{
    cout<<endl;
    for(T i=Start;i!=End;i++)
    {
        cout<<*i<<" ";
    }
    cout<<endl;
}
该函数的模板参数即为迭代器类型,该泛型函数能够输出从迭代器 Start 至 End 之间的所有元素,指针支持 ++ 和 * 操作。Display() 函数与具体的容器没有直接关联,但是间接关联是必需的,对于 MyArray 来说,该参数相当于数组类中的 T* 操作,对于 Mylink 来说,该参数相当于链表类当中的 Unit<T>* 操作,所以该参数就相当于容器中元素的指针。

于是写出 MyArray 的迭代器 ArrayIterator 类为:

template<class T>
class ArrayIterator
{
private:
    T * t;
public:
    //构造函数
    ArrayIterator(T * t)
    {
        this->t=t;
    }
    //重载!=
    bool operator != (const ArrayIterator &it)
    {
        return this->t!=it.t;
    }
    //重载++
    void operator ++ (int)
    {
        t++;
    }
    //重载取值符*
    T operator *()
    {
        return *t;
    }
};
可以看出,ArrayIterator 类是对 T* 的再封装,必须重载 operator !=、++、* 操作符,这是由于 Display 泛型显示函数需要用到这些操作符。

同样地,增加链表类迭代器 LinkIterator 类,重载 operator !=、++、* 操作符:

template <class T>
class LinkIterator
{
private:
    T *t;
public:
    //构造函数
    LinkIterator(T *t)
    {
        this->t=t;
    }
    //重载!=
    bool operator !=(const LinkIterator &it)
    {
        return this->t!=it.t;
    }
    //重载++
    void operator ++(int)
    {
        t=t->next;
    }
    //重载取值符*
    T operator *()
    {
        return *t;
    }
};
可以看出 operator !=、operator * 的重载内容与 ArrayIterator 一致,只有 operator ++ 不同,因为这里链表并不像数组一样连续存储,它是指针的转向,所有不能是 t++ ,而应该是 t=t->next。

细心的同学会发现,若有一个链表类 MyLink,当使用链表类迭代器 LinkIterator Start 为参数使用 Display() 函数时,假设有一步要执行 cout<<*Start 时,根据 LinkIterator 对 operator * 的重载,该式等于 cout<<Unit<T> ,但是 Unit<T> 是一个复合类型,cout 显然不能输出,cout 只能输出 Unit<T>.value,因此需要重载全局函数 operator << ,代码如下:

//重载全局函数operator <<
template <class T>
ostream& operator<<(ostream& os,const Unit<T> &u)
{
    os<<u.value;
    return os;
}

最后给出测试函数如下:

int main()
{
    MyArray<float> ary;
    MyLink<int> link;

    ary.Add(1.1);
    ary.Add(2.2);
    ary.Add(3.3);
    link.Add(1);
    link.Add(2);
    link.Add(3);

    ArrayIterator<float> aryStart(ary.Begin());
    ArrayIterator<float> aryEnd(ary.End());

    //注意!!这里两个尖括号要分开
    LinkIterator<Unit<int> > linkStart(link.Begin());
    LinkIterator<Unit<int> > linkEnd(link.End());

    Display(aryStart,aryEnd);
    Display(linkStart,linkEnd);
    return 0;
}
过程可以描述为:定义一个容器对象,向其中添加数据,获得迭代器对象 Start 、End,最后调用泛型显式函数 Display 完成数据的输出。

这里给出上述完整的代码:

#include <iostream>
using namespace std;

//数组类
template <class T>
class MyArray
{
private:
    int m_nTotalSize;    //数组总长度
    int m_nValidSize;    //数组有效长度,即当前元素数
    T * m_pData;    //数据指针
public:
    //构造函数
    MyArray(int nSize=2)    //假设默认数组长度为2
    {
        m_pData=new T[nSize];
        m_nTotalSize=nSize;
        m_nValidSize=0;
    }
    //获取数组有效长度
    int GetValidSize()
    {
        return m_nValidSize;
    }
    //获取数组总长度
    int GetTotalSize()
    {
        return m_nTotalSize;
    }
    //返回某一位置元素
    T Get(int pos)
    {
        return m_pData[pos];
    }
    //添加一个元素
    void Add(T value)
    {
        if(m_nValidSize<m_nTotalSize)    //若数组未满
        {
            m_pData[m_nValidSize]=value;
            m_nValidSize++;
        }
        else    //数组满时动态增加数组大小
        {
            T * pOldData=m_pData;    //保存当前数据指针
            m_pData=new T[m_nTotalSize*2];    //原先数组空间大小扩大两倍
            for(int i=0;i<m_nTotalSize;i++)    //拷贝原先数据
            {
                m_pData[i]=pOldData[i];
            }
            m_nTotalSize*=2;    //当前数组总长度更新
            delete pOldData;    //释放旧数组占用的内存
            m_pData[m_nValidSize]=value;    //添加新元素
            m_nValidSize++;    //更新数组有效程度
        }
    }
    //返回头指针
    T * Begin()
    {
        return m_pData;
    }
    //返回尾指针
    T * End()
    {
        return m_pData+m_nValidSize;
    }
    virtual ~MyArray()
    {
    if(m_pData!=NULL)
      {
        delete []m_pData;
        m_pData=NULL;
       }
    }
};

//链表结点数据结构
template <class T>
struct Unit
{
    T value;
    Unit * next;
};

//链表类
template <class T>
class MyLink
{
private:
    Unit<T> * head;    //链表头
    Unit<T> * tail;    //链表尾
    Unit<T> * prev;    //指向最后一个结点
public:
    //构造函数
    MyLink()
    {
        head=tail=prev=NULL;
    }
    //添加一个结点
    void Add(const T &value)
    {
        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->next;
    }
    //返回头指针
    Unit<T>* Begin()
    {
        return head;
    }
    //返回尾指针
    Unit<T>* End()
    {
        return tail;
    }
    //析构函数
    virtual ~MyLink()
    {
        Unit<T> *prev=head;
        Unit<T> *next=NULL;
        while(prev!=tail)
        {
            next=prev->next;
            delete prev;
            prev=next;
        }
    }
};

//泛型显示函数
template <class T>
void Display(T Start,T End)
{
    cout<<endl;
    for(T i=Start;i!=End;i++)
    {
        cout<<*i<<" ";
    }
    cout<<endl;
}

template<class T>
class ArrayIterator
{
private:
    T * t;
public:
    //构造函数
    ArrayIterator(T * t)
    {
        this->t=t;
    }
    //重载!=
    bool operator != (const ArrayIterator &it)
    {
        return this->t!=it.t;
    }
    //重载++
    void operator ++ (int)
    {
        t++;
    }
    //重载取值符*
    T operator *()
    {
        return *t;
    }
};

template <class T>
class LinkIterator
{
private:
    T *t;
public:
    //构造函数
    LinkIterator(T *t)
    {
        this->t=t;
    }
    //重载!=
    bool operator !=(const LinkIterator &it)
    {
        return this->t!=it.t;
    }
    //重载++
    void operator ++(int)
    {
        t=t->next;
    }
    //重载取值符*
    T operator *()
    {
        return *t;
    }
};

//重载全局函数operator <<
template <class T>
ostream& operator<<(ostream& os,const Unit<T> &u)
{
    os<<u.value;
    return os;
}

int main()
{
    MyArray<float> ary;
    MyLink<int> link;

    ary.Add(1.1);
    ary.Add(2.2);
    ary.Add(3.3);
    link.Add(1);
    link.Add(2);
    link.Add(3);

    ArrayIterator<float> aryStart(ary.Begin());
    ArrayIterator<float> aryEnd(ary.End());

    //注意!!这里两个尖括号要分开
    LinkIterator<Unit<int> > linkStart(link.Begin());
    LinkIterator<Unit<int> > linkEnd(link.End());

    Display(aryStart,aryEnd);
    Display(linkStart,linkEnd);
    return 0;
}

关于迭代器类位置的探讨

每一种容器对应一种迭代器,如数组迭代器只适用于数组,那么基于封装的思想,迭代器类自然就必须位于它所对应容器类的内部,即成为容器类的内部类。

将数组迭代器 ArrayIterator 放入数组类 MyArray 中:

//数组类
template <class T>
class MyArray
{
private:
    int m_nTotalSize;    //数组总长度
    int m_nValidSize;    //数组有效长度,即当前元素数
    T * m_pData;    //数据指针
public:
    //构造函数
    MyArray(int nSize=2)    //假设默认数组长度为2
    {
        m_pData=new T[nSize];
        m_nTotalSize=nSize;
        m_nValidSize=0;
    }
    //数组迭代器
    class ArrayIterator
    {
	private:
		T * t;
	public:
		//构造函数
		ArrayIterator(T * t)
		{
			this->t=t;
		}
		//重载!=
		bool operator != (const ArrayIterator &it)
		{
			return this->t!=it.t;
		}
		//重载++
		void operator ++ (int)
		{
			t++;
		}
		//重载取值符*
		T operator *()
		{
			return *t;
		}
    };
    //获取数组有效长度
    int GetValidSize()
    {
        return m_nValidSize;
    }
    //获取数组总长度
    int GetTotalSize()
    {
        return m_nTotalSize;
    }
    //返回某一位置元素
    T Get(int pos)
    {
        return m_pData[pos];
    }
    //添加一个元素
    void Add(T value)
    {
        if(m_nValidSize<m_nTotalSize)    //若数组未满
        {
            m_pData[m_nValidSize]=value;
            m_nValidSize++;
        }
        else    //数组满时动态增加数组大小
        {
            T * pOldData=m_pData;    //保存当前数据指针
            m_pData=new T[m_nTotalSize*2];    //原先数组空间大小扩大两倍
            for(int i=0;i<m_nTotalSize;i++)    //拷贝原先数据
            {
                m_pData[i]=pOldData[i];
            }
            m_nTotalSize*=2;    //当前数组总长度更新
            delete pOldData;    //释放旧数组占用的内存
            m_pData[m_nValidSize]=value;    //添加新元素
            m_nValidSize++;    //更新数组有效程度
        }
    }
    //返回头指针
    T * Begin()
    {
        return m_pData;
    }
    //返回尾指针
    T * End()
    {
        return m_pData+m_nValidSize;
    }
    //析构函数
    virtual ~MyArray()
    {
    if(m_pData!=NULL)
      {
        delete []m_pData;
        m_pData=NULL;
       }
    }
};
注意!与原代码相比少了很多尖括号,并且由于模板编程的规则有好几处稍许变化的地方,希望读者能留心发现。

然后将链表迭代器 LinkIterator 放入链表类 MyLink 中:

//链表类
template <class T>
class MyLink
{
public:
    //链表结点数据结构
    struct Unit
    {
        T value;
        Unit * next;
    };
	//链表类迭代器
    class LinkIterator
    {
        private:
            Unit *u;
        public:
            //构造函数
            LinkIterator(Unit *u)
            {
                this->u=u;
            }
            //重载!=
            bool operator !=(const LinkIterator &it)
            {
                return this->u!=it.u;
            }
            //重载++
            void operator ++(int)
            {
                u=u->next;
            }
            //重载取值符*
            Unit operator *()
            {
                return *this->u;
            }
    };
    //重载全局函数operator <<
    friend ostream& operator<<(ostream& os,typename MyLink<T>::Unit& u)
    {
        os<<u.value;
        return os;
    }
    //构造函数
    MyLink()
    {
        head=tail=prev=NULL;
    }
    //添加一个结点
    void Add(const T &value)
    {
        Unit *u=new Unit();
        u->value=value;
        u->next=NULL;
        if(head==NULL)
        {
            head=u;
            prev=u;
        }
        else
        {
            prev->next=u;
            prev=u;
        }
        tail=u->next;
    }
    //返回头指针
    Unit * Begin()
    {
        return head;
    }
    //返回尾指针
    Unit * End()
    {
        return tail;
    }
    //析构函数
    virtual ~MyLink()
    {
        if(head!=NULL)
        {
            Unit *prev=head;
            Unit *next=NULL;
            while(prev!=tail)
            {
                next=prev->next;
                delete prev;
                prev=next;
            }
        }
    }
private:
    Unit * head;    //链表头
    Unit * tail;    //链表尾
    Unit * prev;    //指向最后一个结点
};
由于该 struct Unit 结构体为该链表类专用,因此也一并放进了 MyArray 类中。值得注意的是,全局函数 cout<< 的重载也被放了进来,因为只有用到这个链表类时,才会有 cout<<Unit<T> 的必要,因此要一并封装。另外,为了能使 ostream 对象访问该类的私有变量,必须将这个重载函数用 friend 关键字写为友元函数。

Display()函数不用变化,测试函数相应地修改为:

int main()
{
    MyArray<float> ary;
    MyLink<int> link;

    ary.Add(1.1f);
    ary.Add(2.2f);
    ary.Add(3.3f);

    link.Add(1);
    link.Add(2);
    link.Add(3);

    MyArray<float>::ArrayIterator aryStart(ary.Begin());
    MyArray<float>::ArrayIterator aryEnd(ary.End());

    MyLink<int>::LinkIterator linkStart(link.Begin());
    MyLink<int>::LinkIterator linkEnd(link.End());

    Display(aryStart,aryEnd);
    Display(linkStart,linkEnd);

    return 0;
}

最后给上封装之后的完成代码,一共只有两个类 MyArray 与 MyLink,迭代器被封装在了类中,然后有一个公共的显示函数 Display() 和一个测试函数 main(),代码如下:

#include <iostream>
using namespace std;

//数组类
template <class T>
class MyArray
{
private:
    int m_nTotalSize;    //数组总长度
    int m_nValidSize;    //数组有效长度,即当前元素数
    T * m_pData;    //数据指针
public:
    //构造函数
    MyArray(int nSize=2)    //假设默认数组长度为2
    {
        m_pData=new T[nSize];
        m_nTotalSize=nSize;
        m_nValidSize=0;
    }
    //数组迭代器
    class ArrayIterator
    {
	private:
		T * t;
	public:
		//构造函数
		ArrayIterator(T * t)
		{
			this->t=t;
		}
		//重载!=
		bool operator != (const ArrayIterator &it)
		{
			return this->t!=it.t;
		}
		//重载++
		void operator ++ (int)
		{
			t++;
		}
		//重载取值符*
		T operator *()
		{
			return *t;
		}
    };
    //获取数组有效长度
    int GetValidSize()
    {
        return m_nValidSize;
    }
    //获取数组总长度
    int GetTotalSize()
    {
        return m_nTotalSize;
    }
    //返回某一位置元素
    T Get(int pos)
    {
        return m_pData[pos];
    }
    //添加一个元素
    void Add(T value)
    {
        if(m_nValidSize<m_nTotalSize)    //若数组未满
        {
            m_pData[m_nValidSize]=value;
            m_nValidSize++;
        }
        else    //数组满时动态增加数组大小
        {
            T * pOldData=m_pData;    //保存当前数据指针
            m_pData=new T[m_nTotalSize*2];    //原先数组空间大小扩大两倍
            for(int i=0;i<m_nTotalSize;i++)    //拷贝原先数据
            {
                m_pData[i]=pOldData[i];
            }
            m_nTotalSize*=2;    //当前数组总长度更新
            delete pOldData;    //释放旧数组占用的内存
            m_pData[m_nValidSize]=value;    //添加新元素
            m_nValidSize++;    //更新数组有效程度
        }
    }
    //返回头指针
    T * Begin()
    {
        return m_pData;
    }
    //返回尾指针
    T * End()
    {
        return m_pData+m_nValidSize;
    }
    //析构函数
    virtual ~MyArray()
    {
    if(m_pData!=NULL)
      {
        delete []m_pData;
        m_pData=NULL;
       }
    }
};

//链表类
template <class T>
class MyLink
{
public:
    //链表结点数据结构
    struct Unit
    {
        T value;
        Unit * next;
    };
	//链表类迭代器
    class LinkIterator
    {
        private:
            Unit *u;
        public:
            //构造函数
            LinkIterator(Unit *u)
            {
                this->u=u;
            }
            //重载!=
            bool operator !=(const LinkIterator &it)
            {
                return this->u!=it.u;
            }
            //重载++
            void operator ++(int)
            {
                u=u->next;
            }
            //重载取值符*
            Unit operator *()
            {
                return *this->u;
            }
    };
    //重载全局函数operator <<
    friend ostream& operator<<(ostream& os,typename MyLink<T>::Unit& u)
    {
        os<<u.value;
        return os;
    }
    //构造函数
    MyLink()
    {
        head=tail=prev=NULL;
    }
    //添加一个结点
    void Add(const T &value)
    {
        Unit *u=new Unit();
        u->value=value;
        u->next=NULL;
        if(head==NULL)
        {
            head=u;
            prev=u;
        }
        else
        {
            prev->next=u;
            prev=u;
        }
        tail=u->next;
    }
    //返回头指针
    Unit * Begin()
    {
        return head;
    }
    //返回尾指针
    Unit * End()
    {
        return tail;
    }
    //析构函数
    virtual ~MyLink()
    {
        if(head!=NULL)
        {
            Unit *prev=head;
            Unit *next=NULL;
            while(prev!=tail)
            {
                next=prev->next;
                delete prev;
                prev=next;
            }
        }
    }
private:
    Unit * head;    //链表头
    Unit * tail;    //链表尾
    Unit * prev;    //指向最后一个结点
};

//泛型显示函数
template <class T>
void Display(T Start,T End)
{
    cout<<endl;
    for(T i=Start;i!=End;i++)
    {
        cout<<*i;
        cout<<" ";
    }
    cout<<endl;
}

int main()
{
    MyArray<float> ary;
    MyLink<int> link;

    ary.Add(1.1f);
    ary.Add(2.2f);
    ary.Add(3.3f);

    link.Add(1);
    link.Add(2);
    link.Add(3);

    MyArray<float>::ArrayIterator aryStart(ary.Begin());
    MyArray<float>::ArrayIterator aryEnd(ary.End());

    MyLink<int>::LinkIterator linkStart(link.Begin());
    MyLink<int>::LinkIterator linkEnd(link.End());

    Display(aryStart,aryEnd);
    Display(linkStart,linkEnd);

    return 0;
}
注意!上述代码我在 VS 2010 下编译正确,运行正确,但在 CodeBlocks 用 GNU GCC 编译器编译时报错,难道是 GCC 编译器的bug?希望有知道的大神赐教。

恭喜!你已经能够编写一个带有迭代器的简易容器了,STL 中的容器也是以此为雏形完善代码而来的,现在你应该对于 STL 的迭代器概念有一个整体的认识了,接下来总结一下关于迭代器的思想。

进一步理解迭代器

前面已经详细讨论了迭代器的编程思路,如果用图形来描述一下就是: