STL
STL(standard template libaray ):标准模板库,是C++程序设计语言的标准程序库, 是一个包罗算法与数据结构的软件框架
STL的目的是标准化组件,所以在STL中使用了泛型编程的思想,对我们常用的数据结构:顺序表、链表、树、哈希以及常用的查找、排序等算法使用模板进行了封装,而且从运行效率以及内存使用上都基本达到了优。
引入STL后,再也不需要我们重新造轮子, 而且写出来的代码更加简洁,容易修改,可移植性高。万一STL所提供的容器或者算法不能满足我们的要求,我们也可以实现自己的容器或算法与STL中的其他组件进行交融。
STL六大组件
容器
容器,是用来放东西的,而STL中的容器则是来放数据的,因此也称数据容器。由于对数据进行的操作不同,使用的场景各异,可能需要相应的数据结构来管理数据,常见的数据结构:array、list、tree、stack、queue、hash table、map、set等,因此STL中的容器便是对各种不同数据结构的封装。
根据数据在容器中的排序特性,容器分为序列式容器和关联式容器
序列式容器中的元素次序:即是按照其加入容器中的先后次序,不一定有序
Vector
序列化容器之一
vector底层维护了一段动态的连续空间,随着元素的不断插入,vector的内部机制会自动检测,决定是否需要进行扩容以容纳新元素
当vector检测到空间不足时,动态开辟原空间大小两倍的空间,接着将旧空间中的元素高效的拷贝到新空间中,后释放旧空间,所有的操 作对用户来说都是透明的。
底层架构
vector的迭代器
因为vector底层是一段连续空间,因此vector的迭代器被设计为一个原生态指针,即: 所存储元素类型的指针vector操作
使用vector时,必须包含vector头文件:< vector >
必须引入标准命名空间std
http://www.cplusplus.com/reference/vector/vector/?kw=vector
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <vector>
void TestVector1()
{
// 构造一个空的 v e c t o r
vector<int> v1;
// 构造一个空的 v e c t o r, 但是底层空间大小设置为 2 0 个元素
vector<int> v2(20);
// 构造一个 v e c t o r 对象,并用数组进行初始化
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
vector<int> v3(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << "size= " << v1.size() << endl;//0
cout << "capacity= " << v1.capacity() << endl;//0
cout << "size= " << v2.size() << endl;//20
cout << "capacity= " << v2.capacity() << endl;//20
cout << "size= " << v3.size() << endl;//10
cout << "capacity= " << v3.capacity() << endl;//10
v1 = v3;
cout << "size= " << v1.size() << endl;//101
cout << "capacity= " << v1.capacity() << endl;//10
}
void TestVector2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
cout << "size= " << v.size() << endl;//5
cout << "capacity= " << v.capacity() << endl;//6
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";//1 2 3 4 5
}
cout << endl;
v.pop_back();
v.pop_back();
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";//1 2 3
}
cout << endl;
// 任意位置插入
// 在 vector 起始位置插入数据 10
v.insert(v.begin(), 10);
v.insert(v.end(), 10);
// 在 vector 末尾的位置插入 2 个值为 100 的元素
v.insert(v.end(), 2, 100);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";//10 1 2 3 10 100 100
}
cout << endl;
cout << "size= " << v.size() << endl;//7
cout << "capacity= " << v.capacity() << endl;//9
// 删除任意位置元素
// 删除起始位置
v.erase(v.begin());
// 删除指定区间内的元素,注意区间是[)
v.erase(v.begin(), v.begin()+2);
// 使用 v e c t o r 迭代器遍历 v e c t o r 中所有元素
vector<int>::iterator it = v.begin();
while (it != v.end()){
cout << *it++ << " ";//3 10 100 100
}
cout << endl;
cout << "size= " << v.size() << endl;//4
cout << "capacity= " << v.capacity() << endl;//9
}
void TestVector3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";
}
cout << endl;
// a s s i g n() 方法是给 v e c t o r 进行赋值
// 在进行赋值之前,该方法会先将 v e c t o r 中原有的旧元素 e r a s e 掉
// 然后再将新元素插入进去
// 给 v e c t o r 中 5 个值为 1 0 的元素
v.assign(5, 10);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";
}
cout << endl;
// 将 v e c t o r 中的元素增加到 8 个
// 注意:如果增加到某个个数而没有超过 v e c t o r 的实际容量
// v e c t o r 底层的容量不会改变
v.reserve(8);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";
}
cout << endl;
cout << "size= " << v.size() << endl;//5
cout << "capacity= " << v.capacity() << endl;//8
// 将 v e c t o r 中的元素增加的 2 0 个
// 注意:如果第二个参数没有传,多处的元素使用缺省值,否则多出的元素使用传递的值
v.reserve(20);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";
}
cout << endl;
cout << "size= " << v.size() << endl;//0
cout << "capacity= " << v.capacity() << endl;//6
v.reserve(20);
for (size_t i = 0; i < v.size(); ++i){
cout << v.at(i) << " ";
}
cout << endl;
// 将 v e c t o r 中的元素清空,注意底层容量的空间不变
v.clear();
cout << "size= " << v.size() << endl;//0
cout << "capacity= " << v.capacity() << endl;//6
}
int main()
{
TestVector1();
cout << endl;
TestVector2();
cout << endl;
TestVector3();
system("pause");
return 0;
}
list
vector底层搭载一段连续空间,因此在其任意位置进行数据的插入或删除时效率是非常低下(时间复杂度:O(N)),因此,当集合中需要进行大量的插入和删除操作时候,可以考虑list
- 底层架构
list的底层是一个带头结点的双向循环链表,因此在其任意位置进行数据插入和删除操作非常方便,时间复杂度均为O(1)
list迭代器
- 由于list底层是带头结点的双向循环链表,因此list的迭代器需要list的实现者自己提供, 否则当让迭代器++依次朝后走走访链表时,迭代器将不知道如何朝后移动。
- 迭代器的本质是指针,是将指针封装出来的一种新的类型,因此指针有的操作,迭代器也要视情况支持这些操作。
- 比如:指针可以++,- -,*,->等操作,迭代器在其类中只需将这些操作重载出来即可,后只需将list迭代器类型与list容器绑定在一起即可(将迭代器作为list的一种内部类型使用)。
- 当利用迭代器来遍历list时,在迭代器上进行++操作, 它内部根据自己底层的结构就知道如何朝后去移动。
list操作
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void TestList1()
{
// 构造空的 l i s t
list<int> l1;
// 构造 1 0 个值为 1 的 l i s t
list<int> l2(10, 1);
// 构造用一段区间中的元素构造 l i s t
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> l3(array, array + sizeof(array) / sizeof(array[0]));
// 用 l 3 去构造 l 4
list<int> l4(l3);
cout << "l1中元素个数:" << l1.size() << endl; //0
cout << "l2中元素个数:" << l2.size() << endl; //10
cout << "l3中元素个数:" << l3.size() << endl; //9
cout << "l4中元素个数:" << l4.size() << endl; //9
l1 = l2;
// 遍历 l i s t 1
list<int>::iterator it1 = l1.begin();
while (it1 != l1.end())
{
cout << *it1++ << " "; // 1 1 1 1 1 1 1 1 1 1
}
cout << endl;
//逆向遍历 list3
list<int>::reverse_iterator it3 = l3.rbegin();
while (it3 != l3.rend())
{
cout << *it3++ << " ";//9 8 7 6 5 4 3 2 1
}
cout << endl;
}
void TestList2()
{
list<int> l;
//尾插
l.push_back(1);
l.push_back(2);
l.push_back(3);
//头插
l.push_front(4);
l.push_front(5);
l.push_front(6);
list<int>::iterator it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//6 5 4 1 2 3
}
cout << endl;
//头删
l.pop_front();
l.pop_front();
//尾删
l.pop_back();
l.pop_back();
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//4 1
}
cout << endl;
l.push_front(10);
l.push_front(11);
l.push_back(12);
l.push_back(13);
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//11 10 4 1 12 13
}
cout << endl;
//任意位置插入insert,该函数返回插入的新节点
//find():从first找到last直到找到所要查找的数字
list<int>::iterator pos = find(l.begin(), l.end(), 1);
if (pos != l.end())
pos = l.insert(pos, 0);
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//11 10 4 0 1 12 13
}
cout << endl;
//任意位置删除
l.erase(pos);
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//11 10 4 1 12 13
}
cout << endl;
//给l重新赋值
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
l.assign(arr, arr + sizeof(arr) / sizeof(arr[0]));
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//1 2 3 4 5 6 7 8 9 0
}
cout << endl;
cout << "获取链表第一个元素:" << l.front() << endl;
cout << "获取链表最后一个元素:" << l.back() << endl;
}
class Odd
{
public:
bool operator()(int value)
{
return value & 0x01;//奇数返回真
}
};
void TestList3()
{
int arr[] = { 1, 2, 3, 4, 3, 5, 3, 3, 6, 7, 8, 3, 9, 0 };
list<int> l(arr, arr + sizeof(arr) / sizeof(arr[0]));
list<int>::iterator it = l.begin();
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";
}
cout << endl;
//测试remove
//删除所有指定元素
l.remove(3);
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//1 2 4 5 6 7 8 9 0
}
cout << endl;
//测试remove_if
//按条件删除指定元素
l.remove_if(Odd());//删除奇数
it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//2 4 6 8 0
}
cout << endl;
}
class Three
{
public:
bool operator()(int left, int right)
{
return 0 == (left + right) % 3;
}
};
void TestList4()
{
int array[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
list<int>::iterator it = l.begin();
while (it != l.end())
{
cout << *it++ << " ";//1 2 3 4 5 1 2 3 4 5 6 7 8 9
}
cout << endl;
//测试unique
//将list中“相邻”相同的元素删除
l.unique();
it = l.begin();
while (it != l.end())
{
cout << *it++ << " "; //1 2 3 4 5 1 2 3 4 5 6 7 8 9
}
cout << endl;
//sort
//单链表排序从小到大
l.sort();
it = l.begin();
while (it != l.end())
{
cout << *it++ << " "; // 1 1 2 2 3 3 4 4 5 5 6 7 8 9
}
cout << endl;
l.unique();
it = l.begin();
while (it != l.end())
{
cout << *it++ << " "; //1 2 3 4 5 6 7 8 9
}
cout << endl;
//按指定方法删除元素
//如果 l i s t 中连续两个数之和为 3 的倍数时,删除第二个数
l.unique(Three());
it = l.begin();
while (it != l.end())
{
cout << *it++ << " "; //1 2 3 4 5 6 7 8 9
}
cout << endl;
}
void TestList5()
{
//测试merge
//将两个有序链表合并,仍然有序
list<int>l1;
l1.push_back(1);
l1.push_back(3);
l1.push_back(5);
list<int>l2;
l2.push_back(2);
l2.push_back(4);
l2.push_back(6);
l1.merge(l2);
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
cout << *it++ << " ";//1 2 3 4 5 6
}
cout << endl;
//反转链表
l1.reverse();
it = l1.begin();
while (it != l1.end())
{
cout << *it++ << " ";//6 5 4 3 2 1
}
cout << endl;
}
int main()
{
TestList1();
cout << endl;
TestList2();
cout << endl;
TestList3();
cout << endl;
TestList4();
cout << endl;
TestList5();
system("pause");
return 0;
}
vector和list的区别和使用场景
vector数据结构
- vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);
- 但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
- 另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。
list数据结构
- list是由双向链表实现的,因此内存空间是不连续的。
只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n); - 但由于链表的特点,能高效地进行插入和删除。
- list是由双向链表实现的,因此内存空间是不连续的。
//vector和list使用试例
#include<iostream>
#include<vector>
#include<list>
using namespace std;
int main()
{
vector<int> v;
list<int> l;
for(int i=0;i<8;i++) 往v和l中分别添加元素
{
v.push_back(i);
l.push_back(i);
}
cout<<"v[2]="<<v[2]<<endl;
//cout<<"l[2]="<<l[2]<<endl; //编译错误,list没有重载[]
cout<<(v.begin()<v.end())<<endl;
//cout<<(l.begin()<l.end())<<endl; /编译错误,list::iterator没有重载<或>
cout<<*(v.begin()+1)<<endl;
//cout<<*(l.begin()+1)<<endl; //编译错误,list::iterator没有重载+
vector<int>::iterator itv=v.begin();
list<int>::iterator itl=l.begin();
itv = itv+2;
//itl=itl+2; //编译错误,list::iterator没有重载+
itl++; //list::iterator中重载了++,只能使用++进行迭代访问。
itl++;
cout<<*itv<<endl;
cout<<*itl<<endl;
getchar();
return 0;
}
vector拥有一段连续的内存空间,能很好的支持随机存取。因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的内存空间可以是不连续,它不支持随机访问,
因此list::iterator则不支持“+”、“+=”、“<”等vector::iterator和list::iterator都重载了“++”运算符。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;如果需要大量的插入和删除,而不关心随机存取,则应使用list。
deque
vector是单向开口的真正连续空间,deque是双向开口的假想连续空间。
所谓双向开口:是指可以在头尾两端分别进行元素的插入和删除操作,因此deque要求在常数时间内对头端进行数据的插入和删除操作
- deque的底层结构
deque的底层由许多段定量的连续空间构成,一旦有需要,其内部机制会自动配置一段定量连续空间,串接在整个deque空间的头端和尾端。deque实际是维护一些分段固定大小的连续空间,将这些分段分段连续的空间管理好,造成其连续空间的假象,用户在使用时如同在操作一段连续空间。
deque的迭代器
- deque是分段连续空间,维护其“整体连续”假象的任务就落在其迭代器 operator++和operator–上。
- 因此deque的迭代器必须能够知道分段连续空间在哪里, 其次迭代器还必须能够识别出自己是否已经处于其所在某段连续空间的边缘,如果是,一旦前进或后退时就必须跳跃至下一个或上一个分段空间 。
- deque的操作
使用deque时,必须包含其头文件< deque >和命名空间std
deque在STL中实际是一种比较鸡肋的容器,一般不会用到。
比如:在数据集合中尾插和尾删方式较多时,则可以考虑vector;在数据集合中任意位置插入或删除方式较多时, 则可以考虑list。因此对于deque,只需将其底结构搞和包含操作函数理清楚。
适配器
适配器是设计模式的一种,该模式是将一个类的接口转换成用户希望的另外一个接口。简单的说:就是需要的东西就在眼前,但却不能直接用或者使用不是很方便,而短时间内无 法实现它。如果想要快速实现,有一种办法就是通过已存在的东西去适配它。
STL中的适配器共有三种类型;
- 应用于容器的即容器适配器,比如stack和queue就是对deque的接口进行了转调;
- 应用于迭代器的即迭代器适配器,比如反向迭代器就是对迭代器的接口进行了转调;
- 应用于仿函数的即函数适配器
容器适配器
stack和queue都是一种特殊的线性数据结构,要求在其固定端进行数据的插入和删除操作;比如:
- stack:要求在其一端进行数据的插入和删除即入栈和出栈,称为栈顶;另一端称为栈底;因此stack是一种后进先出(FILO)的线性结构
- queue:要求在尾部进行数据的插入操作即入队列,在其头部进行数据的删除即出 队列,因此queue是一种后进先出的线性结构
deque是双开口的结构,因此STL将其作为栈和队列的底层结构,将deque稍加改装就实现出stack和队列。
像这种:将某个类的接口进行重新包装而实现出的新结构,称之为适配器
关联式容器
序列式容器中元素大小杂乱无章没有次序,因此在其中找某个元素只能使用顺序查找,效率有点低下(其时间复杂度O(N))。
为了提高数据查询的效率以及数据的有序性,关联式容器采用了平衡搜索树作为其底层结构,并且容器中存放的不再是一个一个的数据,而是具有关联关系的键值对<key, value>
。
- 二叉搜索树
但如果向容器中插入元素时,元素序列恰巧是有序或者接近有序,二叉搜索树将会退化为单支树,类似于链表,查询效率又降低到O(N)
因此,序列式容器底层并没有直接采用二叉搜索树结构,而是对二叉搜索树进行优化,使二叉搜索树中每个结点的左右子树高度差的绝对值不超过1来保证树的平衡性
- AVL树
当插入新节点后,AVL树的平衡性有可能会被破坏,一旦破坏,就必须对树的结构进行旋转处理,以保证AVL树的平衡性。
根据插入元素的位置不同,AVL树的旋转分为四种情况:
- 插入新节点位于较高左子树左侧—-左左—右单旋
- 插入新节点位于较高右子树右侧—-右右—左单旋
- 插入新节点位于较高左子树右侧—-左右—左右双旋
- 插入新节点位于较高右子树左侧—-右左—右左双旋
因为AVL树中每个结点的左右子树高度差的绝对值不超过1,因此其查找效率可以达到log2(N) 。但每次向AVL树中插入或删除节点时,只要发现树不平衡,就要进行旋转,而 删除时有可能旋转多次。
在实际的应用中也发现AVL的插入和删除效率不是很高,因此序列式容器底层也没有使用AVL树,而采用了另一种近似平衡的二叉搜索树
- 红黑树
红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是 red或者black,通过对任何一条从根节点到叶子结点简单路径上结点的颜色来约束, 后保证长路径不超过短路径的两倍,因而近似平衡。
红黑树性质:
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
虽然红黑树是一棵近似平衡的搜索树,但是在实际应用中表现出的性能确实比AVL树更优,因此关联式容器选择将红黑树作为其底层结构。
map
map的特性是:所有元素都会根据元素的键值key自动排序,其中的每个元素都是
void TestMap()
{
map<string, string> m;
m.insert(pair<string, string>("鲁智深","花和尚"));
m.insert(make_pair("宋江", "及时雨"));
m["林冲"] = "豹子头";
cout << "map中元素的个数为: " << m.size() << endl;
// 将 m a p 中所有元素输出来
map<string, string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << "--->" << it->second << endl;
++it;
}
cout << endl;
// 通过 k e y 查找 k e y 所对应的 v a l u e
cout << "林冲" << "--->" << m["林冲"] << endl;
// 通过[] 访问键值对中的 v a l u e 时,如果没有该 k e y ,
// m a p 会建立一个默认 v a l u e(字符串为默认空) 的键值对插入进去,
//然后将该 v a l u e 返回
cout << "王伦" << "--->" << m["王伦"] << endl;
cout << "map中元素的个数为: " << m.size() << endl;
// 修改 k e y 所对应的 v a l u e
cout << "宋江" << "--->" << m["宋江"] << endl;
m["宋江"] = "呼保义";
cout << "宋江" << "--->" << m["宋江"] << endl;
// 删除
m.erase(m.find("王伦"));
cout << "map中元素的个数为: " << m.size() << endl;
// 将 m a p 中的元素清空
m.clear();
cout << "map中元素的个数为: " << m.size() << endl;
}
multimap
multimap和map的唯一差别是map中key必须是唯一的,而multimap中的key是可以重复的,其底层结构及方法接口与map完全相同
void TestMultimap()
{
multimap<int, int> m;
for (int i = 0; i < 10; ++i)
m.insert(make_pair(i, i));
m.insert(make_pair(5, 11));
m.insert(make_pair(5, 12));
m.insert(make_pair(5, 13));
m.insert(make_pair(5, 14));
multimap<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << "--->" << it->second << endl;
++it;
}
cout << endl;
cout << "key为5的元素个数: " << m.count(5) << endl;
//区间为 [)
//返回所查元素区间的第一个元素
it = m.lower_bound(5);
cout << it->first << "--->" << it->second << endl;//5-->5
//返回所查元素区间的最后一个位置
it = m.upper_bound(5);
cout << it->first << "--->" << it->second << endl;//6-->6
//返回区间
typedef multimap<int, int>::iterator Iterator;
pair<Iterator, Iterator> ret = m.equal_range(5);
cout << (ret.first)->first << "--->" << (ret.first)->second << endl;
cout << (ret.second)->first << "--->" << (ret.second)->second << endl;
}
set
与map相同的是:底层均采用红黑树,因此所有元素都会根据元素的键值自动排序
与map不同的是:map中存放真正的键值对< key, value>,而set中key就是value, value就是key,虽然set提供给用户的接口只存放value,而实际底层真正存放的是< value, value>的键值对,即set中key就是value,value就是key 。
- 用 set 对集合中的元素去重
void TestSet1()
{
int arr[] = { 1, 2, 3, 3, 4, 8, 6, 5, 6, 3, 7, 8, 2, 9, 0 };
set<int> s;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
s.insert(arr[i]);
}
cout << s.size() << endl;
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it++ << " ";
}
cout << endl;
}
void TestSet2()
{
int arr[] = { 1, 2, 3, 4, 5 };
set<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << s.size() << endl;
cout << s.count(3) << endl;
s.insert(3);
cout << s.size() << endl;
cout << s.count(3) << endl;
s.insert(6);
cout << s.size() << endl;
cout << s.count(3) << endl;
s.erase(1);
cout << s.size() << endl;
cout << s.count(3) << endl;
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it++<< " ";
}
cout << endl;
it = find(s.begin(), s.end(), 1);
if (it != s.end())
cout << "1 is in set" << endl;
else
cout << "1 is not in set!!!" << endl;
// set 中的元素不能被修改,会破坏树的结构,
//因此其迭代器实际为 c o n s t 类型迭代器
it = s.begin();
//*it = 10;
}
set的迭代器有iterator和const_iterator, 但是, 对于set这个特殊的关联容器, 这两者都是一样的, 也就是说: set中的元素只可以读, 不可以写。
multiset
multiset与set唯一的不同就是:multiset中的元素可以重复,而set不能重复。
迭代器
迭代器(itreator)是一种抽象的设计概念,是设计模式的一种,其定义如下:提供一种方法,使之能够依次寻访某个容器中所包含的所有元素,而又无需暴露该容器底层的结构
STL设计的中心思想在于:将数据容器和算法分离开,彼此独立设计,算法要操作容器中的元素时,通过迭代器去访问即可。因此,算法不需要去关心所操作数据底层的结构,只 要能够按照迭代器去寻访到所需的数据即可,实现其通用性。比如:
template <class iterator,class T>
iterator _find(iterator first, iterator last, const T& value)
{
while (first != last && *first != value)
first++;
return first;
}
void TestFind()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
vector<int> v(arr,arr+sizeof(arr)/sizeof(arr[0]));
if (_find(v.begin(), v.end(), 5) != v.end())
cout << "5 is in vector" << endl;
else
cout << "5 is not in vector" << endl;
list<int> l(arr, arr + sizeof(arr) / sizeof(arr[0]));
if (_find(l.begin(), l.end(), 5) != l.end())
cout << "5 is in list" << endl;
else
cout << "5 is not in list" << endl;
- 迭代器本质
迭代器实际是一种行为类似指针的对象,因此指针的所有操作迭代器都必须要支持,使用迭代器时可以像使用指针一样去使用。比如:指针的解引用、成员访问、前置/后置++, 前置/后置–,==, !=等迭代器都要支持,而迭代器是一种行为类似指针的新类型,因此迭代器的实现只需将指针的上述操作在类中重载即可。
- 迭代器是实现
迭代器是算法和容器的粘合剂,同一个算法可以操作不同类型的容器,而容器类型不容, 意味着底层数据结构不容,数据结构不同,迭代器寻访的方式就不同
容器的设计者负责该容器迭代器的实现,根据容器底层数据结构的特点,选择指针的相应操作去重载即可。
比如vector和list的迭代器就不同,因为vector底层是一段连续的空间,其迭代器可以直接搭载原生态指针,而list底层是带头结点的双向循环链表,其迭代器实现需将原生态指针重新进行封装,后再以统一的接口出现即可。
- 迭代器失效
迭代器无法使用,说明迭代器的指针引用的空间非法,即空间被释放
vector :插入操作需要增容,开辟新空间,拷贝元素,释放旧空间,此时没有及时更新迭代器
list:删除操作,没有及时更新迭代器
map:删除操作,没有及时更新迭代器
仿函数
仿函数:又叫函数对象,一种行为类似函数的对象,调用者可以向函数一样使用该对象。 其实现起来也比较简单:用户只需要实现一种新类型,在类中重载()即可,参数根据用户所要进行的操作选择匹配
//实现冒泡排序
template<class T>
class Less
{
public:
bool operator()(const T& left, const T& right)
{
return left < right;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T& left, const T& right)
{
return left > right;
}
};
template <class T,class Com>
void BubbleSort(T* arr, size_t size, Com Compare)
{
bool isChange = false;
for (size_t i = 0; i < size; ++i)
{
isChange = false;
for (size_t j = 0; j < size - i - 1; ++j)
{
if (Compare(arr[j + 1], arr[j]))
{
swap(arr[j], arr[j + 1]);
isChange = true;
}
}
if (!isChange)
return;
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 0, 8, 6, 4, 2 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i;
BubbleSort(arr, sz, Less<int>());
for (i = 0; i < sz; ++i)
cout << arr[i] << " ";//0 1 2 3 4 5 6 7 8 9
cout << endl;
BubbleSort(arr, sz, Greater<int>());
for (i = 0; i < sz; ++i)
cout << arr[i] << " ";//9 8 7 6 5 4 3 2 1 0
cout << endl;
system("pause");
return 0;
}