STL中的线性表有3种实现:顺序实现vector,双链表的实现list和优化实现deque。这些类都可以用迭代器来访问。由于每个类的实现方式是不同的,因此这些类对应的迭代器类的实现也是不同的。为了便于用户使用,STL将迭代器类定义为相应容器内的公有的内嵌类,并且对于所有的迭代器,他们的类名都是相同的。因此,用户不需要分别记忆某个类的迭代器的类名是什么。每个容器提供两个迭代器类:iterator和const_iterator,前者可以通过迭代器修改它指向的数据元素,而后者只能读取它指向的元素而不能修改。
vector 类是可动态增长的数组存储线性表的数据元素。list是采用双链表实现的线性表类,他们都支持线性表的常规操作并根据实现的特点适当增加操作。他们共同支持的操作如下图:
函数 | 作用 |
int size() const | 返回容器中的元素个数 |
void clear() | 删除容器中的所有元素 |
bool empty() | 判断容器是否为空,是空返回true,否则返回false |
void push_back(const object &x) | 将x添加到表尾 |
void pop_back() | 删除表尾元素 |
const object &back() const | 返回表尾元素值 |
const object &front() const | 返回表头元素值 |
除了这些操作,每个类还根据自己的特点增加了一些特有的操作。list是用双链表实现的,在表头插入或者删除十分方便,因此增加了在表头和删除元素的操作 void push_front(const object &x) 和 void pop_front()。 vector是用数组实现的,很容易实现下标运算符重载以及一些数组特有的操作。相比于链表而言,顺序表还有容量的概念,所以还有一些针对容量的操作。vector类增加的操作如下表:
函数 | 作用 |
object &operator[](int idx) | 返回vector中下标为idx的对象,该操作无边界检查 |
object &at(int idx) | 返回vector中下标为idx的对象,但有边界检查 |
int capacity() | 返回vector中数组的容量 |
void reserve(int newCapacity) | 设置数组容量 |
vector和list都有两个迭代器类:iterator和const_iterator,对于iterator指向的对象可以做任何操作,对const_iterator指向的对象不能做修改操作。这两个迭代器类都被定义成相同容器的内嵌类,因为迭代器是要给用户使用的,因此被设计为公有的内嵌类。vector和list类中的迭代器相关操作如表1,迭代器类的本身的操作如表2
函数 | 作用 |
iterator begin() | 返回表头的位置 |
const_iterator begin() | 返回表头的位置 |
iterator end() | 返回表尾的位置 |
const_iterator end() | 返回表尾的位置 |
iterator insert(iterator pos,const object&x) | 在迭代器pos给出的位置前插入x |
iterator erase(iterator pos) | 删除迭代器pos给出的位置的元素 |
iterator erase(iterator start,iterator end) | 删除从start到end之间的元素,但不包括end |
函数 | 作用 |
itr++, ++itr | 迭代器指向下一个元素 |
*itr | 取迭代器指向的元素 |
itr1==itr2 | 判断两个迭代器是否指向同一元素,是返回true,否则返回false |
itr!==itr2 | 判断两个迭代器是否指向不同元素,是返回true,否则返回false |
vector类定义在头文件vector中,list类定义在头文件list中,这两个头文件都定义在名字空间std中。代码清单1给出了vector类的一个使用实例:
//代码清单1
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v; //定义一个整型的顺序容器 v
cout<<"the initial size of v is: "<<v.size()
<<"\n the initial capacity of v is: "<<v.capacity()<<endl;
v.push_back(2);
cout<<"\n after push 2, capacity of v is:" <<v.capacity()<<endl;
v.push_back(2);
cout<<"\n after push 3, capacity of v is :"<<v.capacity()<<endl;
v.push_back(4);
cout<<"\n after push 4,capacity of v is :"<<v.capacity()<<endl;
//用下标访问v的元素
cout<<"\n contents of v using iterator notation:";
vector<int>::const_iterator p;
for(p=v.begin();p!=v.end();p++)
cout<<*p<<" ";
cout<<endl;
return 0;
}
代码清单1用到vector类,因此必须包括头文件vector。程序定义了一个整型的vector类对象v,然后分别输出表中元素个数和存储元素的数组规模,由于此时表尾空表,因此输出:
the initial size of v is: 0
the initial capacity of v is: 0
表示表中元素个数和数组规模都为0. 然后分别再表尾中插入2、3、4,并且输出插入2、3、4以后数组的规模。此时产生三行输出:
After push 2,capacity of v is: 1
After push 3,capacity of v is: 2
After push 4,capacity of v is: 4
从表中可以看出来,随着容器中元素的增加,数组的容量也在增加。在插入了3个元素之后再次输出表长和容量,得到两行输出:
the size of v is: 3
the capacity of v is: 4
接下来利用下标重载输出容器中的所有元素,该过程与输出一个普通数组完全一样,只是数组中的元素个数可以通过函数size或得。输出的结果是
contents of v using []: 2 3 4
最后利用迭代器输出容器中的所有元素。首先定义一个vector类的迭代器p,由于该迭代器是用来访问容器中的对象,而不是修改容器中的对象,因此采用 const_iterator. 然后通过一个for循环输出容器中的对象,输出的结果与采用下标变量的输出完全一样。输出的结果是:
contents of v using iterator notation : 2 3 4
代码清单2 给出list类的一个使用实例:
//代码清单2
#include<iostream>
#include<list>
using namespace std;
template<class T>
void printall(const list<T> &v)
int main(){
list<int> v1;
v1.push_front(1);
v1.push_front(2)
v1.push_back(4);
v1.push_back(3);
printall(v1);
list<int>::iterator itr=v1.begin(),itre=v1.end();
for(int i=5; itr!=itre; ++itr,++i)
v1.insert(itr,i);
printall(v1)
return 0;
}
template<class T>
void printall(const list<T> &v){
if(v.empty())
cout<<"\n the list is empty";
else{
typename list<T>::const_iterator itr,itre;
itr=v.begin();
itre=v.end();
do(cout<<*itr<<" ";
++itr;
}while (itr!=itre);
}
cout<<endl;
}
代码清单2需要用到list库,因此需要包含list头文件。 main函数定义了一个整型的list对象v1,然后执行了两次表头插入,分别插入1、2,又执行两次表尾插入,分别插入4、3,调用printall输出v1中的所有对象。 此时输出为2 1 4 3. 然后演示了通过迭代器的定位插入。先定义两个迭代器类的对象:itr指向起始节点,itre指向尾结点。从起始节点开始,一个间隔一个,依次插入了5、6、7、8.最后再次调用printall输出v1 的所有对象,此时输出为 5 2 6 1 7 4 8 3.
list对象中的元素只能通过迭代器访问。由于main函数中有两个地方需要输出v1 中所有对象,为此定义了函数模板printall。该函数定义了两个迭代器对象:itr指向起始节点,itre指向尾结点。由于这两个迭代器对象是用来输出容器中的元素,不会改变容器中的元素,因此这两个迭代器采用的是const_iterator迭代器。函数首先调用empty函数判断容器是否为空,如果是非空,那么就输出容器中的所有的对象
vector类和list类都内嵌了两个迭代器,而且两个类的迭代器类的名字是相同的。这种实现方法可以简化类的设计和使用。再设计类的时候,设计者不必为选择迭代器的类名而烦恼,不必担心会和其他容器的迭代器重名。用户使用时也非常的方便,不管使用哪一容器,只需记住迭代器的类名是iterator和const_iterator。要使用vector类的迭代器,可定义vector::iterator类的对象,要使用list类的迭代器,可定义list::iterator的对象。
deque 也是C++用于表示线性结构的标准的容器,是一种经过了优化的容器。它既可以通过下标访问,也可以通过迭代器访问。它的两端操作(表头和表尾的插入或者删除)的效率类似于list,下标操作的效率接近于vector。但在中间插入和删除的效率与vector一样低。它常被用为实现栈和队列。