面试题7

242 阅读11分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.请说说STL的基本组成部分

STL主要由6部分组成:容器、算法、迭代器、仿函数、适配器和空间配制器

(1)容器

是一种数据结构,如list,vector和deque,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器。

(2)算法

是用来操作容器中数据的模板函数的。例如。STL用sort()来堆一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数本身与它们操作数据的结构和容器无关,因此它们可以用于简单数组到高度复杂容器的任何数据结构上。

(3)迭代器

提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是迭代器也可以是那些定义了opreator*()以及其他类似于指针的操作符方法的类对象。

(4)仿函数

仿函数又被称为函数对象,就是重载了操作符的struct,没有什么特别的地方

(5)适配器

简单来说是一种接口类,专门用来修改现有类的接口,提供一种新的接口;或者调用现有的函数来实现所需要的功能。主要包括三种适配器:容器适配器、迭代器适配器和函数适配器。

(6)空间配制器

为STL提供空间配置的系统。其中主要工作包括两部分: 一、对象的创建与销毁 二、内存的获取与释放

2.请说说STL中常见的容器,并介绍一下实现原理

容器可以用来存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是模板类,分为顺序容器、关联式容器、容器适配器三种类型三种类型容器的特性分别如下:

一、顺序容器

容器并非有序的,元素的插入位置与元素的值无关。包含vector、deque、list,具体的实现原理如下:

(1)vector:头文件< vector >

动态数组。元素在内存里连续存放。随机存取任何元素都能在常数时间内完成。在尾端增删元素具有较好的性能。

(2)deque:头文件 < deque >

双向队列。元素在内存中连续存放。随机存取任何元素都能在常数时间内完成(仅次于vector)。在两端增删元素具有较好的性能(大部分情况下是常数时间)。

(3)list:头文件 < list >

双向链表。元素在内存中不连续存放。在任何位置增删元素都能在常数时间内完成。不支持随机存取。

二、关联式容器

元素是排好序的;插入任何元素都会按照相应的排序规则来确定其位置;在查找时有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap,具体实现的原理如下:

(1)set/multimap:头文件 < set >

set即集合。set中不允许相同元素,multiset中允许相同元素。

(2)map/multimap:头文件< map >

map与set的不同在于map中存放的元素有且仅有两个成员变量,一个名为first, 另一个名为second,map根据first值对元素从小到大排序,并可快速地根据first来检索元素 **注意:**map允许相同first值的元素,而multimap不允许

三、容器适配器

封装了一些基本的容器,使之具备了新的函数功能,比如把deque封装一下变为一个具有stack功能的数据结构。这新得到的数据结构就叫适配器。包含stack,queue,priority_queue,具体实现原理如下:

(1)stack 头文件< stack >

栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最进插入序列的项(栈顶的项)。后进先出。

(2)queue 头文件< queue >

队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出。

(3)priority_queue 头文件< queue >

优先级队列。内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列

3.说说 STL 中 map、hashtable、deque、list 的实现原理

map、hashtable、deque、list的实现原理分别是红黑树、函数映射、双向队列、双向链表,它们的特性如下:

(1)map实现原理

