C++基础——STL常见问题总结

2023-05-16

1. STL由哪些组件组成

  • 容器(Containers):各种数据结构,如:vector、list、deque、set、map。用来存放数据。从实现的角度来看,STL容器是一种class template。
  • 算法(algorithms):各种常用算法,如:sort、search、copy、erase。从实现的角度来看,STL算法是一种 function template
  • 迭代器(iterators):容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,以及其他衍生变化。从实现的角度来看,迭代器是一种将 operator*、operator->、operator++、operator- - 等指针相关操作进行重载的class template。所有STL容器都有自己专属的迭代器,只有容器本身才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
  • 仿函数(functors):行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般的函数指针也可视为狭义的仿函数。
  • 适配器(配接器 adapters):一种用来修饰容器、仿函数、迭代器接口的东西。例如:STL提供的queue 和 stack,虽然看似容器,但其实只能算是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。改变 functors接口者,称为function adapter;改变 container 接口者,称为container adapter;改变iterator接口者,称为iterator adapter。
  • 配置器(allocators):负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。

2. vector的底层实现机制

vector是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。

3. vector中 resize()扩容和reserve()的区别

resize()扩容的默认构造的方式是0, 之后插入按照一定的倍数扩容(GCC是2倍扩容,VS是1.5倍扩容);扩容后重新申请一片新的内存,需要把旧内存空间中的所有元素都拷贝进新内存空间中去,然后在新内存中进行操作;同时释放旧内存空间,此时会导致旧vector的所有迭代器都失效了。

reserve()只是保证vector的空间大小(_capacity)最少达到它的参数所指定值n;在区间[0, n)范围内,预留了内存但是并未初始化只有当所申请的容量大于vector的当前容量capacity时才会重新为vector分配存储空间;小于当前容量则没有影响,reserve方法对于vector元素大小没有任何影响,不创建对象。

vector的初始的扩容方式代价太大,初始扩容效率低, 需要频繁增长,不仅操作效率比较低,而且频繁的向操作系统申请内存容易造成过多的内存碎片,所以这个时候需要合理使用resize()和reserve()方法提高效率减少内存碎片的。

4. vector的扩容为何按倍数进行,而不是固定值

扩容以倍数进行增长,当申请频繁时相对于固定值的方式可以减少申请的次数;假设vector初始的capacity=10,size=0,总共有100个元素,以2倍的形式增长,需4次,如果以固定值的方式按10的话需要10次。

5. STL常用容器以及他们的特点

  • vector:底层数据结构为数组 ,支持快速随机访问。
  • list:底层数据结构为双向链表,支持快速增删。
  • deque:底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问。
  • stack:底层一般用list 或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
  • queue:底层一般用list 或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
  • priority_queue:的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
  • set:底层数据结构为红黑树,有序,不重复。
  • multiset:底层数据结构为红黑树,有序,可重复。 
  • map:底层数据结构为红黑树,有序,不重复。
  • multimap:底层数据结构为红黑树,有序,可重复。
  • hash_set:底层数据结构为hash表,无序,不重复。
  • hash_multiset:底层数据结构为hash表,无序,可重复 。
  • hash_map :底层数据结构为hash表,无序,不重复。
  • hash_multimap:底层数据结构为hash表,无序,可重复。

6. list的底层实现机制

以结点为单位存放数据,结点内部包含数据和指向前后结点的指针;结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间。

7. STL容器迭代器非法化

  • 只读方法决不非法化迭代器或引用;
  • 修改容器内容的方法可能非法化迭代器和/或引用,如下:
类别容器插入后……擦除后……条件
迭代器合法?引用合法?迭代器合法?引用合法?
顺序容器arrayN/AN/A 
vectorN/A插入更改容量
被修改元素前
于被修改元素或其后
deque是,除了被擦除元素修改首或尾元素
仅修改中部
list是,除了被擦除元素 
forward_list是,除了被擦除元素 
关联容器set

multiset

map

multimap

是,除了被擦除元素 
无序关联容器unordered_set

unordered_multiset

unordered_map

unordered_multimap

N/A插入导致重哈希
是,除了被擦除元素无重哈希

