C++:STL

一、STL-容器(容器的简单使用,底层实现,适用场景)

1> vector

向量是一个封装了动态大小数组的顺序容器。跟任意其他类型容器一样,它能够存放各种类型的对象,可以理解为vector是一个能够存放任意类型的动态数组,能够增加和压缩数据

特征是相当于可以分配拓展的数组,它的随机访问快,在中间插入和删除慢,但在末端插入和删除快

**适用条件:**使用数组来进行插入删除查找
ps:因为其没有数组下标,其插入方便的同时也有中间插入删除慢的问题

特点
1、 一个动态分配的数组(当数组空间内存不足时,都会执行: 分配新空间-复制元素-释放原空间);
2、当删除元素时,不会释放限制的空间,所以向量容器的容量(capacity)大于向量容器的大小(size);
3、 对于删除或插入操作,执行效率不高,越靠后插入或删除执行效率越高;
4、高效的随机访问的容器。

使用:
头文件:

#include <vector> 

基本操作

//容量
vec.size();//向量大小
vec.max_size();//向量最大容量
vec.resize();//更改向量大小
vec.capacity();//向量真实大小
vec.empty();//向量判空
vec.shrink_to_fit(); //减少向量大小到满足元素所占存储空间的大小
//修改
vec.assign(); //多个元素赋值,类似于初始化时用数组进行赋值
vec.push_back();//末尾添加元素 
vec.pop_back();//末尾删除元素
vec.insert();//任意位置插入元素
vec.erase();//任意位置删除元素
vec.swap();//交换两个向量的元素
vec.clear();//清空向量元素
//迭代器
vec.begin();//开始指针
vec.end(); //指向最后一个元素的下一个位置
vec.cbegin(); //指向常量的开始指针,意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
vec.cend();//指向常量的末尾指针: 
//元素的访问
vec[1]; //下标访问,并不会检查是否越界
vec.at(1); //at方法访问,以上两者的区别就是at会检查是否越界,是则抛出out of range异常
vec.front();//访问第一个元素:
vec.back();//访问最后一个元素: 
int* p = vec.data(); //返回一个指针,可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。

底层采用数据结构为一段连续的线性内存空间
使用3个迭代器表示

//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
    ...
protected:
    pointer _Myfirst; //指向vector容器对象的起始字节位置
    pointer _Mylast;  //指向当前最后一个元素的末尾字节
    pointer _Myend;   //指向整个 vector 容器所占用内存空间的末尾字节
};

对于空的 vector 容器,由于没有任何元素的空间分配,因此 _Myfirst、_Mylast 和 _Myend 均为 null

vector扩容本质
当vector的大小和容量相等(size == capacity)也就是满载时,如果再向其添加元素,那么vector就需要扩容
扩容步骤:
1、完全弃用现有的内存空间,重新申请更大的内存空间;
2、将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
3、将旧的内存空间释放

所以vector容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效,而且vector扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时vector都会申请比用户需求量更多的内存空间以便后续使用(但不同编译器申请更多内存空间的量是不同的,如VS通常扩容现有容器容量的50%)。

2> list

list是STL实现的双向链表,与vector相比,它允许快速的插入和删除,但是随机访问却比较慢

**适用条件:**需要大量的插入和删除,而不关心随机存取

使用:
头文件:

#include <list> 

基本操作

Lst1.assign() 给list赋值 
Lst1.back() 返回最后一个元素 
Lst1.begin() 返回指向第一个元素的迭代器 
Lst1.clear() 删除所有元素 
Lst1.empty() 如果list是空的则返回true 
Lst1.end() 返回末尾的迭代器 
Lst1.erase() 删除一个元素 
Lst1.front() 返回第一个元素 
Lst1.get_allocator() 返回list的配置器 
Lst1.insert() 插入一个元素到list中 
Lst1.max_size() 返回list能容纳的最大元素数量 
Lst1.merge() 合并两个list 
Lst1.pop_back() 删除最后一个元素 
Lst1.pop_front() 删除第一个元素 
Lst1.push_back() 在list的末尾添加一个元素 
Lst1.push_front() 在list的头部添加一个元素 
Lst1.rbegin() 返回指向第一个元素的逆向迭代器 
Lst1.remove() 从list删除元素 
Lst1.remove_if() 按指定条件删除元素 
Lst1.rend() 指向list末尾的逆向迭代器 
Lst1.resize() 改变list的大小 
Lst1.reverse() 把list的元素倒转 
Lst1.size() 返回list中的元素个数 
Lst1.sort() 给list排序 
Lst1.splice() 合并两个list 
Lst1.swap() 交换两个list 
Lst1.unique() 删除list中重复的元素

