一、STL(Standard Template Library),即标准模板库,是一个高效的C++程序库,包含了诸多经常使用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。前端
二、从逻辑层次来看,在STL中体现了泛型化程序设计的思想(generic programming)。在这种思想里,大部分基本算法被抽象,被泛化,独立于与之对应的数据结构,用于以相同或相近的方式处理各类不一样情形。ios
三、从实现层次看,整个STL是以一种类型参数化(type parameterized)的方式实现的,基于模板(template)。程序员
STL有六大组件,但主要包含容器、迭代器和算法三个部分。算法
STL 的基本观念就是将数据和操做分离。数据由容器进行管理,操做则由算法进行,而迭代器在二者之间充当粘合剂,使任何算法均可以和任何容器交互运做。这一篇博客暂时只介绍容器,下一篇介绍迭代器。数组
容器用来管理某类对象。为了应付程序中的不一样需求,STL 准备了两类共七种基本容器类型:数据结构
示意图以下图所示:框架
vector(向量): 是一种序列式容器,事实上和数组差很少,但它比数组更优越。通常来讲数组不能动态拓展,所以在程序运行的时候不是浪费内存,就是形成越界。而 vector 正好弥补了这个缺陷,它的特征是至关于可拓展的数组(动态数组),它的随机访问快,在中间插入和删除慢,但在末端插入和删除快。函数
特色性能
优缺点和适用场景spa
优势:支持随机访问,即 [] 操做和 .at(),因此查询效率高。 缺点:当向其头部或中部插入或删除元素时,为了保持本来的相对次序,插入或删除点以后的全部元素都必须移动,因此插入的效率比较低。 适用场景:适用于对象简单,变化较小,而且频繁随机访问的场景。 |
例子
如下例子针对整型定义了一个 vector,插入 6 个元素,而后打印全部元素:
#include <iostream> #include <vector> using namespace std; int main(int argc, char* argv[]) { vector<int> vecTemp; for (int i = 0; i<6; i++) vecTemp.push_back(i); for (int i = 0; i<vecTemp.size(); i++) cout << vecTemp[i] <<" "; // 输出:0 1 2 3 4 5 return 0; }
deque(double-ended queue)是由一段一段的定量连续空间构成。一旦要在 deque 的前端和尾端增长新空间,便配置一段定量连续空间,串在整个 deque 的头端或尾端。所以不论在尾部或头部安插元素都十分迅速。 在中间部分安插元素则比较费时,由于必须移动其它元素。deque 的最大任务就是在这些分段的连续空间上,维护其总体连续的假象,并提供随机存取的接口。
特色
优缺点和适用场景
优势:支持随机访问,即 [] 操做和 .at(),因此查询效率高;可在双端进行 pop,push。 缺点:不适合中间插入删除操做;占用内存多。 适用场景:适用于既要频繁随机存取,又要关心两端数据的插入与删除的场景。 |
例子
如下例子声明了一个浮点类型的 deque,并在容器尾部插入 6 个元素,最后打印出全部元素。
#include <iostream> #include <deque> using namespace std; int main(int argc, char* argv[]) { deque<float> dequeTemp; for (int i = 0; i<6; i++) dequeTemp.push_back(i); for (int i = 0; i<dequeTemp.size(); i++) cout << dequeTemp[i] << " "; // 输出:0 1 2 3 4 5 return 0; }
List 由双向链表(doubly linked list)实现而成,元素也存放在堆中,每一个元素都是放在一块内存中,他的内存空间能够是不连续的,经过指针来进行数据的访问,这个特色使得它的随机存取变得很是没有效率,所以它没有提供 [] 操做符的重载。可是因为链表的特色,它能够颇有效率的支持任意地方的插入和删除操做。
特色
优缺点和适用场景
优势:内存不连续,动态操做,可在任意位置插入或删除且效率高。 缺点:不支持随机访问。 适用场景:适用于常常进行插入和删除操做而且不常常随机访问的场景。 |
例子
如下例子产生一个空 list,准备放置字符,而后将 'a' 至 'z' 的全部字符插入其中,利用循环每次打印并移除集合的第一个元素,从而打印出全部元素:
#include <iostream> #include <list> using namespace std; int main(int argc, char* argv[]) { list<char> listTemp; for (char c = 'a'; c <= 'z'; ++c) listTemp.push_back(c); while (!listTemp.empty()) { cout <<listTemp.front() << " "; listTemp.pop_front(); } return 0; }
成员函数empty()
的返回值告诉咱们容器中是否还有元素,只要这个函数返回 false,循环就继续进行。循环以内,成员函数front()
会返回第一个元素,pop_front()
函数会删除第一个元素。
注意:list <指针> 彻底是性能最低的作法,还不如直接使用 list <对象> 或使用 vector <指针> 好,由于指针没有构造与析构,也不占用很大内存。
set(集合)由红黑树实现,其内部元素依据其值自动排序,每一个元素值只能出现一次,不容许重复。
特色
优缺点和适用场景
优势:使用平衡二叉树实现,便于元素查找,且保持了元素的惟一性,以及能自动排序。 缺点:每次插入值的时候,都须要调整红黑树,效率有必定影响。 适用场景:适用于常常查找一个元素是否在某群集中且须要排序的场景。 |
例子
下面的例子演示 set(集合)的两个特色:
#include <iostream> #include <set> using namespace std; int main(int argc, char* argv[]) { set<int> setTemp; setTemp.insert(3); setTemp.insert(1); setTemp.insert(2); setTemp.insert(1); set<int>::iterator it; for (it = setTemp.begin(); it != setTemp.end(); it++) { cout << *it << " "; } return 0; }
输出结果:1 2 3。一共插入了 4 个数,可是集合中只有 3 个数而且是有序的,可见以前说过的 set 集合的两个特色,有序和不重复。
当 set 集合中的元素为结构体时,该结构体必须实现运算符 ‘<’ 的重载:
#include <iostream> #include <set> #include <string> using namespace std; struct People { string name; int age; bool operator <(const People p) const { return age < p.age; } }; int main(int argc, char* argv[]) { set<People> setTemp; setTemp.insert({"张三",14}); setTemp.insert({ "李四", 16 }); setTemp.insert({ "隔壁老王", 10 }); set<People>::iterator it; for (it = setTemp.begin(); it != setTemp.end(); it++) { printf("姓名:%s 年龄:%d\n", (*it).name.c_str(), (*it).age); } return 0; } /* 输出结果 姓名:王二麻子 年龄:10 姓名:张三 年龄:14 姓名:李四 年龄:16 */
能够看到结果是按照年龄由小到大的顺序排列。另外 string 要使用c_str()
转换一下,不然打印出的是乱码。
另外 Multiset 和 set 相同,只不过它容许重复元素,也就是说 multiset 可包括多个数值相同的元素。这里再也不作过多介绍。
map 由红黑树实现,其元素都是 “键值/实值” 所造成的一个对组(key/value pairs)。每一个元素有一个键,是排序准则的基础。每个键只能出现一次,不容许重复。
map 主要用于资料一对一映射的状况,map 内部自建一颗红黑树,这颗树具备对数据自动排序的功能,因此在 map 内部全部的数据都是有序的。好比一个班级中,每一个学生的学号跟他的姓名就存在着一对一映射的关系。
特色
优缺点和适用场景
优势:使用平衡二叉树实现,便于元素查找,且能把一个值映射成另外一个值,能够建立字典。 缺点:每次插入值的时候,都须要调整红黑树,效率有必定影响。 适用场景:适用于须要存储一个数据字典,并要求方便地根据key找value的场景。 |
例子
#include "stdafx.h" #include <iostream> #include <map> #include <string> using namespace std; int main(int argc, char* argv[]) { map<int, string> mapTemp; mapTemp.insert({ 5,"张三" }); mapTemp.insert({ 3, "李四"}); mapTemp.insert({ 4, "隔壁老王" }); map<int, string>::iterator it; for (it = mapTemp.begin(); it != mapTemp.end(); it++) { printf("学号:%d 姓名:%s\n", (*it).first, (*it).second.c_str()); } return 0; } /* 输出结果: 学号:3 姓名:李四 学号:4 姓名:隔壁老王 学号:5 姓名:张三 */
multimap 和 map 相同,但容许重复元素,也就是说 multimap 可包含多个键值(key)相同的元素。这里再也不作过多介绍。
除了以上七个基本容器类别,为知足特殊需求,STL还提供了一些特别的(而且预先定义好的)容器配接器,根据基本容器类别实现而成。包括:
一、stack
名字说明了一切,stack 容器对元素采起 LIFO(后进先出)的管理策略。
二、queue
queue 容器对元素采起 FIFO(先进先出)的管理策略。也就是说,它是个普通的缓冲区(buffer)。
三、priority_queue
priority_queue 容器中的元素能够拥有不一样的优先权。所谓优先权,乃是基于程序员提供的排序准则(缺省使用 operators)而定义。Priority queue 的效果至关于这样一个 buffer:“下一元素永远是queue中优先级最高的元素”。若是同时有多个元素具有最髙优先权,则其次序无明肯定义。
各容器的特色总结
在实际使用过程当中,到底选择这几种容器中的哪个,应该根据遵循如下原则:
一、若是须要高效的随机存取,不在意插入和删除的效率,使用 vector。 二、若是须要大量的插入和删除元素,不关心随机存取的效率,使用 list。 三、若是须要随机存取,而且关心两端数据的插入和删除效率,使用 deque。 四、若是打算存储数据字典,而且要求方便地根据 key 找到 value,一对一的状况使用 map,一对多的状况使用 multimap。 五、若是打算查找一个元素是否存在于某集合中,惟一存在的状况使用 set,不惟一存在的状况使用 multiset。 |
各容器的时间复杂度分析
各容器的共性
各容器通常来讲都有下列函数:默认构造函数、复制构造函数、析构函数、empty()、max_size()、size()、operator=、operator<、operator<=、operator>、operator>=、operator==、operator!=、swap()。
顺序容器和关联容器都共有下列函数:
begin()
:返回容器第一个元素的迭代器指针;end()
:返回容器最后一个元素后面一位的迭代器指针;rbegin()
:返回一个逆向迭代器指针,指向容器最后一个元素;rend()
:返回一个逆向迭代器指针,指向容器首个元素前面一位;clear()
:删除容器中的全部的元素;erase(it)
:删除迭代器指针it处元素。参考:
《C++标准库 - 侯捷》中的 5.2 节-容器