模板库(STL)属于C++标准库的一部分,定义并声明了标准模板。
STL是一套功能强大的 C++ 模板类。STL的目的是为了标准化组件,这样就不用重新开发,让后来者可以使用现成的组件。
1、STL六大组件:
容器(containers)、算法(algorithms)、迭代器(iterators)、函数对象(functors)、适配器(adapters)、分配器(allocators)。
六大组件的交互关系:
container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接 functor(仿函数)。
- 容器(container)
容器就是一种用来存放数据的对象。
(1)容器是容器类实例化之后的一个具体的对象;
(2)容器类是基于类模板(也就是STL)定义的,存放的数据类型是相同的;
(3)容器可以自行扩展。它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行命令;
(4)STL对定义的通用容器分三类:
顺序性容器:vector、deque、list
关联性容器:set、multiset、map、multimap
容器适配器:stack、queue、 - 算法(algorithm)
算法作用于容器,包括对容器内容进行初始化,排序,搜索和转换等操作。
STL算法分为四类:
(1)非可变序列算法:不直接修改容器中内容。
(2)可变序列算法:可以直接修改容器的内容。
(3)排序算法:包含对序列的排序,合并,搜索和对有序序列的操作等算法那。
(4)数值算法:对容器内容计算。 - 迭代器(iterator)
迭代器是一种对象,用来遍历STL容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址,所以可以认为迭代器其实就是用来指向容器中数据的指针,我们可以通过改变这个指针来遍历容器中的所有元素。 - 函数对象(functor)
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。
注意:
函数对象(仿函数)是一个类,不是一个函数。
函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。
分类:
假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”(unary functor);相反,如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)。
API 意义:
算术类:
template<class T> T plus<T> 加法仿函数
template<class T> T minus<T> 减法仿函数
template<class T> T multiplies<T> 乘法仿函数
template<class T> T divides<T> 除法仿函数
template<class T> T modulus<T> 取模仿函数
template<class T> T negate<T> 取反仿函数
关系运算类:
template<class T> bool equal_to<T> 等于
template<class T> bool not_equal_to<T> 不等于
template<class T> bool greater<T> 大于
template<class T> bool greater_equal<T> 大于等于
template<class T> bool less<T> 小于
template<class T> bool less_equal<T> 小于等于
逻辑运算类:
template<class T> bool logical_and<T> 逻辑与
template<class T> bool logical_or<T> 逻辑或
template<class T> bool logical_not<T> 逻辑非
- 适配器(adapter)
c++中的适配器有三种:
容器适配器:因为这些容器都是基于其他标准容器实现的所以叫做容器的适配器,具体的有stack,queue,priority_queue。默认的情况下,stack和queue基于deque而实现的,priority_queue在vector上实现的,可以根据第二个实参指定容器的类型,但一定要符合标准,queue要求要有push_front操作因此不能建立在vector上面,priority_front要求有随机访问的功能,因此建立在vector上面。优先级队列默认情况下是大顶堆,也就是大者优先级高,后面可以自定义优先级比较规则;
迭代器适配器:包括三种reverse(逆向)适配器,insert(安插型)迭代器,stream(串流)适配器。
reverse适配器:如rbegin(),rend()等。
insert迭代器:是一种迭代器适配器,带有一个容器参数,并生成一个迭代器,提供了三种插入器back_inserter(容器),front_inserter(容器),inserter(容器,位置)。
stream适配器:如ostream_iterator,istream_iterator。
函数适配器:用于扩展一元和二元函数对象,如functor 函数对像等等。用于容器与算法之前的操作时使用。 - 分配器(allocator)
分配器就用于处理容器对内存的分配与释放请求。换句话说,分配器用于封装STL容器在内存管理上的低层细节。默认情况下,C++标准库使用其自带的通用分配器,但根据具体需要,也可自行定制分配器以替代之。
二、容器使用举例
1、vector
vector向量是一种顺序行容器。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。
在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作:
首先,vector 会申请一块更大的内存块;
然后,将原来的数据拷贝到新的内存块中;
其次,销毁掉原内存块中的对象(调用对象的析构函数);
最后,将原来的内存空间释放掉。
当vector保存的数据量很大时,如果此时进行插入数据导致需要更大的空间来存放这些数据量,那么将会大大的影响程序运行的效率,所以我们应该合理的使用vector。
- 初始化vector对象的方式:
vector<T> v1; // 默认的初始化方式,内容为空
vector<T> v2(v1); // v2是v1的一个副本
vector<T> v3(n, i) // v3中包含了n个数值为i的元素
vector<T> v4(n); // v4中包含了n个元素,每个元素的值都是0
- vector常用函数
empty():判断向量是否为空,为空返回真,否则为假
begin():返回向量(数组)的首元素地址
end(): 返回向量(数组)的末元素的下一个元素的地址
clear():清空向量
front():返回得到向量的第一个元素的数据
back():返回得到向量的最后一个元素的数据
size():返回得到向量中元素的个数
push_back(数据):将数据插入到向量的尾部
pop_back():删除向量尾部的数据
.....
- 遍历方式
vector向量支持两种方式遍历,因为可以认为vector是一种动态数组,所以可以使用数组下标的方式,也可以使用迭代器。
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;
int main(void)
{
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);
cout << "向量的大小:" << vec.size() << endl;
// 数组下标方式遍历vector
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << " ";
cout << endl;
// 迭代器方式遍历vector
vector<int>::iterator itor = vec.begin();
for (; itor != vec.end(); itor++)
cout << *itor << " ";
cout << endl;
return 0;
}
2、list
双向链表,数据元素包括:指向前一个链表节点的前向指针、有效数据、指向后一个链表节点的后向指针
链表相对于vector向量来说的优点在于:
(a)动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,在次分配更大的空间,这样的话效率就比较低了;
(b)支持内部插入、头部插入和尾部插入
缺点:
不能随机访问,不支持[]方式和vector.at()、占用的内存会多于vector(非有效数据占用的内存空间)
- 初始化list对象的方式
list<int> L0; //空链表
list<int> L1(3); //建一个含三个默认值是0的元素的链表
list<int> L2(5,2); //建一个含五个元素的链表,值都是2
list<int> L3(L2); //L3是L2的副本
list<int> L4(L1.begin(),L1.end()); //c5含c1一个区域的元素[begin, end]。
- list常用函数
begin():返回list容器的第一个元素的地址
end():返回list容器的最后一个元素之后的地址
rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)
rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)
front():返回链表中第一个数据值
back():返回链表中最后一个数据值
empty():判断链表是否为空
size():返回链表容器的元素个数
clear():清除容器中所有元素
insert(pos,num):将数据num插入到pos位置处(pos是一个地址)
insert(pos,n,num):在pos位置处插入n个元素num
erase(pos):删除pos位置处的元素
push_back(num):在链表尾部插入数据num
pop_back():删除链表尾部的元素
push_front(num):在链表头部插入数据num
pop_front():删除链表头部的元素
sort():将链表排序,默认升序
......
- 遍历方式
双向链表list支持使用迭代器正向的遍历,也支持迭代器逆向的遍历,但是不能使用 [] 索引的方式进行遍历。
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;
int main(void)
{
list<int> l1;
// 插入元素方式演示
l1.push_front(1); // 头部插入
l1.push_back(2); // 尾部插入
l1.insert(l1.begin(), 3); // 开始位置插入
l1.insert(l1.end(), 4); // 结束位置插入
cout << "链表是否为空:" << l1.empty() << endl;
cout << "list链表中元素个数:" << l1.size() << endl;
cout << "list链表第一个元素:" << l1.front() << endl;
cout << "list链表最后一个元素:" << l1.back() << endl;
// 遍历链表正向
list<int>::iterator itor = l1.begin();
for (; itor != l1.end(); itor++)
cout << *itor << " ";
cout << endl;
// 遍历链表逆向
list<int>::reverse_iterator reitor = l1.rbegin();
for (; reitor != l1.rend(); reitor++)
cout << *reitor << " ";
cout << endl;
// 将链表排序
l1.sort();
itor = l1.begin();
cout << "重新排序之后正向遍历:";
for (; itor != l1.end(); itor++)
cout << *itor << " ";
cout << endl;
// 清除容器中的所有元素
l1.clear();
cout << "清除容器所有元素之后大小:" << l1.size() << endl;
return 0;
}
3、map
Map是STL的一个关联容器,它提供一对一(其中第一个称为关键字,每个关键字只能在map中出现一次,第二个称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。map容器提供一种“键- 值”关系的一对一的数据存储能力。其“键”在容器中不可重复,且按一定顺序排列。
- 初始化map对象的方式
map<int, string> m1 = { { 1, "guangzhou" }, { 2, "shenzhen" }, { 3, "changsha" } }; // 实例化一个map容器,还有3组数据
map<char, string> m2; // 实例化一个空map容器
- map常用函数
begin():返回容器第一个元素的迭代器
end():返回容器最后一个元素之后的迭代器
rbegin():
rend():
clera():清除容器中所有元素
empty():判断容器是否为空
insert(p1):插入元素 p1 是通过pair函数建立的映射关系对
insert(pair<char, string>('S', "shenzhen")): 插入元素
size():返回容器中元素的个数
count():返回指定键对应的数据的出现的次数
get_allocator():返回map的配置器
swap():交换两个map容器的元素
......
- 遍历方式
map容器支持迭代器正向方式遍历和迭代器反向方式遍历,同时也支持 [] 方式访问数据,[]中的索引值是键值。
#include <iostream>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
using namespace std;
int main(void)
{
map<int, string> m1 = { { 1, "guangzhou" }, { 2, "shenzhen" }, { 3, "changsha" } };
map<char, string> m2;
// 建立映射关系对
pair<char, string> p1('G', "guangzhou");
pair<char, string> p2('S', "guangzhou");
pair<char, string> p3('C', "changsha");
// 插入数据
m2.insert(p1);
m2.insert(p2);
m2.insert(p3);
cout << "map容器m1元素个数:" << m1.size() << endl;
cout << "map容器m2元素个数:" << m2.size() << endl;
// 采用 [] 方式打印数据
cout << m1[1] << " " << m1[2] << " " << m1[3] << endl;
cout << m2['G'] << " " << m2['S'] << " " << m2['C'] << endl;
// 迭代器正向方式遍历
map<int, string>::iterator itor = m1.begin();
for (; itor != m1.end(); itor++)
{
cout << itor->first << ",";
cout << itor->second << endl;
}
// 迭代器反向方式遍历
map<char, string>::reverse_iterator reitor = m2.rbegin();
for (; reitor != m2.rend(); reitor++)
{
cout << reitor->first << ",";
cout << reitor->second << endl;
}
// 清空容器
m1.clear();
m2.clear();
return 0;
}
参考博客:
C++标准模板库