此处插入指代任何添加一或多个元素到容器的方法,而擦除指代任何从容器移除一或多个元素的方法。

  • 插入方法是: std::set::insert 、 std::map::emplace 、 std::vector::push_back 和 std::deque::push_front ;另外 std::unordered_map::operator[] 也是,因为它可能插入元素到 map 中。
  • 擦除方法是: std::set::erase 、 std::vector::pop_back 、 std::deque::pop_front 和 std::map::clear ;clear 非法化所有迭代器和引用,因为它擦除所有元素。

8. STL容器的线程安全

  • 能同时在不同容器上由不同线程调用所有容器函数。更广泛而言, C++ 标准库函数不读取能通过其他线程访问的对象,除非这些对象能直接或间接地经由函数参数,包含 this 指针访问。
  • 能同时在同一容器上由不同线程调用 const 成员函数。而且,成员函数 begin() 、 end()rbegin() 、 rend() 、 front() 、 back() 、 data() 、 find() 、 lower_bound() 、 upper_bound() 、 equal_range() 、 at() 和除了关联容器中的 operator[] 对于线程安全的目标表现如同 const (即它们亦能同时在同一容器上由不同线程调用)。更广泛而言, C++ 标准库函数不修改对象,除非这些对象能直接或间接地经由函数参数,包含 this 指针访问。
  • 同一容器中不同元素能由不同线程同时修改,除了 std::vector<bool> 的元素(例如, std::future 对象的 vector 能从多个线程接收值)。
  • 迭代器操作(例如自增迭代器)读但不修改底层容器,而且能与同一容器上的其他迭代器操作同时由 const 成员函数执行。非法化任何迭代器的容器操作修改容器,且不能与任何在既存迭代器上的操作同时执行,即使这些迭代器未被非法化。
  • 同一容器上的元素可以同时由不指定为访问这些元素的函数修改。更广泛而言, C++ 标准库函数不间接读取能从其参数访问的对象(包含容器的其他对象),除非其规定要求如此。
  • 任何情况下,容器操作(还有算法,或其他 C++ 标准库函数)可于内部并行化,只要不更改用户可见的结果(例如 std::transform 可并行化,但指定了按顺序观览序列的每个元素的 std::for_each 不行)

大体上讲STL的线程安全的情况如下:

  • 多个线程可以同时读取一个容器中的内容; 即此时多个线程调用 容器的不涉及到写的接口都可以 eg find, begin, end 等.
  • 对不同容器的多个写入者是安全的,即多个线程对不同容器的同时写入合法。 但是对于同一容器当有线程写,有线程读时,如何保证正确? 需要程序员自己来控

9. STL中vector和list的区别和联系

联系:
      均为序列式容器,其中的元素不一定有序,但都可以被排序。

区别:

a. 使用的选择:

vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。

list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁。

b. 时间复杂度      

vector: 随机读取O(1)
              中间位置插入和删除操作时间复杂度为O(N);
              尾部push_back, pop_back操作时间复杂度为O(1)
       list: 随机读取O(n)  插入O(1)    删除O(1)

10. STL中deque的实现原理

deque系由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口。避开了“重新配置、复制、释放”的轮回,代价则是复杂的迭代器架构。

deque采用一块所谓的map(注意,不是STL的map容器,map的默认大小为8,如果分配大小大于8,会在前后个预留一个结点)作为主控。这里所谓map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。SGI STL 允许我们指定缓冲区大小,默认值0表示将使用512 bytes 缓冲区。

11. STL中vector与deque的区别

  • vector是单向开口的连续线性空间,deque是双向开口的连续线性空间;双向开口是指可以在头尾两端分别做元素的插入和删除操作,vector当然可以在头尾两端进行操作,但是其在头部的操作效率奇差,所以把vector当作单向开口的连续空间。
  • deque没有提供空间保留功能,而vector则要提供空间保留功能。
  • deque也提供随机访问迭代器,但是其迭代器比vector迭代器复杂很多。
  • deque没有容量的观念,它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并连接起来,并不会出现vector中的“因旧空间不足而重新配置一块更大的空间,然后复制元素,再释放旧空间”

12. 哪些容器不能遍历,其各自的特点

  • queue,除了头部外,没有其他方法存取deque的其他元素。
  • stack(底层以deque实现),除了最顶端外,没有任何其他方法可以存取stack的其他元素。
  • heap,所有元素都必须遵循特别的排序规则,不提供遍历功能。

