C++之STL(标准模板库)介绍
一、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 命名空间中定义,其中大部分算法都包含在头文件 <algorithm> 中,少部分位于头文件 <numeric> 中。 |
迭代器 |
在 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<int> v(10,5); //创建一个向量v,其已开辟10个元素的空间并全部赋值为5
vector<int>::iterator it; //用迭代器(iterator)访问
for(it=v.begin();it!=v.end();it++){
cout<<*it<<' ';
}
当然,遍历也可以直接使用下标访问:
for(int i=0;i<v.size();i++){
cout<<v[i]<<' ';
}
算法
STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以你所指定的规则对序列进行稳定性排序等等。只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能并大大地提升效率。
算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。
<algorithm>是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。
<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。
<functional>中则定义了一些模板类,用以声明函数对象。
STL中算法大致分为四类:
非可变序列算法:指不直接修改其所操作的容器内容的算法。
可变序列算法:指可以修改它们所操作的容器内容的算法。
排序算法:对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作。
数值算法:对容器内容进行数值计算。
二、C++ STL几个常见容器的使用
★Vector(向量)容器
Vector是最简单的序列是容器,就像数组一样,向量使用连续的存储位置作为元素,这意味着它们的元素也可以使用常量指向其元素的偏移来访问,与数组一样有效。但与数组不同,它们的大小可以动态变化,其存储由容器自动处理。
Vector就是一个动态创建空间,且预先加载了常用的数组操作的数组。
相关文件
头文件:#include <vector>
格式为:vector<Data_Types> name;
如:
vector<int> v1; //创建一个空的向量v1
vector<int> v2(10); //创建一个向量v2,其已开辟10个元素的空间,相当于int v[10];
vector<int> v3(10,5); //创建一个向量v3,其已开辟10个元素的空间并全部赋值为5
下面给出一个完整示例:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<string> v1;
//添加 2个值到向量中
v1.push_back("一民");
v1.push_back(",你好啊!");
// 使用迭代器 iterator 访问值
for(vector<string>::iterator itr=v1.begin();itr!=v1.end();++itr){
cout<<*itr;
}
return 0;
}
运行输出:
一民,你好啊!
vector的遍历有多种方式:
[]方式,如果越界或出现其他错误,不会抛出异常,可能会崩溃,可能数据随机出现;
at方式,如果越界或出现其他错误,会抛出异常,需要捕获异常并处理;
迭代器提供了逆向遍历,可以通过迭代器来实现逆向遍历,当然上面两种方式也可以。
如:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//创建vector
vector<int> v1;
//插入元素
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
//遍历-[]取值
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << endl;
//遍历-at取值
for (int i = 0; i < v1.size(); i++) {
cout << v1.at(i) << " ";
}
cout << endl;
//遍历-迭代器遍历
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
//遍历-迭代器逆向遍历
for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++) {
cout << *it << " ";
}
cout << endl;
//测试越界
cout << "[]越界:" << v1[20] << endl; //不会抛出异常,可能会崩溃,可能会乱码
cout << "at越界:" << v1.at(20) << endl; //会抛出异常,需要捕获异常
return 0;
}
运行输出:
0 1 2 3 4 5 6 7 8 9
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)
关于上面实例中所使用的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<object_type> deque_name;
如:
deque<type> deq; // 声明一个元素类型为type的双端队列que
deque<type> deq(size); //声明一个类型为type、含有size个默认值初始化元素的的双端队列que
deque<type> deq(size, value); //声明一个元素类型为type、含有size个value元素的双端队列que
deque<type> deq(mydeque); // deq是mydeque的一个副本
deque<type> deq(first, last); // 使用迭代器first、last范围内的元素初始化deq
实例
#include<iostream>
#include<deque>
using namespace std;
int main(void)
{
int i;
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
deque<int> q;
for (i = 0; i <= 9; i++)
{
if (i % 2 == 0)
q.push_front(a[i]);
else
q.push_back(a[i]);
} //此时队列里的内容是: {8,6,4,2,0,1,3,5,7,9}
q.pop_front();
cout << q.front()<< endl; //清除第一个元素后输出第一个(6)
q.pop_back();
cout << q.back() << endl; //清除最后一个元素后输出最后一个(7)
deque<int>::iterator it;
for (it = q.begin(); it != q.end(); it++) {
cout << *it << '\t';
}
cout << endl;
system("pause");
return 0;
}
运行输出:
6
7
6 4 2 0 1 3 5 7
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 <object_type> list _name;
如:
list<int> l1; //创建一个空链表
list<int> l2(10); //创建一个链表其有10个空元素
list<int> l3(5,20); //创建一个链表其有5个元素内容为20
实例
#include<iostream>
#include<list>
using namespace std;
int cmp(const int &a,const int &b){
//简单的自定义降序序列
return a>b;
}
int main(){
list<int> li; //创建一个空链表
for(int i=10;i>=6;i--){
li.push_back(i);
}
li.push_front(3);
li.push_back(20);
list<int> li2(li);
for(list<int>::iterator it=li.begin();it!=li.end();it++){
cout<<*it<<' ';
}
cout<<endl;
//排序前3 10 9 8 7 6 20//
li.sort();
for(list<int>::iterator it=li.begin();it!=li.end();it++){
cout<<*it<<' ';
}
cout<<endl;
//默认排序后 3 6 7 8 9 10 20//
li2.sort(cmp);
for(list<int>::iterator it=li2.begin();it!=li2.end();it++){
cout<<*it<<' ';
}
cout<<endl;
//自定义排序后 20 10 9 8 7 6 3//
return 0;
}
运行输出:
3 10 9 8 7 6 20
3 6 7 8 9 10 20
20 10 9 8 7 6 3
list容器具有如下特性:
可以在头部和尾部插入和删除元素;
不能随机访问元素,也就是迭代器只能只能++,不能一次性跳转。
如:
#include<iostream>
#include<list>
using namespace std;
int main()
{
//创建list对象
list<int> l;
//尾部添加元素
for (int i = 0; i < 10; i++) {
l.push_back(i);
}
//头部添加元素
l.push_front(22);
//遍历
for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
cout << *it << " ";
}
cout << endl;
//list不能随机访问
list<int>::iterator it = l.begin();
it++;
it++;
cout << *it <<endl;
// it = it + 5; //编译报错,不能随机访问
return 0;
}
运行输出:
22 0 1 2 3 4 5 6 7 8 9
1
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>
创建语法格式为:set| multiset <object_type> name;
如:
set<int> set1; //创建默认的从小到大的int类型的集合
set<int,less<int> > set2; //创建一个从小打到大的int类型的集合,注意> >之间有空格。
set<int,greater<int> > set3; //创建一个从大到小的int类型的集合,注意> >之间有空格。
特别提示:set/multiset容器是有序的集合,默认的顺序是从小到大的。
Set示例:
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s;
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(1);
//输出结果按照从小到大的顺序排序
for (set<int>::iterator x = s.begin(); x != s.end(); x++)
{
cout << *x << endl;
}
cout << endl;
s.erase(2); //删除一个元素
for (set<int>::iterator x = s.begin(); x != s.end(); x++)
{
cout << *x << endl;
}
//判断一个元素是否存在
if (s.count(1))
cout << "存在\n";
else
cout << "不存在\n";
return 0;
}
运行输出:
1
2
3
1
3
存在
Multiset示例:
#include<iostream>
#include<set>
using namespace std;
int main()
{
multiset<int> s;
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(1);
//输出结果按照从小到大的顺序排序
for (multiset<int>::iterator x = s.begin(); x != s.end(); x++)
{
cout << *x << endl;
}
cout << endl;
s.erase(2); //删除一个元素
for (multiset<int>::iterator x = s.begin(); x != s.end(); x++)
{
cout << *x << endl;
}
//判断一个元素是否存在
if (s.count(1))
cout << "存在\n";
else
cout << "不存在\n";
return 0;
}
运行输出:
1
1
2
3
1
1
3
存在
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<map>。
相关文件
头文件:#include <map>
创建语法格式为:map| multimap <object_type> name;
如:
map<int, string> mapName;
map示例:
#include <iostream>
#include <map> //map
using namespace std;
int main()
{
//创建并初始化 multimap 容器
map<char, int>mymap{ {'a',10},{'b',20},{'b',15}, {'c',30} };
//输出 mymap 容器存储键值对的数量
cout << mymap.size() << endl;
//输出 mymap 容器中存储键为 'b' 的键值对的数量
cout << mymap.count('b') << endl;
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
运行输出:
3
1
a 10
b 20
c 30
multimap示例:
#include <iostream>
#include <map> //map
using namespace std;
int main()
{
//创建并初始化 multimap 容器
multimap<char, int>mymultimap{ {'a',10},{'b',20},{'b',15}, {'c',30} };
//输出 mymultimap 容器存储键值对的数量
cout << mymultimap.size() << endl;
//输出 mymultimap 容器中存储键为 'b' 的键值对的数量
cout << mymultimap.count('b') << endl;
for (auto iter = mymultimap.begin(); iter != mymultimap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
运行输出:
4
2
a 10
b 20
b 15
c 30
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 容器以类模板的形式定义在 <array> 头文件,并位于命名空间 std 中,因此,在使用该容器之前,代码中需引入 <array> 头文件,并默认使用 std 命令空间,如下所示:
#include <array>
using namespace std;
array 容器有多种初始化方式,如下代码展示了如何创建具有 10 个 double 类型元素的 array 容器:
std::array<double, 10> values;
【提示,如果程序中已经默认指定了 std 命令空间,这里可以省略 std::】
由此,就创建好了一个名为 values 的 array 容器,其包含 10 个浮点型元素。但是,由于未显式指定这 10 个元素的值,因此使用这种方式创建的容器中,各个元素的值是不确定的。
std::array<double, 10> values {};
使用该语句,容器中所有的元素都会被初始化为 0.0。
在创建 array 容器的实例时,也可以像创建常规数组那样对元素进行初始化:
std::array<double, 10> values {0.5,1.0,1.5,,2.0};
可以看到,这里只初始化了前 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 容器中的所有元素,但前提是它们具有相同的长度和类型。
示例:
#include <iostream>
//需要引入 array 头文件
#include <array>
using namespace std;
int main()
{
std::array<int, 4> values{};
//初始化 values 容器为 {0,1,2,3}
for (int i = 0; i < values.size(); i++) {
values.at(i) = i;
}
//使用 get() 重载函数输出指定位置元素
cout << get<3>(values) << endl;
//如果容器不为空,则输出容器中所有的元素
if (!values.empty()) {
for (auto val = values.begin(); val < values.end(); val++) {
cout << *val << " ";
}
}
}
运行之输出结果为:
3
0 1 2 3