c++八股学习day5

本文详细介绍了STL的六大组件:容器、算法、迭代器、仿函数、适配器和空间配置器,以及它们之间的交互关系。重点探讨了vector、list、deque等容器的特点、应用场景和与算法的配合,以及迭代器在不同容器中的使用。
摘要由CSDN通过智能技术生成

STL(Standard Template Library,标准模板库)

STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。

算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.

迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。

仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template

适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。

空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.

STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
 

1. 容器


几乎可以说,任何特定的数据结构都是为了实现某种特定的算法。STL容器就是将运用最广泛的一些数据结构实现出来。
常用的数据结构:数组(array) , 链表(list), tree(树),栈(stack), 队列(queue), 集合(set),映射表(map), 根据数据在容器中的排列特性,这些数据分为序列式容器和关联式容器两种。

序列式容器强调值的排序,序列式容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。Vector容器、Deque容器、List容器等。
关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。关联式容器另一个显著特点是:在值中选择一个值作为关键字key,这个关键字对值起到索引的作用,方便查找。Set/multiset容器 Map/multimap容器

2. 算法


算法,问题的解法,以有限的步骤,解决逻辑或数学上的问题。

我们所编写的每个程序都是一个算法,其中的每个函数也都是一个算法,毕竟它们都是用来解决或大或小的逻辑问题或数学问题。STL收录的算法经过了数学上的效能分析与证明,是极具复用价值的,包括常用的排序,查找等等。特定的算法往往搭配特定的数据结构,算法与数据结构相辅相成。

算法分为:质变算法和非质变算法。

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

3. 迭代器


迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。 在<<Design Patterns>>一书中提供了23种设计模式的完整描述, 其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。

迭代器的设计思维-STL的关键所在,STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。

从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。
 

vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。

deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。

vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置 却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。

list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。

set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。

map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。
 

Vector 的数据模型就是 数组
优点:内存和 C 完全兼容、高效随机访问、节省空间
缺点:内部插入删除元素代价巨大、动态大小查过自身容量需要申请大
量内存做大量( 2 倍)拷贝。
List 的数据结构模型是 链表
优点:任意位置插入删除元素常量时间复杂度、两个容器融合是常量时间复杂度
缺点:不支持随机访问、比 vector 占用更多的存储空间
Deque 的数据模型是数组和链表的折中( stack queue 的基础)
优点:高效随机访问、内部插入删除元素效率方便、两端 push pop
缺点:内存占用比较高
Map set multimap multiset 的数据结构模型是 二叉树 ( 红黑树 )
优点:元素会按照键值排序、查找是对数时间复杂度、通过键值查元素、map 提供了下标访问
vector resize 和 和 reserve 操作的 区别
Resize :改变容器的大小,并且创建对象,因此,调用这个函数之后, 就可以引用容器内的对象了,因此当加入新的元素时,用 operator[ ] 操作符(数组下标的操作符),或者用迭代器来引用元素对象。简单的说 resize 是带初始化的!这个是关系道 .size()
Reserve :容器预留空间,但并不真正创建元素对象,在创建对象之 前,不能引用容器内的元素,因此当加入新的元素时,需要用 push_back()/insert() 函数。 总的来说是扩充了 capacity () **
  参数不同: resize(n)/resize(n t) reserve(n)

STLvector的实现

STL 中的 vector 是封装了 动态数组的顺序容器 。不过与动态数组不同的是, vector可以根据 需要自动扩大容器的大小 。具体策略是每次容量不够用时重新申请一块大小为原来容量两倍 的内存(主要还是要看编译器),将原容器 的元素拷贝至新容器,并释放原空间,返回新空间的指针。在原来空间不够存储新值时,每次 调用push_back方法都会重新分配新的空间以满足新数据的添加操作 。如果在程序中频繁进行这种操作,还是比较消耗性能的。

C++vectorpush_backemplace_back的区别

push_back ()
首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样 造成的问题就是临时变量申请资源的浪费 引入了右值引用,转移构造函数后, push_back() 右值时就会调用构造函数 和转移构造函数 , 如果可以在插入的时候直接构造,就只需要 构造一次 可。
emplace_back()
容器尾部添加一个元素 ,这个元素原地构造,不需要触发拷贝构造和转移构造。而且调用形式更加简洁,直接根据参数初始化临时对象的成员。

STL的容器线程不安全怎么解决

1. 加锁是一种解决方案 ,但是加 std::mutex互斥锁 确实性能较差。对于多读 少写的场景可以用读写锁(也叫共享独占锁),来缓解。
2. 更多的时候,其实可以通过固定 vector 的大小,避免动态扩容(无 push_back )来做到 lock-free

关联容器的线程安全问题