stack是一种先进后出的数据结构,它只有一个出口;stack允许新增元素、移除元素、取得最顶端的元素;但是除了顶端外不可以存取其他元素;stack没有迭代器;stack在缺省的情况下是以deque作为底部容器来完成所有的工作,元素的操作只有push 和 pop 两个接口;stack也可以使用vector 、 list  作为底部容器。

queue是一种先进先出的数据结构,他有两个接口;queue允许新增元素、移除元素、从最低端加入元素、从最顶端取得元素;queue也没有迭代器;queue缺省的情况下以deque作为底部容器实现;queue可以使用 deque 、 list 作为底部实现容器。注意的是queue的底层实现不可以用vector,因为queue要在头部进行元素操作,其效率奇差。

13. vector中erase方法与algorithn中的remove方法区别

vector中erase方法真正删除了元素,迭代器不能访问了

remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

14. 红黑树有什么性质

  • 每个结点是红色或者黑色。
  • 根结点为黑色。
  • 叶结点为黑色的NULL结点。
  • 如果结点为红,其子节点必须为黑。
  • 任一结点到NULL的任何路径,所含黑结点数必须相同。

15. set、map的区别与联系

联系:

  • Map,Set属于标准关联容器,底层数据结构使用红黑树;
  • 时间复杂度均为红黑树的时间复杂度,插入删除查找近似为O(logN)

区别:

  • map适合存储一个数据字典,并要求方便地根据key找value;set适合查找一个元素是否在某集合内存中
  • Set节点只含有Key,Key不重复;Map节点有一个Key和Value两个元素,Key不重复,Value可以重复也可不重复
  • set不能直接改变元素值。因为这样会打乱原有的顺序,影响红黑树的结构。改变元素值的方法是:先删除旧元素,再插入新元素。存取元素只能通过迭代器,从迭代器的角度看,元素值是常数。map可以通过key改变value的值

16. set、multiset 容器的insert的区别

set的insert:
      pair<iterator,bool> insert(const value_type& elem);
      iterator insert(iterator pos_hint, const value_type& elem);

multiset的insert:
      iterator insert(const value_type& elem);
      iterator insert(iterator pos_hint, const value_type& elem);

      set的第一个insert返回值型别不同的原因是set不允许元素重复,当插入的元素在set中已经包含有同样值的元素时,插入就会失败。所以set的返回值型别是由pair组织起来的两个值:
      第一个元素返回新元素的位置,或返回现存的同值元素的位置;
      第二个元素表示插入是否成功。

      set的第二个insert函数,如果插入失败,就只返回重复元素的位置!

      但是,所有拥有位置提示参数的插入函数的返回值型别是相同的。这样就确保了至少有了一个通用型的插入函数,在各种容器中有共通接口。

17. 当数据元素增多时(10000和20000个比较),map和set的插入和搜索速度变化如何

map和set的底层实现是RB-TREE,而红黑树的查找是使用二分查找法,时间复杂度为logn,所以从10000增到20000时,查找次数从log10000=14次到log20000=15次,多了1次而已。

18. map和set每次Insert之后,iterator是否失效

不会失效,因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。

19. 为何map和set不能像vector一样有个reserve函数来预分配数据

map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map声明的时候从参数中传入的Alloc;如下map源码中定义的红黑树_Rb_tree,它的分配器是一个_Pair_alloc_type,并不是初始化map时传进来的allocator了。

      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
    rebind<value_type>::other _Pair_alloc_type;

      typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
               key_compare, _Pair_alloc_type> _Rep_type;

20 .hashtable,hash_set,hash_map的区别

  • hash_set以hashtable为底层,不具有排序功能,能快速查找。其键值就是实值。(set以RB-TREE为底层,具有排序功能。)
  • hash_map以hashtable为底层,没有自动排序功能,能快速查找,每一个元素同时拥有一个实值和键值。(map以RB-TREE为底层,具有排序功能。)

21. unordered_map、hash_map和map的区别

unordered_map和hash_map底层是散列,所以理论上操作的平均复杂度是常数时间,map底层是红黑树,理论上平均复杂度是O(logn):

  • hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
  • hash_map采用hash表存储,map采用红黑树实现。因此内部使用的数据结构是不一样的。

hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,如果考虑效率,特别是在元素达到一定数量级时,可以考虑hash_map。但对内存使用特别严格,希望程序尽可能少消耗内存,需要考虑hash_map对内存的消耗,另外 hash_map的构造速度较慢,因为要处理hash冲突的问题。

22. auto_ptr是否可以作为vector的元素

不能。因为STL的标准容器规定它所容纳的元素必须是可以拷贝构造和拷贝赋值的。而auto_ptr不能,可以用shared_ptr智能指针代替。

23. stl对于小内存块请求与释放的处理

STL考虑到小型内存区块的碎片问题,设计了双层级配置器,第一级配置直接使用malloc()和free();第二级配置器则视情况采用不同的策略,当配置区大于128bytes时,直接调用第一级配置器;当配置区块小于128bytes时,便不借助第一级配置器,而使用一个memory pool来实现。究竟是使用第一级配置器还是第二级配置器,由一个宏定义来控制。

SGI STL中默认使用第二级配置器;二级配置器会将任何小额区块的内存需求量上调至8的倍数,(例如需求是30bytes,则自动调整为32bytes),并且在它内部会维护16个free-list, 各自管理大小分别为8, 16, 24,…,128bytes的小额区块,这样当有小额内存配置需求时,直接从对应的free list中拔出对应大小的内存(8的倍数);当客户端归还内存时,将根据归还内存块的大小,将需要归还的内存插入到对应free list的最顶端。

当需求的内存在free-list中找不到时,必须向内存池申请支持20个结点,1. 如果内存池足够申请成功,2. 如果内存池空间不足,就分配最大数目的结点数,3. 如果内存池不足分配一个结点,那么将零头分配到相应的free-list中 4.内存池无空间时,配置heap空间,来补充内存池 5。如果heap空间也不足,那么就搜索适当的free-list结点使用 6. 实现没空间抛出异常。

 目的:

  • 小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。
  • 避免内存碎片的产生;零乱的内存碎片不仅会浪费内存空间,而且会给OS的内存管理造成压力。
  • 尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,