底层实现
list 容器实际上就是一个带有头节点的双向循环链表

3> deque

特点
1、double-ended queue 双端队列
2、具有分段数组、索引数组, 分段数组是存储数据的,索引数组是存储每段数组的首地址
3、向两端插入元素效率较高,中间插入元素效率较低(若向两端插入元素,如果两端的分段数组未满,既可插入;如果两端的分段数组已满,则创建新的分段函数,并把分段数组的首地址存储到deque容器中即可)

**适用条件:**需要随机存取,而且关心两端数据的插入和删除

使用:
头文件:

#include <deque> 

基本操作

deq.size();  //容器大小
deq.max_size();  //容器最大容量
deq.resize();    //更改容器大小
deq.empty();     //容器判空
deq.shrink_to_fit();         //减小容器大小到满足元素所占存储空间大小

deq.push_front(const T& x);  //头部添加元素
deq.push_back(const T& x);   //末尾添加元素
deq.insert(iterator it, const T& x);       //任意位置插入一个元素
deq.insert(iterator it, int n, const T& x);//任意位置插入 n 个相同元素
deq.insert(iterator it, iterator first, iterator last);//插入另一个向量的 [forst,last] 间的数据

deq.pop_front();//头部删除元素
deq.pop_back();//末尾删除元素
deq.erase(iterator it);//任意位置删除一个元素
deq.erase(iterator first, iterator last);//删除 [first,last] 之间的元素
deq.clear();//清空所有元素

deq[1];//下标访问,并不会检查是否越界
deq.at(1);//at 方法访问,以上两者的区别就是 at 会检查是否越界,是则抛出 out of range 异常
deq.front();//访问第一个元素
deq.back();//访问最后一个元素

deq.assign(int nSize, const T& x);//多个元素赋值,类似于初始化时用数组进行赋值
swap(deque&);//交换两个同类型容器的元素

deq.begin();//开始迭代器指针
deq.end();//末尾迭代器指针,指向最后一个元素的下一个位置
deq.cbegin();//指向常量的开始迭代器指针,即不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
deq.cend();//指向常量的末尾迭代器指针
deq.rbegin();//反向迭代器指针,指向最后一个元素
deq.rend();//反向迭代器指针,指向第一个元素的前一个元素

底层实现
deque 容器存储数据的空间是由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,可以位于在内存的不同区域

为了管理这些连续空间,deque 容器用数组存储着各个连续空间的首地址。也就是说,数组中存储的都是指针,指向那些真正用来存储数据的各个连续空间
deque容器的底层存储机制
通过建立 map 数组,deque 容器申请的这些分段的连续空间就能实现“整体连续”的效果。换句话说,当 deque 容器需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map 数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部

如果 map 数组满了,再申请一块更大的连续空间供 map 数组使用,将原有数据(很多指针)拷贝到新的 map 数组中,然后释放旧的空间

4> set/multiset

为关联式容器,是有序的,升序排列,自动排序;实现的是一个平衡二叉树,每个元素都有一个父节点和两个子节点,左子树的所有元素都比自己小,右子树的所有元素都比自己大

特点
构造set集合的主要目的是为了快速检索,去重与排序;
set存储的是一组无重复的元素,而multiset允许存储有重复的元素;
不提供直接用来存取元素的任何操作元素;
通过迭代器进行元素的存取;
如果要修改某一个元素值,必须先删除原有的元素,再插入新的元素

自动排序的主要优点在于使二叉树搜寻元素具有良好的性能,在其搜索函数算法具有对数复杂度。但是自动排序也造成了一个限制,不能直接改变元素值,因为这样会打乱原有的顺序,要改变元素的值,必须先删除旧元素,再插入新元素

