map 和 set 都是C++的关联容器,其底层实现是红黑树(RB-Tree)。
map和set区别:
(1)map中的元素是键值对:关键字起到索引作用,值则表示与索引相关联的数据;set就是关键字的简单集合,set中每个元素只包含一个关键字。
(2)set的迭代器是const的, 不允许修改元素的值;map允许修改value,不允许修改key。其原因是map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先要删除该键,调节平衡,然后插入修改的键,再调节平衡,这样一来,严重破坏了map和set的结构,导致迭代器失效,不知道应该指向修改前的位置,还是指向修改后的位置。
(3)map支持下标操作,set不支持。map可以使用key来做下标进行查找,如果key不存在,则创建一个具有该关键码和value类型默认值的元素值map中,因此下标运算符在map中药慎用,const_map不能用(会报错),只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。
STL的分配器是用于封装STL容器在内存管理上的底层细节。在C++中其内存配置和释放如下:
new运算分为两个阶段:调用::operator new 配置内存;调用对象构造函数构造对象内容。
delete运算分为两个阶段:调用对象析构函数;调用::operator delete释放内存。
为了精密分工,STL allocator将两个阶段操作区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construction()负责,对象析构由::destroy()负责。
同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。
(1)迭代器概念:迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,换句话说,迭代器就是指针的简单包装,通过一个模板类封装的对象。
(2)对于序列容器 (如vector,deque)来说,使用erase(iterator)后,后边的每个元素的迭代器都会失效。这是因为顺序容器内存是连续分配,删除一个元素导致后面所有元素都会往前移动一个位置。(删除一个元素后,后面的所有元素都要挪位置,此时iter++已经指向的是未知内存)。
(3)对于关联容器(如map, set)来说,使用erase(iterator)之后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响到下一个元素的迭代器,所以在调用erase()之前,记录下一个元素的迭代器即可。
(4)对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种正确的方法都可以使用。
4、STL 基本组成
STL(standard template library,标准模板库)由容器、算法、迭代器、内存分配器、适配器、函数对象(仿函数)组成。
(1)容器:一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。
(2)算法:STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 中。
(3)迭代器:在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。
(4)仿函数(函数对象): 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。
(5)适配器:可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。
(6)分配器:为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。
(1)vector:连续存储的容器,在堆上分配内存,底层实现是动态数组。vector容器在插入新元素时,如果为未超过当时的容量,则还有剩余空间,那么直接添加到最后,然后调整迭代器。如果没有剩余的空间,则会配置原有元素个数两倍空间(不同编译器扩容的量是不同的),然后将原空间元素通过复制的方式初始化新空间,在向新空间添加元素,最后释放原空间,之前的迭代器失效。
适用于随机访问,且不经常对非尾节点进行插入和删除的场景。
(2)list:动态链表,在堆上分配内存,底层实现是双向链表,每插入一个元素都会分配空间,每删除一个元素都会释放空间。
随机访问性能很差,只能快速访问头尾结点,使用与经常进行插入和删除操作的场景。
6、STl中迭代器的作用,有指针为何 要迭代器?
(1)迭代器:Iterator(迭代器)模式又称cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
(2)迭代器和指针区别:迭代器表现的像指针,但不是指针,而是封装了指针的类模板(主要是通过重载指针的一些操作符'->' 、'*' 、++、--等),他可以根据不同类型的数据结构来实现不同的++,- - 等操作。