这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的。

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++基础——STL常见问题总结 的相关文章

  • NDK--CMakeLists配置第三方so库

    当我们创建一个NDK工程时 xff0c 会自动创建一个CMakeLists txt的文件 xff0c 在AS中c 43 43 的编译器是使用LLVM xff0c 规则为cmake xff0c 今天来学习下cmake的基本套路 首先 xff0
  • postman插件下载安装教程(详细)

    一 前言 postman是一款强大网页接口调试工具 xff0c 我们在平时开发过程中经常会使用到 xff0c 一般使用最多的是postman的客户端 xff0c 实际上postman在谷歌浏览器上也提供了插件 xff0c 可以不必要安装客户
  • CMake交叉编译简单教程

    首先要安装cmaek 然后安装交叉编译链 一 CMake简介 xff1a CMake是一个跨平台的安装 编译 工具 可以通过简单的语句来描述所有平台的安装 编译过程 他能够输出各种各样的 makefile 或者 project 文件 二 C
  • 锂电池的常见接口

    我们在做一些小型化便携式设备的时候 xff0c 经常会用到锂电池 xff0c 常见的锂电池接口如图 xff1a
  • Ubuntu14.04_ROS学习笔记(7) odroid板上操作系统和电脑端主从连接

    4 29日 xff0c 距离上次写过于odroid ROS的博客已经过去近4周 xff0c 在这四周发生了很多曲折事 xff0c 研究生的调剂和面试问题 xff0c 导师双向选择也出现了问题 xff0c 调档问题 xff0c 然后和GF出去
  • ROS学习----Publisher与Subscriber

    1 Publisher 发布者 与subscriber 订阅者 关系 Publisher的主要作用是对于指定话题发布特定数据类型的消息 下面是利用代码实现一个节点 xff0c 节点创建一个Publisher并发布字符串 Hello worl
  • liunx 下如何查看make与cmake版本

    cmake cmake version 即可查看cmake的版本 make 如果是在 shell 中查看 xff0c 那么直接 make v 即可 如果是在 makefile 中获取 xff0c 则用 MAKE VERSION xff0c
  • CAN协议解析

    CAN协议解析 CAN 总线组网连线图CAN的报文格式报文格式扩展CAN错误检测 波形解析ID数据长度数据字段CRC CAN 总线组网连线图 根据CAN总线的硬件特性 xff0c 当一条CAN总线上挂接多个驱动器的时候 xff0c 应当按照
  • 字节序的大端和小端

    字节序 字节序 xff08 Byte Order xff09 是指在多字节的数据类型 xff08 如整型 浮点型等 xff09 在内存中存储时 xff0c 字节的排列顺序 大端字节序 xff08 Big Endian xff09 xff1a
  • STM32 HAL 串口收发(无DMA,中断接收)

    STM32CUBE配置 一 使用printf发送数据 xff0c 在usart c中添加代码串口重定向 USER CODE BEGIN 0 include lt stdio h gt ifdef GNUC define PUTCHAR PR
  • 2019年电赛综合测评题详解

    2019年全国大学生电子设计竞赛综合测评已经结束 xff0c 邀请到西电研究生李天红同学给大家做重点分析 首先看题目 xff1a 视频要点提示 xff1a 题目分析 常用波形变换电路 两种可行方案 方案仿真 实际过程中遇到的问题分析 完整视
  • 《Spring源码深度解析 郝佳 第2版》SpringBoot体系分析、Starter的原理

    往期博客 Spring源码深度解析 郝佳 第2版 容器的基本实现与XML文件的加载 Spring源码深度解析 郝佳 第2版 XML标签的解析 Spring源码深度解析 郝佳 第2版 bean的加载 循环依赖的解决 Spring源码深度解析
  • SpringCloud Alibaba Nacos实践与原理分析

    目录 一 Nacos概述 Nacos是什么 xff1f Nacos中的相关概念 二 微服务的注册与发现 Nacos client提供注册接口的原理 服务注册的原理分析服务注册的时机分析NacosServiceRegistryAutoConf
  • Linux课堂篇3_Linux目录结构、快捷键、常用基础命令

    目录 此系列博客为大三下期末小学期课程大数据疫情分析平台项目学习学习笔记 xff0c 内容参考中共教育讲义文件 Linux目录结构Linux快捷键Linux命令 命令分类快捷键基本命令常用命令用户管理命令文件权限命令磁盘大小查看命令搜索查找
  • 《深入理解RPC框架原理与实现 华钟明》读书笔记

    前言 这本书更像是全面系统的讲解RPC xff0c 内容可以连贯起来 xff0c 从计算机处理器发展到RPC的诞生 xff0c 后面讲几种常见的RPC组件 通信协议 序列化协议 xff0c 虽然内容不是很深入 xff0c 但是对于小白较易理
  • Nebula Graph学习篇1_基础概念、初步使用、整合SpringBoot使用

    目录 一 基础概念 图数据库的概念适用场景数据模型路径点的VID架构 二 初步使用 Windows安装Nebula Graph服务Nebula Console 连接 Nebula Graph常用命令 3 1异步实现创建修改类型 xff08
  • 报错8_引入Sl4j-log4j12报错 Class path contains multiple SLF4J bindings.

    报错 SpringBoot版本2 6 7 xff0c pom文件 span class token prolog lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt span spa
  • Kubernetes学习篇1_集群环境搭建测试

    目录 一 集群环境准备 集群环境安装要求虚拟机服务器网络IP配置主机名解析其他配置 二 集群初始化 需要提前准备集群所需组件镜像kubernetes初始化集群 需要在master上面执行的操作需要在node1 node2执行的命令需要在ma
  • Kubernetes学习篇2_资源管理、操作资源命令练习(Nginx集群外部访问)

    目录 一 资源管理介绍 资源管理介绍资源管理方式 命令式对象管理 xff08 只用命令 xff09 命令式对象配置 xff08 命令 43 yaml文件 xff09 声明式对象配置 xff08 apply命令 43 yaml文件 xff09
  • 大数据疫情可视化平台1_基于Hadoop3.2.1、Hive3.1.2、搭建疫情信息可视化系统

    前言 项目效果展示 项目源码免费获得请私信博主 xff0c 绝对免费 xff01 目录 Linux基础命令 xff1a 往期博客Linux课堂篇3 Linux目录结构 快捷键 常用基础命令Hadoop3 2 1介绍与环境搭建Hive3 1

随机推荐