STL概述

STL是C++程序库的核心。
STL是一个泛型generic的函数库,提供了一系列的软件方案,利用先进、高效的算法来管理数据。

泛型程序设计的概念:所有操作行为都使用相同的操作接口,但是具备不同的类型。

1.STL组件:最关键的三个组件: container容器、Iterator迭代器、algorithm算法。
#容器:用来管理某类对象的集合
#迭代器:用来在一个对象集群collection of objects的元素上进行遍历动作。
        好处是为所有的容器提供了一组很小的公共接口。
         迭代器的接口和一般指针差不多,可以使用累加++,*提领所指对象,可以被看做是一种smart pointer。
#算法:用于处理集群中的元素,包括搜索、排序、修改、使用

STL的基本观念是将数据和操作分离。数据由具体的容器管理,而操作则由定制的算法定义,迭代器在其中充当粘合剂。
STL的一个根本特性是,所有组件都可以任何类型运作,容器和算法对于任何的types和class都已经被一般化了。
STL提供更泛化的组件,adapter适配器和functor仿函数,作为补充约束。

2.容器
一般而言,STL容器只提供通常具有良好时间性能的成员函数。

#序列式容器sequence containers可序集群:vector  deque list
 此外也可以将string和array当做是序列式容易。
 vector:
 将元素放在dynamic array中加以管理,允许通过索引随机存储,在尾部的插入和删除效率很高。
 deque:
 也是dynamic array,可以向两端发展,无论是在头在尾插入和删除元素都十分迅速。
 list:
 实际上是双端链表doubly linked list,不提供随机存取。
 优势是在任何位置插入删除都很迅速,只需要修改链表就行。
string:
array:

#关联式容器associative containers已序集群:set multiset  map multimap
 关联式容器自动对元素排序。

关联式容器是由二叉树来实现的。关联式容器的差别主要在于元素的类型以及处理重复元素的方式。
set:内部元素根据其值自动排序,每一个元素只能出现一次,不允许重复
multiset:相对于set,允许重复的元素
map:元素是键值对,每一个元素有一个键,是排序准则的基础,每一个键只能出现一次,不允许重复
     map可以看做是关联式数组,具有任意索引类型的数组
multimap:相对于map,允许相同的键存在,常被当字典使用

所有的关联式容器都有一个可供选择的template参数,指明排序准则,缺省采用operater<;

#容器适配器
由基本的容器类实现而成。
Stack:
Queue:是个普通的缓冲区
Priority Queue:  基于程序员给定给定排序规则,默认使用operater<

3.迭代器
支持的基本操作:operater*   operater++  operater== operater!= operater=
迭代器是一种smart指针,具有遍历复杂数据结构的能力,其下层运行机制取决于容器的数据结构。
事实上,每一种容器都以嵌套的方式将迭代器定义在容器内部。

所有的容器类都提供的成员函数:
begin()     end()  构成半开区间 half-open range----》还有一个好处,当为空时,begin()== end()

任何一种容器都定义了两种不同的迭代器类型:
container::iterator                          //读写模式
container::const_iterator                    //只读模式
使用前置式增量++iterater比后置式增量iterator++效率高。

迭代器分类:根据能力的不同,迭代器被分类为五种不同类属。
STL预先定义好的容器,其迭代器都属于以下两种类型:
1.双向迭代器Bidirectional Iterator
2.随机存取迭代器Random Access Iterator
  提供迭代器算数运算,支持operator<,不推荐使用的。

4.算法
为了处理容器中的元素,STL提供了一些标准算法,包括搜寻、排序、拷贝、重排、修改、数值运算等基本而普遍的算法。
算法并非容器的成员函数,而是搭配迭代器使用的全局函数。这并不是面向对象的思维模式OOP Paradigm,而是泛型函数式编程思维模式Generic Functional Programming Paradigm。
在OOP中,数据和操作是一体的。但是在算法中,两者是明确分离,通过特定的接口彼此互动。这样做有以下代价:1.用法有失直观2.某些数据结构和算法并不兼容。

头文件<algorithm>
单个区间的使用:使用来源于同一容器的半开区间作为算法操作的范围Range。
tip:在使用顺序容器的随机存取迭代器时可以使用迭代器算术运算。
多个区间的使用:如equal、copy
tip:确保第二个及其他区间的元素个数,至少和第一区间的元素个数相同。
如果其后区间的元素个数比第一区间大,那么就会产生未定义行为,在STL安全版本中,这种错误都会被导向一个错误处理程序error handling procedure。
避免以上错误:
1.保证第一区间元素足够大
2.使用insert Iterator
tips:关联式容器不可能当做覆写型算法的操作目标。

5.迭代器适配器 Iterator Adapters
迭代器是一个纯粹的抽象概念。任何行为类似于迭代器的东西,都是迭代器。
C++标准库提供了数个预先定义的特殊迭代器,也就是所谓的迭代器适配器。他们不仅起辅助作用,还赋予了整个迭代器抽象概念强大的功能。

