STL学习笔记-容器

来源:互联网 发布:网络体系结构的定义 编辑:程序博客网 时间:2024/06/07 05:01

转载自:http://blog.csdn.net/lwbeyond/article/details/7301443

一.STL 组件

1. 容器(Containers):用来管理某类对象的集合。

2. 迭代器(Iterators):用来在一个对象群集的元素上进行遍历动件。迭代器的接口和一般指针差不多,以operator++累加,以operator* 提取所指的值。

3. 算法(Algorithms):用来处理群集内的元素。

STL 的基本观念就是将数据与操作分离

数据由容器类加以管理,操作由算法定义,迭代器在两者之间充当粘合剂,使任何算法都可以和任何容器交互运作,如下图:



二.容器

容器可以分为两类:

1. 序列式容器:vector, deque, list

2. 关联式容器:set, multiset, map, multimap


3. 容器配接器:stacks,queues, priority queues, string

这是一些特别的容器,它们是根据基本容器类别实现来的。

三.迭代器

迭代器是一个“可以遍历 STL 容器内全部或部分元素”的对象。一个迭代器用来指出容器中的一个特定位置。

事实上每一种容器类型都有自己的迭代器,但它们对外的接口是相同的,这就是一种泛型程序设计。

1. 迭代器的基本操作

(1). Operator*

(2). Operator++

(3). Operator== 和 operator!=

(4). Operator=

2. 获取迭代器

所有容器都提供一些成员函数使我们获得迭代器,最重要的是:

(1). begin() 返回一个迭代器,指向容器的起点,也就是第一个元素的位置。

(2). end() 返回一个迭代器,指向容器结束点,也就是最后一个元素之后的位置

(1). rbegin() 返回一个迭代器,指向容器最后一个元素的位置。

(2). rend() 返回一个迭代器,指向容器第一个元素之前的位置

注意:begin() 和 end() 形成了一个半开区间([)),如下图:


3. 迭代器的类型

任何容器都提供两种迭代器类形,读写模式和只读模式

(1). Container::iterator 读/写模式遍历元素。

(2).Container::const_iterator 只读模式遍历元素。

4. 迭代器的分类

(1). 双向迭代器

双向迭代器,可以以递增++运算前进或以递减--运算后退。

list,set,multiset,map,multimap 这些容器所提供的迭代器都属于此类。

(2). 随机存取迭代器

随机存取不但具备双向迭代器的所有属性,还具备随机访问能力。

也就是它提供了“迭代器算法运算”必要的操作符,如:+n,-n,>,< 等。

vector,deque,string 这些容器的迭代器属于此类。

 注意:

