博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

STL通用编程技术—Standard Template Library

Posted on 2011-08-19 17:19  yuanzfy  阅读(354)  评论(0)    收藏  举报

STL提供了一组表示容器、迭代器、函数对象、算法的模板。

  容器可以存储若干同类型数据,类似于数组。

  迭代器能够便利容器的对象,与数组的指针类似。

  算法是指完成特定任务的通用算法。

  函数对象是类似函数的对象,可以是类对象也可以是函数指针。

迭代器

首先说说迭代器,迭代器就是对容器对象定义的一个广义指针,用于象指针一样方便的读写容器的数据。

C++的模板使得算法可以独立于存储的数据类型,假设一个模板可以接收int型的数组,也可以接收string型的数组,但是他却不可以接收一个链表。那是因为数组和链表数据遍历方式是不同的,数组指针通过“++”就可以访问下一个值,但是链表要通过->next得到下一个值的指针。

为了能够统一数据遍历方式而引入迭代器,迭代器使得算法独立于使用的容器本身的数据结构。既然如此,迭代器需要一些通用的特性:

  1. 应该定义*p操作用于访问迭代器所引用的值。

  2. 应该定义p=q的复制操作。

  3. 应该定义迭代器的比较操作p==q和p!=q。

  4. 迭代器必须能够访问容器内的所有元素,并且定义++p和p++来遍历容器。

既然如此,每个容器的迭代器实现方式肯定不一样,就链表而言,需要对++运算符进行重载,并在内部实现->next操作。

所以,每个容器类定义了相应的迭代器类型,可以把迭代器当做是容器类的内部类来看待,在内部实现自己的操作符重载方式。

int a[10]={0,1,2,3,4,5,6,7,8,9};

std::vector<int> vec(&a[0],&a[10]);//定义一个vector容器,并且用数组中的值初始化vec。

std::vector<int>::iterator pr;//定义一个int型vector容器的迭代器
for(pr=vec.begin();pr!=vec.end();pr++)
    std::cout<<*pr<<' ';

程序输出:

0 1 2 3 4 5 6 7 8 9

指针是一种简单的迭代器,迭代器是广义指针,所以可以讲STL算法用于数组。

注意这里用到了一个不存在的a[10]。容器为了得到类似遍历指针的属性,设置了超尾这一概念,是一个空的迭代器,类似于C-风格字符串最后一个字符后边都会有一个空字符。

初始化的时候也只是把a[0]到a[9]这十个值复制到vec中。

从上述程序可以看出,我们使用了了独立于容器本身数据结构的迭代器。如下,我们声明用于遍历列表的迭代器:

std::list<int>::iterator pr;

for(pr=li.begin();pr!=li.end();pr++)
    std::cout<<*pr<<' ';

可以看出,vector数组和list链表的遍历方式统一了。

迭代器权限类型

迭代器根据权限不同分为多种:

  1. 输入迭代器 :仅仅通过++操作符遍历并读出容器元素,但是不能保证第二次遍历时顺序不变。

  2. 输出迭代器 :仅仅通过++操作符遍历并改变容器元素。

  3. 正向迭代器 :与前两者类似,仅适用++操作符遍历,但是他可以保证按照相同的顺序遍历一系列值。

  4. 双向迭代器 :与正向迭代器类似,但是同时支持递减操作符。例如list的迭代器。

  5. 随机访问迭代器 :拥有以上所有权限,并可以随机访问。例如vector的迭代器。

不同类型的容器封装了不同的迭代器,根据特定迭代器类型编写的算法只能使用拥有这种特定迭代器权限的迭代器,所以所有迭代器都可以接收随机访问迭代器的参数。

迭代器操作类型

根据迭代器操作特点,有以下几种常用的迭代器:

  1. 常规迭代器 :iterator

  2. 反向迭代器 :reverse_iterator

  3. 插入迭代器 :insert_iterator

  4. 首部插入迭代器 :front_insert_iterator

  5. 尾部插入迭代器 :back_insert_iterator