以下三种迭代器适配器:

1.Insert Iterator 安插型迭代器
  也称inserter,它可以使算法以插入而非覆写的方式运作,使用它可以解决算法的“目标控件不足”的问题,它促使目标空间按需要增长。
三种预先定义的安插型迭代器:
#back inserters安插在容器尾部
 back_inseter(container)
 只有提供了push_back成员函数的容器才能使用back inserter。这样的三个顺序容器是Vector、list、deque。
 元素的排列次序与安插次序相同
#front inserter安插在容器前部
 front_inserter(container)
 只有提供了push_front成员函数的容器才能使用front inserter。这样的两个顺序容器是list、deque。
 元素的排列次序与安插次序相反
#general inserter一般性安插器
 inserter(container,pos)
 对于关联式容器,不指定位置pos,而由元素的值来决定插入的位置。
 元素的排列次序与安插次序相同

2.Stream Iterator流迭代器
vector<string> col1;
copy(istream_iterator<string>(cin),istream_iterator(),back_inserter(col1));
sort(col1.begin(),col1.end());
unique_copy(col1.begin(),col1.end(),ostream_iterator<string>(cout,"\n"));  //\n为打印单词分隔符

3.reverse Iterator逆向迭代器
  所有的容器都通过成员函数rbegin()和rend()产生reverse iterator。
  rbegin()指向的是最后一个元素,而rend()指向的是第一个元素的前一位置。
  所以,类似于*(end()),*(rend())也是未定义的。

copy(col1.rbegin(),col1.rend(),ostream<string>(cout,"\n"));
cout<<endl;

6.更易型算法Manipulation Algorithms
#移除元素remove()
list<int> col;
//remove all elemets with value of 3
remove(col.begin(),col.end(),3);
这样做的结果只是让值为3的元素被后面的元素覆盖,而容器的size不变,只是将后面的值覆盖前面的元素的值,那么意味着在尾部存在着多个重复的元素。

改进版本:
list<int> iterator end = remove(col.begin(),col.end(),3);
cout<<"number of elemets removed:"<<distance(end,col.end)<<endl;   //如果是随机存取迭代器,可以直接算数运算
col.erase(end,col.end);
copy(col.begin(),col.end(),ostream_iteratror<int>(cout," "));
这意味着remove操作并没有真正删除掉多余的部分,不会修改多余的存储结构。

对删除操作的改进:
col.erase(remove(col.begin(),col.end(),3,),col.end());

出现上述现象的原因:
在设计上,我们让迭代器对它所处的容器一无所知。任何以迭代器访问容器元素的算法,都不得通过迭代器调用容器类型所提供的任何成员函数。
通常也没有必要删除“被remove”的元素。

#关联式容器与更易型算法
 关联式容器不能作为更易型算法操作的对象,如果存在更易,将改变关联式容器原本有序序列,从而推翻了关联式容器的基本原则。为了保证这个原则,关联式容器的迭代器都被定义为指向常量,不允许更易的。
那么只是不能通过迭代器去remove元素,但是可以通过容器的成员函数来remove函数。
set<int> col;
int num =  col.erase(3);  //remove elements with value of 3,return number of removed elements
之所以这样使用,是因为容器提供了多种erase函数。应该是重载。

#算法vs成员函数
 算法有时候不如成员函数高效:
 如在list中使用remove算法,是十分低效的,不如使用list中指针的修改。所以,为了强调高效,优先使用成员函数。

7.使用者自定义泛型函数User-Defined Generic Function
  STL本身就是一个可扩展的框架,用户也可以自定义高效的泛型函数。

8.以函数作为算法的参数

#一些算法可以接受用户自定义的函数,由此提高算法的灵活性和能力。
foreach(col.begin(),col.end(),print);  //接受一个自定义的print函数

#还有的是一些排序准则、搜索准则等
std::transform(col.begin(),col.end(),std::back_inserter(col2),square);
//square作为一个用户自定义函数,所有col中元素在后插到col2前先要经过square处理。

#判断式Predicates
所谓判断式,就是返回bool型值的函数。

@一元判断式Unary Predicate:检查唯一参数的某项特性
bool isPrim(int num){
 if(num < 2) return false;
 int divisor;
 for(divisor = num/2; num%divisor != 0; --divisor);
 if(divisor == 1) return true;
   else return false;
}
int pos = find_if(col.gegin(),col.end(),isPrim);
//isPrim函数就是一个一元判断式,检查元素是否为质数

@二元判断式Binary Predicate:检查两个参数的特定属性
 如果元素本身不支持operator<,或者需要其他的比较函数,二元判断式就十分实用。
 bool personSortCriterion(Cosnt Person& p1,const Person& p2){
 return p1.lastname < p2.lastname ||
  (!(p2.lastname< p1.lastname) && p1.firstname < p2.firstname);
}

sort(col.begin(),col.end(),personSortCriterion);