list<char>::iterator pos;for (pos = coll.begin(); pos != coll.end(); ++pos) {       *pos = toupper(*pos);}for (pos = coll.begin(); pos != coll.end(); pos++) {                                               ^^^^^  // OK, but slower      ...}
++pos 要比 pos++ 效率高,因为后者需要一个额外的临时对象来存放迭代器的原来位置并将它返回,所以一般情况下最好用 ++pos。

四.算法

算法并非容器类形的成员函数,而是搭配迭代器使用全局函数

这里体现的是泛型函数式编程思维模式,而不是OOP 模式,因为在OOP 概念里,数据和操作是一体的。

注意:

1. 所有的算法都用来处理一个或多个区间内的元素,所以我们必须将区间首尾当做两个参数传给算法。调用者必须保证这个区间的有效性。

2. 所有算法处理的都是半开区间 [begin, end )。

3. 在处理多个区间时,要保证第二个区间能够容纳第一个区间的所有元素。

五. 迭代器配接器

迭代器配接器,也就是一类特殊的迭代器。主要有以下三种:

1. 安插型迭代器 (Insert iterators)

2. 流迭代器 (Stream iterators)

3. 逆向迭代器 (Reverse iterators)

六. 容器内的元素

容器元素的条件:

STL 容器内的元素必须满足下面三个基本要求:

1. 必须是可以用拷贝构造函数进行复制的。

2. 必须是可以用 =操作符完成赋值操作的。

3. 必须是可以用析构函数完成销毁操作的。

Value 与 Reference

所有容器都会建立元素副本,并返回该副本。这意味着 STL 只提供 Value 语意。

STL 只支持 Value 语意,不支持 Reference 语意。如果你想要支持 Reference 语意,你可以用指针作为元素来实现,但是要非常谨慎。

七. 错误与异常

STL 的设计原则是效率优先,安全次之。而错误检查相当花时间,所以在 STL 里几乎没有错误处理

所以对操作人员来说,要自己保证使用 STL 的正确性,或者自己增加一层包装来实现STL的安全性。

2. 异常处理

(1). 所有以节点实现的容器,如 list, set, multiset, mpa, multimap,如果节点构造失败,容器保持不变。

(2). 移除(removing)节点的动作保证不会失败。

(3). 对关联式容器插入(insert)多个元素,如果失败,则无法完全恢复原状。

(4). 对关联式容器插入单一个元素,要么成功,要么没有任何影响。

(5). 所有擦除(erase)操作,无论是针对单一元素或针对多重元素,肯定会成功。

(6). vector,deques类型的容器,在安插(insert)元素时如果失败,都不可能做到完全回复。


STL 的设计原则是效率优先,安全次之。而错误检查相当花时间,所以在 STL 里几乎没有错误处理

所以对操作人员来说,要自己保证使用 STL 的正确性,或者自己增加一层包装来实现STL的安全性。

2. 异常处理

(1). 所有以节点实现的容器,如 list, set, multiset, mpa, multimap,如果节点构造失败,容器保持不变。

(2). 移除(removing)节点的动作保证不会失败。

(3). 对关联式容器插入(insert)多个元素,如果失败,则无法完全恢复原状。

(4). 对关联式容器插入单一个元素,要么成功,要么没有任何影响。

(5). 所有擦除(erase)操作,无论是针对单一元素或针对多重元素,肯定会成功。

(6). vector,deques类型的容器,在安插(insert)元素时如果失败,都不可能做到完全回复。


八、应用实例

一. vector
vector 模塑出来一个动态数组,在末端添加和删除元素时,性能相当好,在前端或中部插入或删除元素时,性能不怎么样。
vector 性能优异的原因是,就是分配了比其所容纳元素所需的,更多的内存空间。有两个函数要特别注意一下:
v.capacity;  //返回 vector 实际能够容纳的元素数量  
这个容量值是当前所能容纳的最大值,这个值是会的,当超过当前最大容量时 vector 就会重新配置内部存储空间。

容量一但分配,就不会缩减。

//仅分配空间,size 是不变的v.reserve();//下面两种方式是一样的。不过第一种方法只分配空间,而第二种方法还要调用构造函数vector<int> v;v.reserve(80);vector<int> v(80); //如果构造函数很耗时,那么推荐使用上面的方法
元素存取
//返回索引 idx 所标示的元素。如果idx越界,抛出out_of_range。只有这个是进行范围检查的。v.at(idx);//返回索引 idx 所标示的元素。不进行范围检查v[idx]; //返回第一个元素。不检查第一个元素是否存在v.front();//返回最后一个元素。不检查最后一个元素是否存在v.back();
插入与删除
//在尾部添加和删除一个elem,无返回v.push_back(elem);v.pop_back();//移除pos位置上的元素,返回下一元素的位置v.erase(pos);//将元素数量改为num。//如果 num < 当前 size,则后面的元素被置为空。//如果 num > 当前 size,多出来的新元素都以default/elem构造完成v.resize(num);v.resize(num,elem)

如果容量不足,则扩大;如果调用所给的参数比当前 vector 的容量小,不会引发任何反应
二. deque
deque也采用动态数组来管理元素,提供随机存取,与vector非常相似。不同的是deque的动态数组头尾都开放,因此能在头尾两端进行快速安插和删除。deque 不提供容量操作 capacity() 和 reserve()。

1. vector 插入或删除操作会使“作用点”之后的各元素的pointers, references和iterators失效,如果插入操作引发内存重新分配,那么容器上所有的pointers, references和iterators都将失效

2. deque 插入或删除都可能引起内存重新分配,所以任何插入或删除动作都会使所有指向 deque元素的 pointers, references 和 iterators 失效。所以在插入或删除操作之后,要重新对 pointers, references和iterators 进行赋值的值。

三. list

1.List 不支持随机存取。如果你要存取第5个元素,就必须从头开始数。

2.任何位置插入和删除都很快。

3.插入和删除不会造成其它元素pointers, references 和 iterators 失效。

4.List 的迭代器是双向迭代器,所以凡是用到随机存取的迭代器的算法(特别是排序算法)都不能用,只能用list 提供的特殊版本。

List 提供了deque所有的功能,还增加了 remove() 和 remove_if() 算法(注意:全局的 remove() 函数并没有改变容量元素的数量,并且在操作上是后面的元素覆盖了前面的元素。)

四. Set和multiset

Set 和 multiset 会根据特定的排序准则,自动将元素排序。两者不同处在于multiset允许元素重复set不允许重复

优点:搜索元素时具有良好的性能。

限制:不能直接改变元素值,因为这样会打乱原本正确的顺序,因此要改变元素值,必须先删除旧元素,再插入新元素。

Set 和 multiset 通常以平衡二叉树来实现

Set<Elem, op>,其中op为排序准则,缺省准则是less,即从小到大排序。

std::set<int, std::greater<int> > coll;  //从大到小std::set<int, std::less<int> > coll;//从小到大std::set<int> coll; //默认less<>,也是从小到大 
由于 set 和 multiset 在元素快速搜索方面有优化设计,所以提供了特殊的搜索函数:

//返回元素值为elem的元素个数s.count(elem)//返回第一个元素值为 elem 的位置,找不到的话返回end()s.find(elem)//返回 elem 的第一个可以安插的位置,也就是 (元素值 >=elem)的位置s.lower_bound(elem)//返回 elem 的最后一个可以安插的位置,也就是(元素值 > elem)的位置s.upper_bound(elem)//返回 elem 可以安插的第一个位置和最后一个位置,也就是(元素值 == elem) 的元素区间s.equal_range(elem)
插入和删除函数:

//插入elem元素,返回新元素的位置,pos 指出插入操作的搜寻起点,如果 pos 恰当可以加快速度c.insert(pos,elem)c.insert(elem)//删除与elem相等的元素,返回删除元素的个数c.erase(elem)c.erase(pos)c.erase(beg, end)

注意插入操作的返回值:

因为 set 不允许重复,面 multiset 允许重复,所以他们的 insert 操作有不有同的返回值。

//set 提供的接口pair<iterator, bool> insert(const value_type& elem); //返回 pair<>

因为 set 不允许元素重复,所以如果插入相同的元素,将会返回失败

Pari 的 secode 成员表示插入是否成功。

Pair 的 first 成员返回新元素的位置。

//multiset 提供的接口iterator            insert(const value_type& elem); //返回新元素的位置
注意删除操作:

//删除与 elem 相等的元素,返回删除元素的个数c.erase(elem)

我们可以看到,其它容器的 erase() 操作参数都是位置。而set 除了其它几个erase(),还有这个传入值的 erase() 函数操作。这个操作在 list 里应该对应的是,c.remove()。但是这里确没有一至,可能是历史原因吧,我也不知道,但是这个要注意。反正我们记住,在 set 里有一个 erase(elem),在 list 里有一个 remove()  操作就行了。

如果 multisets 内含有重复元素,你不能使用 erase(),来删除这些重复元素中的第一个,你可以这么做:

std::multiset<Elem> coll;...//remove first element with passed valuestd::multiset<Elem>::iterator pos;pos = coll.find (elem);if (pos != coll.end()) {    coll.erase(pos);}
五. Map与 Multimap 
Map 和 Multimap 将(key/value) 一组当做元素,它们可以根据 key 的排序准则自动将元素排序。Multimap允许重复元素,map不允许重复

Map 和 multimap 通常以平衡二叉树来实现,map 和 multimap 拥有 set multiset 所有能为和所有操作函数。通常,不能直接改变key 的值,value 的值是可以直接修改的,因此可以想像元素的实质类型是 pair<const key, T>。

搜索函数:

//返回键值等于 key 的元素个数m.count(key)//返回键值等于 key 的第一个元素,找不到就返回 end()m.find(key)//返回 键值 >= key 的第一个元素位置m.lower_bound(key)//返回 键值 >= key 的第一个元素位置m.upper_bound(key)//返回 键值 == key 的元素区间m.equal_range(key)

注意:所有的搜索函数,参数是key,而不是value。这样你就不能以 find() 搜寻拥有某特定 value 的元素。

插入和删除函数

//插入c.insert(elem) c.insert(pos,elem)c.insert(beg,end)//删除c.erase(elem)c.erase(pos)c.erase(beg,end)
操作和 set 一样,不同的是参数是value,而不是key。这里要区别于上面搜索函数的参数。删除元素时,会使 pos 无效。