vector 是顺序容器, STL 中还有一类关联容器其线程安全问题也不容小觑。 比如map unordered_map
当有多个写线程对情况下 ,并发地插入 map/unordered_map 都会引发 core dump。对此, 在某些场景下 也可以避免加锁: 如果全量的 key 有办法 在并发之前就能拿到的,那么就对这个 map ,提前做一下 insert 。并发环 境中如果只是修改value ,而不是插入新 key 就不会 core dump !不过如果
你没办法保证多个写线程不会同时修改同一个 key value ,那么可能存在value的覆盖。无法保证这点时,还是需要加锁。不过可以对 key 采取某种hash策略转成整型,然后进行分段加锁,减少一点锁冲突的概率,或者用一下CAS 的策略。

哪些容器都使用sort算法

关系型容器拥有自动排序功能,因为底层采用 RB-Tree ,所以不需 要用到sort 算法。其次,序列式容器中的 stack queue priority-queue 都 有特定的出入口,不允许用户对元素排序。 剩下的 vector deque ,适用 sort 算法。

vector/list/deque 的区别与联系

联系:均为序列式容器,其中的元素不一定有序,但都可以被排序。
区别:
① 使用场景
vector:高效的随即存取,不在乎插入和删除的效率,支持下标操作
list:大量的插入和删除,不关心随即存取,不支持下标操作的
deque:随即存取,而且关心两端数据的插入和删除,但是内存开销 比较大,支持下标操作的
② 存储方式
vector:连续的内存空间进行存储(扩容是要分配新空间)
list:非连续的内存空间进行存储
deque: 表面上的连续性存储空间,在堆上分配了一块一块的动态储存 区(不连续),每一块动态存储去本身是连续的,由_Map(中央控制器)存储区虚拟地连在一起。
③时间复杂度
vector: 随机读取 O(1),中间位置插入和删除操作时间复杂度为 O(N); 尾部 push_back, pop_back 操作时间复杂度为 O(1)
list: 随机读取 O(n) 插入 O(1) 删除 O(1)
dequepush_back, push_front, pop_back, pop_front 操作时间复杂度为 O(1)
1. vector 和数组类似,拥有一段连续的内存空间,并且起始地址不变,这样对随机的读取很有效率(就是我们所有的[]运算符了)。
2. list 的对象是离散存储的(就是内存不是连续的),想要随机访问某个 元素就需要遍历 list,但是在插入元素的效率很高(只需要改变元素的指针)。
时间复杂度
Map 为红黑树的时间复杂度 插入删除查找近似为 O(logN)
Hash_map 为哈希表的时间复杂度
插入:O(1),最坏情况 O(N)
查看:O(1),最坏情况 O(N)
删除:O(1),最坏情况 O(N)
map
优点:有序性,这是 map 结构最大的优点,其元素的有序性在很多应用中 都会简化很多的操作
红黑树,内部实现一个红黑书使得 map 的很多操作在 lgn 的时间复杂度下就 可以实现,因此效率非常的高
缺点: 空间占用率高,因为 map 内部实现了红黑树,虽然提高了运行效 率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/ 黑性质, 使得每一个节点都占用大量的空间适用处:对于那些有顺序要求的问题,用 map 会更高效一些
STL 的容器在哪些情况下迭代器会失效
vector
1 、当插入 (push_back) 一个元素后, end 操作返回的迭代器肯定失效。
capacity 返回值没有改变,则 first 返回的迭代器依旧有效;否则, first
迭代器也失效
2 、当进行删除操作( erase pop_back )后,指向删除点和指向删除点
后面的元素的迭代器也将全部失效 ( 向前移动了 )
list
删除操作( erase )也只有指向被删除元素的那个迭代器失效,其他迭代 器不受影响。( list 目前只发现这一种失效的情况)
deque
1. 在容器首部或者尾部插入元素不会使得任何迭代器失效。但在任何其他
位置的插入和删除操作将使指向该容器元素的所有迭代器失效
2. 在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。
Hash (哈希) 函数的构造法方法:
1. 直接定制法
哈希函数为关键字的线性函数如 H key =a*key+b 这种构造方法比较简便,均匀,但是有很大限制,仅限于地址大小= 关键字集合的情况
2. 除留余数法用的较多
H key =key % p p<=m m 为表长)
3. 数字分析法
假设关键字集合中的每个关键字 key 都是由 s 位数字组成( k1,k2...kn),, 分析 key 中的全体数据,并从中提取分布均匀的若干位或他们的组合构 成全体
4. 平方取中法
如果关键字的每一位都有某些数字重复出现频率很高的现象,可以先求 关键字的平方值,通过平方扩大差异,而后取中间数位作为最终存储地址。
5. 折叠法
如果数字的位数很多,可以将数字分割为几个部分,取他们的叠加和作为 hash 地址

STL源码中的hash表的实现

STL 中的 hash 表就 unordered_map 。使用的是哈希进行实现(注意与 map 的区别)。它记录的键是元素的哈希值,通过对比元素的哈希值来确定元素的值。
unordered_map 的底层实现是 hashtable ,采用开链法(也就是用桶)来 解决哈希冲突,当桶的大小超过8 时,就自动转为红黑树进行组织
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值