STL学习小结(原创:桑英硕 )

类别:编程语言 点击:0 评论:0 推荐:

提供了类型安全、高效而易用特性的STL无疑是最值得C++程序员骄傲的部分。每一个C++程序员都应该好好学习STL:).   

STL(Standard Template Library 标准模板库)是C++标准库的一个重要组成部分,它由Stepanov and Lee等人最先开发,它是与C++几乎同时开始开发的;一开始STL选择了Ada作为实现语言,但Ada有点不争气,最后他们选择了C++,一个原因了,C++中已经有了模板。在后来,STL又被添加进了C++库。1996年,惠普公司又免费公开了STL,为STL的推广做了很大的贡献。

STL大体上包括container(容器)、algorithm(算法)和iterator(迭代器),容器和算法通过迭代器可以进行无缝连接。  

STL体现了范型编程的思想,它具有高度的可重用性,高性能,高移植性。程序员不用思考具体实现过程,只要能够熟练的应用就OK了。(有兴趣研究具体实现的,可以看侯捷老师编著的《STL源码剖析》)这样他们就可以把精力放在程序开发的别的方面。

我非常佩服创造STL的那些计算机和数学精英。因为他们做了一件非常辛苦的事情―――抽象概念。而STL就是通过把容器抽象为统一的接口,算法利用这个接口,通过迭代器来操纵容器。因为接口不变,实现的容器可以随意更改。这样,就为编程、调试和扩展提供了便利。也许有一天,我们生产软件的时候也可以想DIY一台PC一样简单,只要拿来相应的实现模块,通过简单的拼装和调试就可以创造一个软件。这是多么令人兴奋的一件事^_^.不过,到时候,可能会有很多程序员失业了。呵呵,毕竟编写类库不需要很多的人员。

虽然STL不是面向对象的,但,我想,每个人都会为它的创造力和高性能而感到兴奋和折服。