这里要特别注意,前两种迭代器和后三种迭代器使用方法不一样。为了使用后三种迭代器需要#include<iterator>

int a[10]={0,1,2,3,4,5,6,7,8,9};
std::vector<int> vec(&a[0],&a[10]);
std::vector<int>::iterator pr;//定义一个int型vector容器的迭代器

std::vector<int>::reverse_iterator rpr;//定义一个反向迭代器
//std::vector<int>::back_insert_iterator pp;错误的定义方式
std::back_insert_iterator<std::vector<int>> bpr(vec);//定义一个尾端插入迭代器

std::insert_iterator<std::vector<int>> ipr(vec,vec.begin());
copy(&a[5],&a[7],bpr);//拷贝a[5]和a[6]到vec尾部

ipr=std::insert_iterator<std::vector<int>> (vec,vec.begin()+3);//注意这句话的重要性
copy(&a[1],&a[2],ipr);
for(pr=vec.begin();pr!=vec.end();pr++)//正向打印vec,也可以用for_each(start;end;function)遍历容器,这样不需要显示的利用迭代器
    std::cout<<*pr<<' ';
std::cout<<std::endl;
for(rpr=vec.rbegin();rpr!=vec.rend();++rpr)//反向打印vec
    std::cout<<*rpr<<' ';

程序输出:

0 1 2 1 3 4 5 6 7 8 9 5 6
6 5 9 8 7 6 5 4 3 1 2 1 0

先说一下iterator和reverse_iterator,这两个迭代器是在vector内部定义的,所以定义的时候可以直接用::定义。

reverse_iterator有一个和iterator不同的地方,就reverse_iterator进行递加时相当于递减,可以用双向链表的正向反向链表来理解。

rbegin()和end()返回相同的值,但是类型不同,返回的是方向迭代器类型,同理rend()和begin()。

注意:pr++和++rpr,因为rbegin()指向超尾,所以要先进行++操作,在进行解除引用操作' *'。

再说back_insert_iterator,这三种迭代器定义在iterator.h中,和iterator不同的是,他是外部定义的迭代器。

所以定义的时候需要将容器类型传给back_insert_iterator,这样迭代器才能使用合适的方法。

程序中对ipr重新赋值的语句是不能少的,否则会出现运行错误。至于为什么在尾端插入数据为什么首部迭代器会受影响,机制不清楚。

这也可以看出,STL对迭代器的管理不是透明的,所以尽量不要用显示的迭代器访问数据,而是用尽可能地使用STL函数begin()、end()等等。

基本容器特征

X代表容器,T代表容器存储的对象类型:

  X::iterator迭代器类型、X::value_type存储对象类型T 。

  X a;X b(a);X ();X b=a;

  a.begin();

  a.end();

  a.swap(b);交换内容

  a.size();

  a==b;a!=b;

序列

序列要求这些容器的迭代器至少是正向迭代器,包裹deque、list、queue、priority_queue、stack、vector

序列基本要求,i j p q为迭代器:

  X a(n,t);//声明一个名为a由n个t值组成的序列

  X (n,t);//同上的匿名序列

  X a(i,j);//声明一个名为a的序列,并且初始化为区间(i,j)的内容,i,j可以使简单数组的指针

  X (i,j);//同上的匿名序列

  a.insert(p,t);

  a.insert(p,n,t);

  a.insert(p,i,j);

  a.erase(p );

  a.erase(p,q);

  a.clear();

序列可选要求:

  a.push_front(t); //list queue 注意vector没有这个函数,因为效率太低

  a.push_back(t); //list queue vector

  a.pop_front();   //list queue  注意vector没有这个函数,因为效率太低

  a.pop_back();   //list queue vector

  a[n]和a.at(n)    //a.at(n)检查边界可以产生out_of_range异常