map内部实现了一个红黑树(红黑树并非严格平衡的二叉搜索树,而AVL是严格平衡的二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个节点都代表着map的一格元素。因此,对于map中进行的查找、删除、添加等一系列元素都是按照二叉树来存储的,特点就是左子树上的所有节点的键值都小于根节点的键值,而右子树所有结点的键值都大于根节点的键值,使用中序遍历便可将键值按照从小到大的顺序遍历出来。

(2)hashtable(哈希表/散列表)实现原理

hashtable采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能很快的进行查找。这决定了哈希表特殊的数据结构,它可以快速定位到自己想要查找的记录,而不是通过表中存在的记录的关键字来进行比较查找。

(3)deque实现原理

deque的内部实现是一个双向队列。元素在内存中连续存放。随机存取任何元素都能在常数时间内完成(仅次于vector)。所有适用于vector的操作都适用于deque,在两端增删元素性能比较好。

(4)list实现原理

list内部实现的是一个双向链表。元素在内存中不连续存放。在任何位置增删元素都能在常数时间内完成。不支持随机存取,没有成员函数,给定一个下标i,要想访问第i个元素,只能从头部遍历到第i个元素。

4.请你来介绍一下 STL 的空间配置器(allocator)

一般容器都需要一定空间存放数据,allocator就是用来配置空间的,SGI的allocator配置的对象时内存。 一个allocator通常包含两个部分,一是内存配置和内存释放(allocate的deallocate),二是对象构造和析构(construct和destory)。

内存配置操作:alloc::allocate()实现 内存释放操作:alloc::deallocate()实现 对象构造操作: ::construct()实现 对象释放操作: ::destroy()实现

STL构造于析构结构图如下所示: 在这里插入图片描述

5.STL的容器用过哪些,查找的时间复杂度都是多少,为什么?

(1)vector

采用一维数组实现,元素在内存连续存放,不同操作的时间复杂度为: 插入:O(N) 查看:O(1) 删除:O(N)

(2)deque

采用双向队列实现,元素在内存中连续存放,不同操作的时间复杂度为: 插入:O(N) 查看:O(1) 删除:O(N)

(3)list

采用双向链表实现,元素存放在堆中,不同操作的时间复杂度为: 插入:O(1) 查看:O(N) 删除:O(1)

(4)set、multiset、map、multimap

上述四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为: 插入:O(logN) 查看:O(logN) 删除:O(logN)

(5)unordered_map、unordered_set、unordered_multimap、 unordered_multiset

上述四种容器采用哈希表实现,不同擦欧总的时间复杂度为: 插入:O(1),最坏情况为O(N) 查看:O(1),最坏情况为O(N) 删除:O(1),最坏情况为O(N)

注意:容器的时间复杂度取决于其底层实现方式。

6.迭代器用过吗,什么时候会失效?

用过,常用容器迭代器的失效情形如下: (1)对于序列容器vector、deque来说,使用erase后,后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。

(2)对于关联容器map、set来说,使用了erase后,当前元素的迭代器失效,但是因为它们的结构是红黑树,删除当前元素并不会影响下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

(3)对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用。

7.说说STL容器动态链接可能产生的问题

(1)可能产生的问题

容器是一种动态分配内存空间的一个变量集合类型变量。在一般的程序函数里,局部容器,参数传递容器的引用,参数传递容器指针都是可以正常运行的,而在动态链接库函数内部使用容器也是没有问题的,但是给动态库函数传递容器的对象本身,则会出现内存堆栈破坏的问题。

(2)产生问题的原因

容器和动态链接库相互支持不够好,动态链接库函数中使用容器时,参数中只能传递容器的引用,并且要保证容器的大小不能超出初始大小,否则导致容器自动重新分配,就会出现内存堆栈破坏问题。

8.说说map 和 unordered_map的区别?底层实现

map 和 unordered_map的区别在于他们的实现机理不同。

(1)map实现机理

map内部实现了一个红黑树(红黑树是非严格平衡的二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此对于map进行的增删改查操作都相当于对红黑树进行的操作。map中的元素是按照二叉树存储的,特征就是u总字数上所有节点的键值都小于根节点的键值。使用中序遍历可以将键值从小到大遍历出来。

(2)unordered_map实现机理

unordered_map的内部实现了一个哈希表,通过把关键码值映射到hash表中的一个位置来访问记录,查找时间复杂度为O(1),在海量数据处理中有着广泛的应用,元素的排列顺序是无序的。

9.说说vector 和 list 的区别,分别适用于什么场景?

(1)vector:一维数组

特点:元素在内存中连续存放,动态数组,在堆中分配内存,元素连续存放,有保留内存,如果减小大小后内存也不会释放。

优点:和数组类似开辟一段连续的空间,并且支持随机访问,所以它的查找效率高,时间复杂度为:O(1)。

缺点:由于开辟一段连续的空间,所以插入删除会需要对数据进行移动,比较麻烦,时间复杂度为:O(N),另外当空间不足时还需要进行扩容。

(2)list:双向链表

特点:元素在队中存放,每个元素都存放在一块内存中,它的内存空间是可以不连续的,通过指针来进行数据的访问。

优点:底层实现是双向循环链表,当对大量数据进行插入删除时,其时间复杂度为:O(1)。

缺点底层没有连续的空间,只能通过指针来访问,所以查找数据遍历时,其时间复杂度为:o(N),没有提供[]操作符的重载。

(3)应用场景

vector拥有一段连续的内存空间,因此支持随机访问,如果需要搞笑的随机访问,而不在乎插入和删除的效率,使用vector。

list拥有一段不连续的内存空间,如果需要搞笑的插入和删除,而不关心随机访问,则可以使用list。

10.简述STL中map的实现原理

map时关联式容器,它们的底层容器都是红黑树。map的所有元素都是pair,同时拥有实值(value)和键值(key)。pair的第一元素会被看成键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。不允许键值重复。

map的特性如下:

(1)map以RBTree作为底层容器 (2)所有元素都是以键 + 值的形式存在的 (3)不允许键重复 (4)所有的元素都是通过键来进行自动排序的 (5)map的键是不能修改的,但是其键对应的值是可以修改的。