底层实现
二叉搜索树虽然可以缩短查找效率,但如果数据有序或接近有序,二叉搜索树将退化为单枝树,查找元素相当于在顺序表中搜索元素,效率低下
当向二叉搜索树中插入新节点后,保证每个节点的左右子树高度之差的绝对值不超过1,既可降低树的高度,从而减少平均搜索长度即可解决以上问题
故采用AVL树或者是空树实现(保证以下条件即可)
他的左右子树都是AVL树且左右子树高度之差的绝对值不超过1即可

基本使用

s.size();//返回当前的元素数量
s.empty ();//判断大小是否为零,等同于0==size()0==size(),效率更高
count (elem);//返回元素值为elemelem的个数
find(elem);//返回元素值为elemelem的第一个元素,如果没有返回end()
lower _bound(elem);//返回元素值>=elem>=elem的第一个元素位置
upper _bound (elem);//返回元素值>elem>elem的第一个元素位置
s.begin();//返回一个随机存取迭代器,指向第一个元素
s.end();//返回一个随机存取迭代器,指向最后一个元素的下一个位置
s.insert(elem);//插入一个elemelem副本,返回新元素位置,无论插入成功与否
s.erase(elem);//删除与elemelem相等的所有元素,返回被移除的元素个数
s.erase(pos);//移除迭代器pospos所指位置元素,无返回值
s.clear();//移除所有元素,将容器清空

5> map/multimap

为关联式容器,是有序的,升序排列,自动排序;实现的是一个平衡二叉树,每个元素都有一个父节点和两个子节点,左子树的所有元素都比自己小,右子树的所有元素都比自己大

适用场景
去重类问题、可以打乱重新排序的问题、有清晰的一对一关系的问题

特点
1、map为单重映射、multimap为多重映射;
2、主要区别是map存储的是无重复键值的元素对,而multimap允许相同的键值重复出现,既一个键值可以对应多个值。
3、 map内部自建了一颗红黑二叉树,可以对数据进行自动排序,所以map里的数据都是有序的,这也是我们通过map简化代码的原因。
4、自动建立key-value的对应关系,key和value可以是你需要的任何类型。
5、key和value一一对应的关系可以去重。

底层实现
红黑树,因此在进行查询,修改,删除等操作上具有很高的效率,可以达到O(logN)

基本使用

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的函数

6> stack、queue、priority_equal

这三者是容器适配器,分别为栈(stack)、队列(queue)、优先队列(priority_equal)提供简单易用的接口,满足编程中的特殊数据处理需求

适配器是标准库中的一个通用概念。容器、迭代器、函数本质上都是适配器。适配器是一种机制,能是某种事物的行为看起来像另一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型

stack

stack中的元素具有后进先出的特点。stack只能从一端插入、删除、读取元素,不允许一次插入或删除多个元素,且不支持迭代器操作, stack类型是定义在头文件中的一种数据结构,在使用时我们只需要将头文件包含进自己创建的cpp文件中即可

stack<value_type>   stk;     //定义一个栈,value_type指代栈中元素类型,可以是int、string等
stk.pop();       //删除栈顶元素,但不返回栈顶的元素
stk.top();        //返回栈顶的元素,但不会将栈顶元素弹出
push(item);       //将item元素的副本压入栈顶,该元素通过拷贝或移动而来。
stk.emplace(arg);    //将arg放置在stk中,作用相当于push
size();            //返回栈中元素的个数
empty();        //判断栈是否为空,若栈为空,返回false,否则返回true
swap(stk1);      //将stk和stk1中元素互换,此操作并不会真正交换他们的元素,只是交换了两个容器的内部数据结构,可以用指针的思想类比理解
swap(stk1,stk2);      //此函数是非成员版本的函数,功能和成员版本的swap相同

栈是一个后进先出的数据结构,有很多广泛的用处,例如在中缀表达式转后缀表达式中便很有用处,将此方法用于实践便可编写出一个可以计算加减乘除的简易的计算器了。

queue

queue中的元素具有先进先出的特点,元素只能从一端使用push()函数进行插入,从另一端使用pop()函数进行删除。queue也不允许一次插入或删除多个元素,且不支持迭代器操作,queue定义在头文件中,使用时要将头文件包含进去