一、容器 
作为STL的最主要组成部分--容器,分为向量(vector),双端队列(deque),表(list),队列(queue),堆栈(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。

容器    

特性

所在头文件

向量vector

可以用常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具有常数时间复杂度,对任意项的插入和删除就有的时间复杂度与到末尾的距离成正比,尤其对向量头的添加和删除的代价是惊人的高的

<vector>

双端队列deque

基本上与向量相同,唯一的不同是,其在序列头部插入和删除操作也具有常量时间复杂度

<deque>

表list

对任意元素的访问与对两端的距离成正比,但对某个位置上插入和删除一个项的花费为常数时间。

<list>

队列queue

插入只可以在尾部进行,删除、检索和修改只允许从头部进行。按照先进先出的原则。

<queue>

堆栈stack

堆栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项。即按照后进先出的原则

<stack>

集合set

由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,具有快速查找的功能。但是它是以牺牲插入车删除操作的效率为代价的

<set>

多重集合multiset

和集合基本相同,但可以支持重复元素具有快速查找能力

<set>

映射map

由{键,值}对组成的集合,以某种作用于键对上的谓词排列。具有快速查找能力

<map>

多重集合multimap

比起映射,一个键可以对应多了值。具有快速查找能力

<map>

考虑到不同的实际需要,更主要的是效率的需要,我们可以选择不同的容器来实现我们的程序,以此达到我们提高性能的目的。这也是用好STL的一个难点,但这也是关键。

二、算法
算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。<algorithm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象。

STL的算法也是非常优秀的,它们大部分都是类属的,基本上都用到了C++的模板来实现,这样,很多相似的函数就不用自己写了,只要用函数模板就OK了。

我们使用算法的时候,要针对不同的容器,比如:对集合的查找,最好不要用通用函数find(),它对集合使用的时候,性能非常的差,最好用集合自带的find()函数,它针对了集合进行了优化,性能非常的高。

三、迭代器
它的具体实现在<itertator> 中,我们完全可以不管迭代器类是怎么实现的,大多数的时候,把它理解为指针是没有问题的(指针是迭代器的一个特例,它也属于迭代器),但是,决不能完全这么做。

迭代器功能(Abilities Of Iterator Gategories)

输入迭代器

Input iterator

向前读

Reads forward

istream

输出迭代器

Output iterator

向前写

Writes forward

ostream,inserter

前向迭代器

Forward iterator

向前读写

Read and Writes forward

 

双向迭代器

Bidirectional iterator

向前向后读写

Read and Writes forward and

backward

list,set,multiset,map,mul

timap

随机迭代器

Random access iterator

随机读写

Read and Write with random

access

vector,deque,array,string

由此可见,指针和迭代器还是有很大差别的。和指针最接近的就是随机访问迭代器。下面是一个我编写的小例子:功能是分别对数组,向量,表,多重集合进行插入操作,对每个容器插入100万个随机整数;

#include<iostream>
#include<iterator>
#include<vector>
#include<list>
#include<set>
#include<time.h>
#include<conio.h>
#include<algorithm>
using namespace std;
template<typename T>
void arrayInsert(T*a,T*s,long size)    //向数组插入数据
{
   //for(long i=0;i<10;i++)  // //好像数组支持不到100万,我们就算10万的
                                         //最后在把把结果乘以10吧,
   //{
       for(long k=0;k<size;k++)
       {
           a[k]=s[k]; 
       }
   //}
}
template<typename T>
void vectorInsert( vector<T> *v,T*s,long size)     //向向量插入数据
{
   for(int i=0;i<10;i++)
   {
       for(long k=0;k<size;k++)
       {
           v->push_back(s[k]);
      }
   }
}
template<typename T>
void listInsert(list<T>*l,T*s,long size)   //向表插入数据
{
   for(int i=0;i<10;i++)
   {
       for(long k=0;k<size;k++)
       {
           l->push_back(s[k]);
       }  
   }
}
template<class T>
void multisetInsert(multiset<T>*s1,T*s,long size)   //向多重集合插入数据
{
   for(int i=0;i<10;i++)
   {
       for(long k=0;k<size;k++)
       {
           s1->insert(s[k]);  
       }
   }
}
int* genIntData(long size)                  //生成随机数
{
   int* data=new int[size];
   generate(&data[0],&data[size],rand);
   return data;

}  
int main(void)
{
   const long size=100000;
   int* s_data,array1[size];
   double begin,end;
   s_data=genIntData(size);
   vector<int> vector1;
   list<int> list1;
   multiset<int> multiset1;
   clock();
   cout<<"?"<<endl;
   begin=(double)clock()/CLK_TCK;
   arrayInsert<int>(array1,s_data,size);
   end=(double)clock()/CLK_TCK;
   cout<<"??"<<(end-begin)<<endl;
   getch();
   begin=(double)clock()/CLK_TCK;
   vectorInsert<int>(&vector1,s_data,size);
   end=(double)clock()/CLK_TCK;
   cout<<"??"<<(end-begin)<<endl;
   getch();
   begin=(double)clock()/CLK_TCK;
   listInsert<int>(&list1,s_data,size);
   end=(double)clock()/CLK_TCK;
   cout<<"??"<<(end-begin)<<endl;
   getch();
   begin=(double)clock()/CLK_TCK;
   multisetInsert<int>(&multiset1,s_data,size);
   end=(double)clock()/CLK_TCK;
   cout<<"??"<<(end-begin)<<endl;
   getch();
   free(s_data);
   return 0;      
}

 

这个程序清晰的表明这几种容器在插入速度之间的差别,当然,每种容器不是万能的,不能一好百好。比如说,多集在查找方面的优势是其他序列容器不可比拟的。  还有,最好不要试图超越STL,因为:
1、STL实现使用的是最佳算法。它是几乎完美的。
2、STL实现的设计者通常是相应领域的专家。
3、各领域的专家致力于提供灵活的、强大和高效的库。这是他们的首要的任务。对于,我们这些其余的人,开发可重用的容器和算法顶多只算第二个目标。我们的首要任务是交付紧扣主题的应用程序。大多数情况下,我们没有时间和专门的技术去和那些专家相比。

但是,超越STL不是不可能的。但是一般情况下,你只能靠牺牲可移植性来提高性能,这对于很多情况来说是很不好的。为了,超越STL,我们要付出非常大的努力。而且,最好我们知道一些STL专家们不知道的东西,尔后我们可以有针对性的进行优化,否则,我们的努力完全有可能白费。

面对这样一个优秀的库,并且它是免费的。我们C++程序员没有理由拒绝它,甚至去自己开发一个库。

本文地址:http://com.8s8s.com/it/it23666.htm