一、C++ STL概述
1994 年,STL 被正式纳入 C++ 标准中。STL 是“Standard Template Library”(标准模板库)的缩写。STL 是 C++ 标准库的一部分,不用单独安装。
C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。
标准模板库(STL),它的基本概念就是把数据和操作分离,含有容器(Container)、算法(Algorithm)、迭代器(Iterator)等组件。
通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的,它们各自的含义如下表:
STL的组成 |
含义 |
容器 |
一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。 |
算法 |
STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 |
迭代器 |
在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。 |
函数对象(又称仿函数) |
如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。 |
适配器 |
可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。 |
内存分配器 |
为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。 |
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合。
STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)——OOP的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效;
从逻辑层次来看,在STL中体现了泛型(generic)化程序设计的思想,引入了诸多新的名词,比如像需求(requirements),概念(concept),模型(model),容器(container),算法(algorithmn),迭代器(iterator)等。与OOP(object-oriented programming)中的多态(polymorphism)一样,泛型也是一种软件的复用技术;泛型编程(Generic Programming) 指在多种数据类型上皆可操作,泛型算法是建立在语法一致性上。
从实现层次看,整个STL是以一种类型参数化的方式实现的,这种方式基于一个在早先C++标准中没有出现的语言特性——模板(template)。从根本上说,STL 是一些容器、算法和其他一些组件的集合,所有容器和算法都是总结了几十年来算法和数据结构的研究成果,汇集了许多计算机专家学者经验的基础上实现的,因此可以说,STL 基本上达到了各种存储方法和相关算法的高度优化。
STL容器
1)序列式容器(Sequence containers),每个元素都有固定位置——取决于插入时机和地点,和元素值无关,vector、deque、list;
Vectors:将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快速。但是在中部或头部安插元素比较费时;
Deques:是“double-ended queue”的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素都非常快速。但是在中部或头部安插元素比较费时;
Lists:双向链表,不提供随机存取(按顺序走到需存取的元素,O(n)),在任何位置上执行插入或删除动作都非常迅速,内部只需调整一下指针;
2)关联式容器(Associated containers),元素位置取决于特定的排序准则,和插入顺序无关,set、multiset、map、multimap;
Sets/Multisets:内部的元素依据其值自动排序,Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找;
Maps/Multimaps:Map的元素是成对的键值/实值,内部的元素依据其值自动排序,Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素,内部由二叉树实现(实际上基于红黑树(RB-tree)实现),便于查找;
另外有其他容器hash_map,hash_set,hash_multiset,hash_multimap。
容器的比较:
|
Vector |
Deque |
List |
Set |
MultiSet |
Map |
MultiMap |
内部结构 |
dynamic array |
array of arrays |
double linked list |
binary tree |
binary tree |
binary tree |
binary tree |
随机存取 |
Yes |
Yes |
No |
No |
No |
Yes(key) |
No |
搜索速度 |
慢 |
慢 |
很慢 |
快 |
快 |
快 |
快 |
快速插入移除 |
尾部 |
首尾 |
任何位置 |
-- |
-- |
-- |
-- |
【STL通用容器分三类:顺序性容器、关联式容器和容器适配器。
简单的理解,容器适配器其就是将不适用的序列式容器(包括 vector、deque 和 list)变得适用。
容器适配器本质上还是容器,只不过此容器模板类的实现,利用了大量其它基础容器模板类中已经写好的成员函数。当然,如果必要的话,容器适配器中也可以自创新的成员函数。】
STL迭代器
无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。
Iterator(迭代器)又称Cursor(游标),用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。或者这样说可能更容易理解:Iterator模式是运用于聚合对象的一种模式,通过运用该模式,使得我们可以在不知道对象内部表示的情况下,按照一定顺序(由iterator提供的方法)访问聚合对象中的各个元素。
STL 标准库为每一种标准容器定义了一种迭代器类型,这意味着,不同容器的迭代器也不同,其功能强弱也有所不同。
容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。
常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器(forward iterator)、双向迭代器(bidirectional iterator)、随机访问迭代器 5 种。
迭代器类别 |
说明 |
输入 |
从容器中读取元素。输入迭代器只能一次读入一个元素向前移动, 输入迭代器只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列 |
输出 |
向容器中写入元素。输出迭代器只能一次一个元素向前移动。 输出迭代器只支持一遍算法,统一输出迭代器不能两次遍历一个序列 |
正向 |
组合输入迭代器和输出迭代器的功能,并保留在容器中的位置 |
双向 |
组合正向迭代器和逆向迭代器的功能,支持多遍算法 |
随机访问 |
组合双向迭代器的功能与直接访问容器中任何元素的功能, 即可向前向后跳过任意个元素 |
迭代器遍历访问的大致思路是,创建容器的迭代器,让迭代器指向第一个元素,逐步向后移动并最终指向最后一个元素结束。
遍历示例代码:
vector
vector
for(it=v.begin();it!=v.end();it++){
cout<<*it<<' ';
}
当然,遍历也可以直接使用下标访问:
for(int i=0;i cout< } 算法 STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以你所指定的规则对序列进行稳定性排序等等。只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能并大大地提升效率。 算法部分主要由头文件 STL中算法大致分为四类: 非可变序列算法:指不直接修改其所操作的容器内容的算法。 可变序列算法:指可以修改它们所操作的容器内容的算法。 排序算法:对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作。 数值算法:对容器内容进行数值计算。 二、C++ STL几个常见容器的使用 ★Vector(向量)容器 Vector是最简单的序列是容器,就像数组一样,向量使用连续的存储位置作为元素,这意味着它们的元素也可以使用常量指向其元素的偏移来访问,与数组一样有效。但与数组不同,它们的大小可以动态变化,其存储由容器自动处理。 Vector就是一个动态创建空间,且预先加载了常用的数组操作的数组。 相关文件 头文件:#include 格式为:vector 如: vector vector vector 下面给出一个完整示例: 运行输出: vector的遍历有多种方式: []方式,如果越界或出现其他错误,不会抛出异常,可能会崩溃,可能数据随机出现; at方式,如果越界或出现其他错误,会抛出异常,需要捕获异常并处理; 迭代器提供了逆向遍历,可以通过迭代器来实现逆向遍历,当然上面两种方式也可以。 如: 运行输出: 0 1 2 3 4 5 6 7 8 9 关于上面实例中所使用的begin() 、end()函数见下面。 C ++ Vector(向量)常见的成员函数: 函数 描述 at(idx) 传回索引idx所指的数据,如果idx越界,抛出out_of_range。 back() 返回最后一个原始,不检查这个数据是否存在。 front() 返回第一个元素。 swap() 交换两个Vector。 push_back() 在Vector最后添加一个元素。 pop_back() 它从向量中删除最后一个元素。 empty() 判断Vector是否为空(返回true时为空) insert() 它将在指定位置插入新元素。 erase() 删除指定的元素。 resize() 它修改向量的大小。 clear() 它从向量中删除所有元素。 size() 返回Vector元素数量的大小。 capacity() 返回vector所能容纳的元素数量(在不重新分配内存的情况下) assign() 它将新值分配给向量。 operator=() 它将新值分配给向量容器。 operator[]() 它访问指定的元素。 end() 返回最末元素的迭代器(实指向最末元素的下一个位置) emplace() 它将在位置pos之前插入一个新元素。 emplace_back() 它在末尾插入一个新元素。 rend() 它指向向量的第一个元素之前的元素。 rbegin() 它指向向量的最后一个元素。 begin() 返回第一个元素的迭代器。 max_size() 返回Vector所能容纳元素的最大数量(上限)。 cend() 它指向量中的last-last-element。 cbegin() 它指向量的第一个元素。 crbegin() 它指的是向量的最后一个字符。 crend() 它指的是向量的第一个元素之前的元素。 data() 它将向量的数据写入数组。 shrink_to_fit() 它减少了容量并使它等于向量的大小。 ★Deque(双端队列) 所谓的deque是”double ended queue”的缩写,双端队列不论在尾部或头部插入元素,都十分迅速。而在中间插入元素则会比较费时,因为必须移动中间其他的元素。双端队列是一种随机访问的数据类型,提供了在序列两端快速插入和删除操作的功能,它可以在需要的时候改变自身大小,完成了标准的C++数据结构中队列的所有功能。 双端队列表示双端队列。它概括了队列数据结构,即可以从前端或后端的两端进行插入和删除。 相关文件 头文件:#include 创建语法格式为:deque 如: deque deque deque deque deque 实例 运行输出: 6 C ++ Deque(双端队列)常见的成员函数: 函数/方法 描述 assign() 它分配新内容并替换旧内容。 emplace() 它将在指定位置添加一个新元素。 emplace_back() 它在末尾添加一个新元素。 emplace_front() 它在双端队列的开头添加一个新元素。 insert() 它在指定位置之前添加一个新元素。 push_back() 它在容器的末尾添加一个新元素。 push_front() 它在容器的开头添加一个新元素。 pop_back() 它从双端队列中删除最后一个元素。 pop_front() 它从双端队列中删除第一个元素。 swap() 它交换两个双端队列的内容。 clear() 它将删除双端队列的所有内容。 empty() 它检查容器是否为空。 erase() 它删除元素。 max_size() 它确定双端队列的最大大小。 resize() 它改变了双端队列的大小。 shrink_to_fit() 它减少了内存以适合双端队列的大小。 size() 它返回元素数。 at() 它访问位置pos处的元素。 operator[]() 它访问位置pos处的元素。 operator=() 它将新的内容分配给容器。 back() 它访问最后一个元素。 begin() 它将迭代器返回到双端队列的开头。 cbegin() 它向双端队列的开头返回一个常量迭代器。 end() 它将迭代器返回到末尾。 cend() 它将常量迭代器返回到末尾。 rbegin() 它将反向迭代器返回到开头。 crbegin() 它将常量反向迭代器返回到开头。 rend() 它将反向迭代器返回到末尾。 crend() 它将常量反向迭代器返回到末尾。 front() 它访问最后一个元素。 ★List(列表)容器 list是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 。list支持双向,并为插入和删除操作提供了一种有效的方法。在列表中遍历速度很慢,因为列表元素是按顺序访问的。 相关文件 头文件:#include 创建语法格式为:list 如: list list list 实例 运行输出: 3 10 9 8 7 6 20 3 6 7 8 9 10 20 20 10 9 8 7 6 3 list容器具有如下特性: 可以在头部和尾部插入和删除元素; 不能随机访问元素,也就是迭代器只能只能++,不能一次性跳转。 如: 运行输出: 22 0 1 2 3 4 5 6 7 8 9 List(列表)常见的成员函数: 函数/方法 描述 insert() 它将新元素插入到迭代器指向的位置之前。 push_back() 它在容器的末尾添加了一个新元素。 push_front() 它在前面增加了一个新元素。 pop_back() 删除最后一个元素。 pop_front() 删除第一个元素。 empty() 它检查列表是否为空。 size() 它查找列表中存在的元素数。 max_size() 它找到列表的最大大小。 front() 它返回列表的第一个元素。 back() 它返回列表的最后一个元素。 swap() 当两个列表的类型相同时,它将交换两个列表。 reverse() 它反转了列表的元素。 sort() 它以递增的顺序对列表中的元素进行排序。 merge() 它合并两个排序的列表。 splice() 它将新列表插入到调用列表中。 unique() 它从列表中删除所有重复的元素。 resize() 它更改列表容器的大小。 assign() 它将一个新元素分配给列表容器。 emplace() 它将在指定位置插入一个新元素。 emplace_back() 它将在容器的末尾插入一个新元素。 emplace_front() 它将在列表的开头插入一个新元素。 ★set(集合)/multiset(多重集合)容器 set/multiset属于关联式容器,底层结构是用二叉树实现。 set和multiset区别: set不允许容器中有重复的元素,其中每个键都是唯一的,可以插入或删除但不能更改。 multiset允许容器中有重复的元素。 相关文件 头文件:#include 创建语法格式为:set| multiset 如: set set set 特别提示:set/multiset容器是有序的集合,默认的顺序是从小到大的。 Set示例: 运行输出: 1 1 Multiset示例: 运行输出: 1 1 set(集合)/multiset(多重集合)常见成员函数的列表: 函数/方法 描述 begin() 返回指向第一个元素的迭代器 clear() 清除所有元素 count() 返回某个值元素的个数 empty() 如果集合为空,返回true end() 返回指向最后一个元素的迭代器 equal_range() 返回集合中与给定值相等的上下限的两个迭代器 erase() 删除集合中的元素 find() 返回一个指向被查找到元素的迭代器 get_allocator() 返回集合的分配器 insert() 在集合中插入元素 lower_bound() 返回指向大于(或等于)某值的第一个元素的迭代器 key_comp() 返回一个用于元素间值比较的函数 max_size() 返回集合能容纳的元素的最大限值 rbegin() 返回指向集合中最后一个元素的反向迭代器 rend() 返回指向集合中第一个元素的反向迭代器 size() 集合中元素的数目 swap() 交换两个集合变量 upper_bound() 返回大于某个值元素的迭代器 value_comp() 返回一个用于比较元素间的值的函数 ★map(映射)/multimap(多重映射)容器 map和multimap都是键值对(key-value),每一个元素都有一个键,唯一的不同是,map的键值key不可重复,而multimap可以,也正是由于这种区别,map支持[ ]运算符,multimap不支持[ ]运算符。在用法上没什么区别。map和multimap都需要#include 相关文件 头文件:#include 创建语法格式为:map| multimap 如: map map示例: 运行输出: 3 multimap示例: 运行输出: 4 Map(映射)/multimap(多重映射)常见成员函数的列表: 函数/方法 描述 begin() 返回指向map头部的迭代器 clear() 删除所有元素 count() 返回指定元素出现的次数 empty() 如果map为空则返回true end() 返回指向map末尾的迭代器 equal_range() 返回特殊条目的迭代器对 erase() 删除一个元素 find()查找一个元素 get_allocator() 返回map的配置器 insert() 插入元素 key_comp() 返回比较元素key的函数 lower_bound() 返回键值>=给定元素的第一个位置 max_size() 返回可以容纳的最大元素个数 rbegin() 返回一个指向map尾部的逆向迭代器 rend()返回一个指向map头部的逆向迭代器 size()返回map中元素的个数 swap() 交换两个map upper_bound() 返回键值>给定元素的第一个位置 value_comp()返回比较元素value的函数 ★array容器 array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。在使用上,它比普通数组更安全。和其它容器不同,array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法借由增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。 array 容器以类模板的形式定义在 #include using namespace std; array 容器有多种初始化方式,如下代码展示了如何创建具有 10 个 double 类型元素的 array 容器: std::array 【提示,如果程序中已经默认指定了 std 命令空间,这里可以省略 std::】 由此,就创建好了一个名为 values 的 array 容器,其包含 10 个浮点型元素。但是,由于未显式指定这 10 个元素的值,因此使用这种方式创建的容器中,各个元素的值是不确定的。 std::array 使用该语句,容器中所有的元素都会被初始化为 0.0。 在创建 array 容器的实例时,也可以像创建常规数组那样对元素进行初始化: std::array 可以看到,这里只初始化了前 4 个元素,剩余的元素都会被初始化为 0.0。如下图所示: array容器成员函数 成员函数 功能 begin() 返回指向容器中第一个元素的随机访问迭代器。 end() 返回指向容器最后一个元素之后一个位置的随机访问迭代器,通常和 begin() 结合使用。 rbegin() 返回指向最后一个元素的随机访问迭代器。 rend() 返回指向第一个元素之前一个位置的随机访问迭代器。 cbegin() 和 begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素。 cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 size() 返回容器中当前元素的数量,其值始终等于初始化 array 类的第二个模板参数 N。 max_size() 返回容器可容纳元素的最大数量,其值始终等于初始化 array 类的第二个模板参数 N。 empty() 判断容器是否为空,和通过 size()==0 的判断条件功能相同,但其效率可能更快。 at(n) 返回容器中 n 位置处元素的引用,该函数自动检查 n 是否在有效的范围内,如果不是则抛出 out_of_range 异常。 front() 返回容器中第一个元素的直接引用,该函数不适用于空的 array 容器。 back() 返回容器中最后一个元素的直接应用,该函数同样不适用于空的 array 容器。 data() 返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能。 fill(val) 将 val 这个值赋值给容器中的每个元素。 array1.swap(array2) 交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型。 示例: 运行之输出结果为: 3 附录、参考资料 https://blog.csdn.net/u010183728/article/details/81913729 https://www.nhooo.com/cpp/cpp-stl-tutorial.html https://blog.csdn.net/acceptedday/article/details/118529280#include
一民,你好啊!
#include
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
[]越界:1596032
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 20) >= this->size() (which is 10)
#include
7
6 4 2 0 1 3 5 7#include
using namespace std;
int cmp(const int &a,const int &b){
//简单的自定义降序序列
return a>b;
}
int main(){
list
#include
using namespace std;
int main()
{
//创建list对象
list
1#include
2
3
3
存在#include
1
2
3
1
3
存在#include
1
a 10
b 20
c 30#include
2
a 10
b 20
b 15
c 30#include
0 1 2 3