queue<value_type>    q;//定义一个队列
q.pop();      //删除queue的队头元素
q.front();    //返回队列的队头元素,但不删除该元素
q.back();    //返回队列的队尾元素,但不删除该元素
q.push(arg);      //将元素arg插入到队列的队尾
q.emplace(arg);    //将元素arg放置到队列的尾部,作用和push一样
q.size();     //返回队列中元素的个数
q.empty();    //当队列为空时返回true,否则返回false
q.swap(q1);     //交换q和q1中的元素,方法和stack中一样,并不会真正使用拷贝形式进行交换,只是交换底层的数据结构
swap(q,q1);      //非成员函数,和成员函数swap一样

队列在我们实际的应用中有很大的作用,许多领域都涉及到排队问题,比如一个服务系统,会根据用户的申请服务的时间依次进行服务

priority_queue

priority_queue中的元素可以按照自定义的方式进行动态排序。向priority_queue中插入或删除元素时,priority_queue会动态地调整,以保证元素有序,作为队列的一个延伸,优先队列同样是一种很有用的数据结构,它包含在头文件中

优先队列是一种重要的数据结构,它是由二项队列编写而成的,可以以log(n)的效率查找一个队列中最大值或最小值(最大值和最小值是由你选择创建的优先队列的性质决定的),这在很多场合可以派上很大的用处,例如prim算法如果结合优先队列可以产生出很好的效果

优先队列是一种重要的数据结构,它是由二项队列编写而成的,可以以log(n)的效率查找一个队列中最大值或最小值(最大值和最小值是由你选择创建的优先队列的性质决定的),这在很多场合可以派上很大的用处,例如prim算法如果结合优先队列可以产生出很好的效果

priority_queue的模板生命是带有三个参数的:priority_queue<type,container,function>,其中,type是数据的类型,container为实现优先队列的底层容器,function为元素间的比较方式,其中container要求必须是数组形式实现的容器,如vector,deque,而不能是
list。在c++标准库中,默认情况下是以vector为容器,以operator<为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素

priority_queue<int>   temp;//定义一个默认的优先队列
temp.pop();      //删除priority_queue的队头元素
temp.top();       //返回priority_queue优先队列中队头元素,但不删除该元素
temp.push(arg);      //将元素arg插入到优先队列中
temp.emplace(arg);    //将元素arg放置到优先队列中,作用和push一样
temp.size();     //返回优先队列中元素的个数
temp.empty();    //当优先队列为空时返回true,否则返回false
temp.swap(temp1);      //交换temp和temp1中的元素,并不会真正使用拷贝形式进行交换,只是交换底层的数据结构
swap(temp,temp1)      //非成员函数,和成员函数swap一样

公共函数

size_type;     //一种类型,足以保存当前类型的最大对象的大小
value_type;      //元素类型
container_type;    //实现适配器的底层容器类型
A  a;           //创建一个名为a的空适配器
A  a(c);      创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符     //每个适配器都支持所有关系运算符:==、!=、<、<=、>、和>=这些运算符返回底层容器的比较结果
a.empty();       //若a包含容纳和元素,返回false,否则返回true
a.size();           //返回a中元素的个数
swap(a,b);     //交换a和b的内容,a和b必须有相同类型,包括底层容器类型也比必须相同
a.swap(b)

对于适配器的构造有两种方式:一种是用默认构造函数创建一个空对象,另一种是接收一个容器作为参数进行拷贝初始化

deque<int>   myDeque;

            ......//假设经过一些操作后myDeque中已经有了一些元素

            stack<int>   myStack(myDeque);      // 将myDeque中的元素拷贝到myStack中

默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的

也可以在创建一个适配器时通过将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型

stack<int,vector<int> >   str_stk1;    //在vector上实现的空栈,也就是说此时的栈的数据存放是以vector为底层的
stack<int,vector<int> >    str_stk2(vec);      //vec是一个存放了int型数据的一个vector容器,此时str_stk2对vec进行拷贝初始化的

参考资料:https://blog.csdn.net/qq_29883591/article/details/50932827

二、STL-函数对象

重载函数调用操作符的类,其对象称为函数对象(function object),即他们是行为类似函数的对象,也叫仿函数(functor),其实就是重载"()"操作符,使得类对象可以像函数那样被调用但函数对象(仿函数)是一个类,不是一个函数

