STL笔试


STL的六大部件和联系?

STL的6大部件包括:容器(Containers)、分配器(Allocators)、算法(Algorithm)、迭代器(Iterators)、适配器(Adapters)、仿函数(Functors)。

容器用于存储数据,是与内存打交道的,其背后有分配器支撑其分配内存,基于容器写好的仿函数封装成算法,迭代器是算法对容器中数据访问的桥梁,是一种泛化的指针。从实现角度看,迭代器是一种将operator*,operator->,operator++,operator--等指针相关操作予以重载的class template。适配器,英文中adapter就是变压器的意思,用于帮助部件进行转换,包括容器适配器,仿函数适配器,迭代器适配器。

 

STL容器知道哪些,都解释一下,原理是什么?

序列式容器,其中每个元素均有固定位置——取决于插入时机和地点,和元素值无关。(vector、deque、list)

向量vector:在序列尾部进行插入和删除,相似的 array 是静态空间,一旦配置了就不能改变,但 vector 是动态数组,空间不足时以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对 vector 的任何操作,一旦引起空间重新配置,同时指向原vector 的所有迭代器就都失效了。
vector 的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块的 array。

优点:支持快速随机访问
缺点:对中间和开始处进行添加删除元素操作需要移动大量元素。
∴ vector 常用来保存需要经常进行随机访问的内容,并且不需要经常对中间元素进行添加删除操作。


双向队列deque:在序列头尾部都可进行插入和删除,对外声称连续空间,实际是分段连续,因为分配中央控制器map(并非map容器),map是一个连续的空间, 其每个元素都是一个指向缓冲区的指针,真正的数据在缓冲区中存放着,deque先从map中央的位置(因为双向队列,前后都可以插入元素)找到一个缓冲区地址,向该缓冲区中放入数据,空间不够时继续在map中找空闲的缓冲区来存数据。当map也不够时按照vector规则重新分配内存当作新的map,把原来map中的内容copy到新map中,并释放原空间。

优点:支持快速随机访问
缺点:需要复杂的迭代器,重载运算符

他有两个容器适配器,分别是队列queue(先进先出),堆栈stack(先进后出)

 

双向链表list:迭代器必须具备前移、后退的能力,节点形式来存放数据,非连续的内存空间来存放数据

优点:快速增删,不用频繁的拷贝转移
缺点:不支持随机访问和存取,不支持下标

 

关联式容器,元素位置取决于特定的排序准则以及元素值,和插入次序无关。(set、multiset、map、multimap)

set:元素的键值就是实值,实值就是键值。set 底层是通过红黑树(RB-tree)来实现的,由于红黑树是一种自平衡二叉搜索树,键值会自动被排序,如果set中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了set的结构,导致iterator失效,不知道应该指向之前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值。由于 set 所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 set 操作行为,都只有转调用 RB-tree 的操作行为而已。

multiset的特性以及用法和 set 完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制是 RB-tree 的 insert_equal() 而非 insert_unique()。


map 的所有元素都是 pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。同样是通过红黑树(RB-tree)来实现的,虽然不能改变key,但可以通过迭代器改变value。

其他与set一样,不赘述

 

容器hashtable:一段连续的空间vector,每个篮子(桶)是一个list,//buckets vector
符合Separate Chaining,编号对长度取余打散分开,碰撞就串在一起

为了避免list过长导致查询效率变慢,如果元素个数大于篮子就要rehashing

篮子个数要扩充为原来的倍数附近的素数

HashCode(编号)是通过 class HashFcn 得到的,有泛化和偏特化,
制造够乱的数字,尽量打散不要碰撞

元素最终要落到哪个篮子上,其实底层是 return hash(key) % n

 


常用算法及其原理

std,标准库,命名空间里

一定带两迭代器和容器沟通

1、accumulate 累计,四个参数(头、尾迭代器、一个初值,一个累计规则)

如果不设累计规则,默认把元素加至初值上

写一个一般的函数,就必须对binary_op(init,*first)可被调用

如果是函数对象,需要重载小括号


通常算法至少有两个版本,其中允许加上个自定义原则,以满足某种需求

 

2、for_each :在一段范围内,对每一个元素做一件你指定的事情

原理:first一直++,如果!=last,就执行 f(*first)

C++11,for( decl(变量声明) : coll(容器) ) { statement }

 

3、replace,replace_if,replace_copy

范围内所有等同于old_value的都以new_value取代

原理:first一直++,如果!=last且*first==old_value,
执行*first==new_value


如果是_if,就是把old_value参数替换成你所指定的比较规则(条件)

Predicate pred //判断式,返回真假


如果是_copy,把原先的元素copy到新区间,等于old_value的以new_value为值

原理:*result = *first == old_value ? new_value : *first;

 

4、count、count_if //遍历,
如果值和value相等 或者符合 pred(*first),计算器加1

关联式容器,可以用一个key找到data,他们拥有自己特化的算法(快速)

同样find、find_if也是如此

 

5、sort,原理:return(i<j)

链表是不允许随机访问,他们是有特化版本的,属于自身成员函数

 

6、binary_search //二分搜寻,前提排好序

原理:转调用std :: lower_bound(first,last,val)

              low        upper_bound
             |            |
10 10 10 20 20 20 30 30

posted @ 2020-11-15 10:43  赫拉克利特  阅读(127)  评论(0编辑  收藏  举报