9.仿函数
传递给算法的“函数型参数”functional arguments,并不一定是函数,也可以是行为类似函数的对象,称之为function object,或为仿函数functor。
仿函数是泛型编程强大威力和纯粹抽象概念的又一例证,可以说,任何东西,只要其行为像函数,它就是一个函数。
函数行为:使用小括号传递参数,藉此调用某个东西
//定义 function call operator
class X{
 public:
  return-value operater() (arguments) const;
}

X fo; fo(arg1,arg2);

例子2:
class PrintInt{
 public:
  void operater()(int elem)const{
   cout<<elem<<"";
  }
};

for_each(col.begin(),col.end(),PrintInt());

for_each函数的一个版本:
template<class Iterator,class Operation>
Operation for_each(Iterator act,Iterator end,Operation op){
 while(act != end){
  op(*act);
  ++act;
 }
 return op;
}

仿函数的优点:
1.仿函数是智能型函数
  仿函数可以有状态,可以在调用它之前初始化它,而不是普通函数中需要定义额外的全局变量
  关键是这个状态是可控的
  class AddValue{
 private:
  int value;
 public:
  AddValue(int v):value(v){}
  void operator()(int& elem) const{
   elem += value;
  }
}
for_each(col.begin(),col.end(),AddValue(4));
for_each(col.begin(),col.end(),AddValue(*col.begin()));

还可以定义仿函数的不同实例:
AddValue addX(x);
AddValue addY(y);
2.每一个仿函数可以有自己的型别
  基于template,可以有自己的型别。基于类,还可以设计仿函数继承体系。
3.仿函数通常比普通函数要快
  仿函数在编译期就可以确定了。

预先定义的仿函数:
C++标准库预先定义了一些仿函数:
排序规则less<>()  greater<>()  negate<>()取负值 multiplies<>()求平方

通过一些特殊的函数适配器,还可以将预先定义的仿函数和其他值绑定到一起。
set<int,greater<int>> col1;
deque<int> col2;
transform(col1.begin(),col1.end(),back_inserter(col2),bind2nd(multiplies<int>(),10));
replace_if(col1.begin(),col1.end(),back_inserter(col2),bind2nd(equal_to<int>(),10));

函数适配器bind2nd,将第二个参数绑定到预定义的仿函数中,这样将equal_to<int>()这个一元判断式转化为了二元判断式。类似的用法:bind2nd(less<int>(),20),这种函数的编写方式导致了函数的组合。

而所有这些仿函数都被定义为inline。

有些仿函数可以用来调用集群中元素的成员:
for_each(col1.begin(),col1.end(),mem_fun_ref(&Preson::save()));

10.容器中的元素
STL的容器、迭代器、算法都是templates,因此可以操作任何类型。

容器中的元素必须符合特定条件,因为容器是以一种特别方式处理它们,三个基本条件:
#必须可通过copy函数进行复制,且副本和原本必须相等
 因为copy构造函数会被频繁调用
#必须可通过operator=完成赋值操作
#必须可通过析构函数完成销毁动作
 析构函数不能申明为private,且按照C++管理,是不能抛出异常的。

其他:
#对于序列式容器,其默认构造函数必须是可用的
#对于搜寻等操作,其operator==是必须定义的
#对于关联式容器,必须定义排序规则,默认是operator<,通过less仿函数调用

STL容器支持value语意而不是Reference语意:
#好处:拷贝简单 如果是Reference,必须确保Reference存在,
#缺点:有可能的拷贝不成功,不能在数个容器中管理同一份对象

在使用指针作为容器元素时,要十分小心,最好使用智能指针。
然而,auto_ptr不符合容器元素的基本条件的,拷贝的副本和原本是不相等的。使用计数型智能指针可以实现容器元素的Reference语意。

11.STL内部的错误处理和异常处理

错误处理:
STL的设计原则是效率优先,安全次之。错误检查相当费时间,所以基本没有。TL和C指针一样的容易引发错误。

使用STL的基本要求:
#迭代器必须合法,必须在使用前初始化
#迭代器如果指向逾尾位置,不应该指向任何对象
#区间必须合法:两个迭代器必须来源于同一容器,从第一个迭代器出发必须能够到达第二个
#如果涉及多个区间,第一区间必须足够大
    #覆盖overwrite操作,必须保证第一区间有足够的元素给予操作,否则必须采用inserter

异常处理:
STL几乎不检查逻辑错误,所以逻辑问题基本上不会引发STL异常。
事实上,C++标准只要求唯一的一个函数调用动作必要时直接引发异常:vector和deque的成员函数at()。
STL无法保证确定的异常引发行为,甚至不能正确地将堆栈辗转开解stack unwinding。

C++标准库的保证:
#以结点为实现基础node-based的容器,如list、set、multiset、map、multimap:
  结点构造失败不影响原容器,删除结点总成功
 对于关联式容器,如果插入多个元素,这个操作必须是原子的atomic,commit-or-rollback
#以array为实现基础array-based的容器,如vector和deque:
  安插数据失败是不可能得到完全回复。

12.扩展的STL
STL遗漏的最重要组件是hash table。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值