在c++中,可以像对待其他运算符一样对待函数调用运算符();这个运算符也可以重载。
()运算符能够返回任何类型,可以使用任何数量的参数,但和赋值运算符一样,该运算符只能重载为成员函数。
包含函数调用运算符定义的对象称为函数对象。
函数对象也是对象,只是它们的行为表现得像函数而已。
当调用函数对象时,其参数是函数调用运算符的参数

内建函数对象

STL 内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件 #include

1> 一元函数对象

假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,就称其为“一元函数对象(unary functor)”,二元函数对象同理

//算术类函数对象
template<class T> T negate<T>//取反仿函数
//逻辑运算类运算函数
template<class T> bool logical_not<T>//逻辑非

2> 二元函数对象

//算数类函数对象,
template<class T> T plus<T>//加法仿函数
template<class T> T minute<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<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>//逻辑或

三、迭代器

1> iterator

指针可以用来遍历存储空间连续的数据结构,但是对于存储空间费连续的,就需要寻找一个行为类似指针的类,来对非数组的数据结构进行遍历

迭代器是一种检查容器内元素并遍历元素的数据类型
C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只用少数容器(如vector)支持下标操作访问容器元素,迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围
迭代器(Iterator)是指针(pointer)的泛化,它允许程序员用相同的方式处理不同的数据结构(容器)

迭代器类似于C语言里面的指针类型,它提供了对对象的间接访问
指针是C语言中的知识点,迭代器是C++中的知识点。指针较灵活,迭代器功能较丰富
迭代器提供一个对容器对象或者string对象的访问方法,并定义了容器范围

迭代器和指针的区别:
容器和string有迭代器类型同时拥有返回迭代器的成员。如:容器有成员begin和end,其中begin成员复制返回指向第一个元素的迭代器,而end成员返回指向容器尾元素的下一个位置的迭代器,也就是说end指示的是一个不存在的元素,所以end返回的是尾后迭代器

常用操作

*iter                //对iter进行解引用,返回迭代器iter指向的元素的引用
iter->men            //对iter进行解引用,获取指定元素中名为men的成员。等效于(*iter).men
++iter                //给iter加1,使其指向容器的下一个元素
iter++
--iter                //给iter减1,使其指向容器的前一个元素
iter--
iter1==iter2        //比较两个迭代器是否相等,当它们指向同一个容器的同一个元素或者都指向同同一个容器的超出末端的下一个位置时,它们相等 
iter1!=iter2

假设已经声明一个vector的ivec容器,下面用迭代器来遍历ivec容器,把其每个元素重置为0

for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
        *iter=0;

在C++定义的容器类型中,只有vectorqueue容器提供迭代器算数运算和除!=和==之外的关系运算

iter+n     //在迭代器上加(减)整数n,将产生指向容器中钱前面(后面)第n个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一个元素
iter-n
iter1+=iter2        //将iter1加上或减去iter2的运算结果赋给iter1。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素
iter1-=iter2
iter1-iter2            //两个迭代器的减法,得出两个迭代器的距离。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素
>,>=,<,<=        //元素靠后的迭代器大于靠前的迭代器。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素

注意两个迭代器相减得出两个迭代器对象的距离,该距离名为difference_type的signed类型的值,该类型类似于size_type类型,也是有vector定义的。可以迭代器算术操作来移动迭代器直接指向某个元素

vector<int>::iterator    mid=v.begin()+v.size()/2;    //初始化mid迭代器,使其指向v中最靠近正中间的元素

2> const_iterator

每种容器还定义了一种名为const_iterator的类型。该类型的迭代器只能读取容器中的元素,不能用于改变其值。之前的例子中,普通的迭代器可以对容器中的元素进行解引用并修改,而const_iterator类型的迭代器只能用于读不能进行重写

for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
     cout<<*iter<<endl;       //合法,读取容器中元素值

for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
    *iter=0;        //不合法,不能进行写操作

const_iterator和const iterator是不一样的,后者对迭代器进行声明时,必须对迭代器进行初始化,并且一旦初始化后就不能修改其值。这有点像常量指针和指针常量的关系

vector<int>    ivec(10);
const    vector<int>::iterator    iter=ivec.begin();
*iter=0;    //合法,可以改变其指向的元素的值
++iter;    //不合法,无法改变其指向的位置

3> reverse_iterator

反向迭代器适配器(reverse_iterator),可简称为反向迭代器或逆向迭代器,常用来对容器进行反向遍历,即从容器中存储的最后一个元素开始,一直遍历到第一个元素

反向迭代器底层可以选用双向迭代器或者随机访问迭代器作为其基础迭代器
通过对 ++(递增)和 --(递减)运算符进行重载,使得:
当反向迭代器执行 ++ 运算时,底层的基础迭代器实则在执行 – 操作,意味着反向迭代器在反向遍历容器;
当反向迭代器执行 – 运算时,底层的基础迭代器实则在执行 ++ 操作,意味着反向迭代器在正向遍历容器

实现反向迭代器的模板类定义在 头文件,并位于 std 命名空间中

反向迭代器是通过操纵内部的基础迭代器实现逆向遍历的,但是反向迭代器的指向和底层基础迭代器的指向并不相同,反向迭代器的指向和其底层基础迭代器的指向具有这样的关系,即反向迭代器的指向总是距离基础迭代器偏左 1 个位置;反之,基础迭代器的指向总是距离反向迭代器偏右 1 个位置处
反向迭代器和基础迭代器的关系

operator*	//以引用的形式返回当前迭代器指向的元素
operator+	//返回一个反向迭代器,其指向距离当前指向的元素之后 n 个位置的元素。此操作要求基础迭代器为随机访问迭代器
operator++	//重载前置 ++ 和后置 ++ 运算符。
operator+=	//当前反向迭代器前进 n 个位置,此操作要求基础迭代器为随机访问迭代器
operator-	//返回一个反向迭代器,其指向距离当前指向的元素之前 n 个位置的元素。此操作要求基础迭代器为随机访问迭代器
operator--	//重载前置 -- 和后置 -- 运算符。
operator-=	//当前反向迭代器后退 n 个位置,此操作要求基础迭代器为随机访问迭代器
operator->	//返回一个指针,其指向当前迭代器指向的元素
operator[n]	//访问和当前反向迭代器相距 n 个位置处的元素

4> 使迭代器失效的操作

由于一些对容器的操作如删除元素或移动元素等会修改容器的内在状态,这会使得原本指向被移动元素的迭代器失效,也可能同时使其他迭代器失效。使用无效的迭代器是没有定义的,可能会导致和使用悬垂指针相同的问题。所以在使用迭代器编写程序时,需要特别留意哪些操作会使迭代器失效。使用无效迭代器会导致严重的运行时错误
参考资料:https://www.cnblogs.com/maluning/p/8570717.html

四、空间配置器(allocator)

在使用内存池时,如果先开辟一个大空间且只使用了一部分,那么就在开辟空间的同时还构造了对象,浪费资源
为解决这一问题我们将对象的生成的两个步骤分开执行:只开辟内存 allocate()、单独调用构造 construct();将对象的销毁的两个步骤分开执行单独调用虚构 destory()、只释放内存 deallocate()

空间适配器的工作原理就是将对象的生成和销毁的四个步骤单独的分离出来,让他们单独的执行

容器适配器主要提供以下函数

allocate	//负责开辟内存
deallocate  //负责释放内存
construct	//负责在容器中构造对象
destroy	    //负责析构对象

1> 一级空间配置器

malloc/free
一级空间配置器就是将 malloc和free进行了封装,即在开辟内存时调用malloc,释放内存时调用free,没有内存池。如果开辟的内存>128个字节时,不容易产生内存碎片化的问题,这时系统会调用一级空间适配器。

2> 二级空间配置器

内存池
二级空间配置器有内存池,在开辟内存和释放内存时会使用内存池。如果开辟的内存<=128个字节时,容易产生内存碎片化的问题,这时系统会调用二级空间适配器。

二级空间配置器的内存池是一个自由链表内存池。开辟了一个长度固定的指针数组,这个指针数组的大小为16

这个指针数组的每个单元格都指向一个自由链表,第一个自由链表的每个内存单元格的大小都是8,第二个自由链表的每个内存单元格的大小都是16,以此类推。然后根据要开辟内存的大小来选择合适的自由链表来存储数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值