C++八股文之STL篇

15 篇文章 0 订阅

 🤖个人主页晚风相伴-CSDN博客

思维导图链接STL

持续更新中……

💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧

🙏如果内容有误或者有写的不好的地方的话,还望指出,谢谢!!!

让我们共同进步

目录

1、STL的基本组成部分

 2、常见容器的实现原理

3、map的实现原理 

 4、push_back和emplate_back的区别

5、hashtable的实现原理 

6、deque的实现原理 

7、list的实现原理 

8、各容器的时间复杂度 

9、迭代器

9.1迭代器的作用 

9.2迭代器和指针的区别 

9.3迭代器产生的原因 

9.4迭代器失效的问题 

10、resize和reserve的区别 

10.1capacity和size的区别 

10.2resize和reserve的区别 

11、map和unordered_map的区别 

11.1红黑树的特性 

12、map和set的区别

 13、哈希表和红黑树的比较

14、数组和链表的区别 

15、智能指针

15.0RAII 

15.1auto_ptr

15.2unique_ptr

15.3shared_ptr

15.4weak_ptr


 

1、STL的基本组成部分

广义上讲,STL分为三类:算法、容器和迭代器
详细的说,STL由6部分组成:容器、算法、迭代器、仿函数、适配器、空间配置器

  1. 容器(Container):是一种数据结构,如list、vector和deque等,以模版类的方法提供。
  2. 算法(Algorithm):是用来操作容器中的数据的模板函数。
  3. 迭代器(iterator):提供了访问容器中对象的方法。
  4. 仿函数(Function object):仿函数又被称之为函数对象,其实就是重载了操作符的struct,没有什么特别之处
  5. 适配器(Adaptor):简单的说就是一个接口类,专门用来修改现有类的接口,提供一种新的接口,或调用现有的函数来实现所需要的功能。主要包括三种适配器Container Adaptor、Iterator Adaptor、Function Adaptor。
  6. 空间配置器(Allocator):为STL提供空间配置的系统。其中主要工作包括两部分:(1)对象的创建与销毁

         (2)内存的获取与释放

 2、常见容器的实现原理

1.顺序容器
(1)vector
动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。
(2)deque
双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在两端增删元素具有较佳的性能。
(3)list
双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取
2.关联式容器
元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap,具体实现原理如下:
(1)set/multiset 头文件
set 即集合。set中不允许相同元素,multiset中允许存在相同元素。
(2)map/multimap 头文件
map与set的不同在于map中存放的元素有且仅有两个成员变量,一个名为first,另一个名为second, map根据first值对元素从小到大排序,并可快速地根据first来检索元素。
注意:map同multimap的不同在于是否允许相同first值的元素。 
3.容器适配器
(1)stack
栈,后进先出
(2)queue
队列,先进先出
(3)priority_queue
优先级队列。内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列 

3、map的实现原理 

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

 4、push_back和emplate_back的区别

如果要将一个临时变量push到容器的末尾,push_back需要先构造临时对象,再将这个对象拷贝到容器的末尾,而emplace_back则直接在容器的末尾构造函数,这样就省去了拷贝的过程。 

5、hashtable的实现原理 

hashtable采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。这决定了哈希表特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。 

6、deque的实现原理 

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

7、list的实现原理 

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

8、各容器的时间复杂度 

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.map、set、multimap、multiset:这四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为:
插入: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) 

9、迭代器

9.1迭代器的作用 

  1. 用于指向顺序容器和关联容器中的元素
  2. 通过迭代器可以读取它指向的元素
  3. 通过非const迭代器还可以修改其指向的元素

9.2迭代器和指针的区别 

迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,重载了指针的一些操作符,-->、++、--等。
迭代器返回的是对象引用而不是对象的值 

9.3迭代器产生的原因 

iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。 

9.4迭代器失效的问题 

  1. 对于序列容器vector,deque来说,使用erase后,后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。
  2. 对于关联容器map,set来说,使用了erase后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
  3. 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用。

10、resize和reserve的区别 

10.1capacity和size的区别 

capacity:该值在容器初始化时赋值,指的是容器能够容纳的最大的元素的个数。还不能通过下标等访问,因此此时容器中还没有创建任何对象。

size:指的是此时容器中实际的元素个数。可以通过下标访问0-(size-1)范围内的对象。 

10.2resize和reserve的区别 

  1. resize既分配了空间,也创建了对象;reserve表示容器预留空间,但并不是真正的创建对象,需要通过insert或push_back等创建对象
  2. resize既修改capacity大小,也修改size大小;reserve只修改capacity大小,不修改size大小。
  3. 两者的形参个数不一样。resize带两个参数,一个表示容器大小,一个表示初始值;reserve只带一个参数,表示容器预留的大小

11、map和unordered_map的区别 

  1. map实现机理:map内部实现了一个红黑树(红黑树并不是非常严格的二叉搜索树,而AVL是严格的平衡二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。map中的元素是按照二叉树存储的,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值。使用中序遍历可将键值按照从小到大遍历出来。
  2. unordered_map实现机理:内部实现了一个哈希表,通过把关键码映射到hash表中一个位置来访问记录,查找时间复杂度可达O(1)。

11.1红黑树的特性 

红黑树保证最长路径不超过最短路径的二倍,因而近似平衡(最短路径就是全黑节点,最长路径就是一个红节点一个黑节点,当从根节点到叶子节点的路径上黑色节点相同时,最长路径刚好是最短路径的两倍)
特性:

  1. 节点是红色或黑色
  2. 根是黑色
  3. 叶子节点都是黑色,这里所说的叶子节点是空节点
  4. 红色节点的子节点都是黑色
  5. 红色节点的父节点都是黑色
  6. 从根节点到叶子节点的所有路径上不能有两个连续的红色节点
  7. 从任意节点到叶子节点的所有路径都包含相同数目的黑色节点

时间复杂度为logN

12、map和set的区别

map和set底层实现都是红黑树,map和set的区别在于map的值不作为键,键和值是分开的 

 13、哈希表和红黑树的比较

当需要快速查找、删除操作,并且不要求有序性时,哈希表是更好的选择。
当需要有序性、支持范围查找和有序遍历时,或者对空间利用率要求不高时,红黑树是更好的选择。 

14、数组和链表的区别 

 一、存储结构

数组是一种顺序存储结构,它在内存中是连续分配的一块空间

链表是一种链式存储结构,由一系列节点组成,每个节点包含数据域和指针域

二、内存分配

数组的内存分配是静态的,在创建数组时,需要预先指定数组的大小,并且一旦创建,其大小就不能再改变。

链表的大小可以动态地改变,根据需要在运行时添加或删除节点
三、插入和删除

数组的插入删除的时间复杂度为O(N)

链表的插入删除的时间复杂度为O(1)

四、查找

数组支持下标随机访问进行查找

链表只能通过遍历的方式进行查找

15、智能指针

15.0RAII 

资源获取即初始化,资源(如内存、文件句柄、锁等)在对象创建时自动获取,并且在对象销毁时自动释放。 

15.1auto_ptr

auto_ptr就是一个管理权转移问题

两个指针同时指向一块内存,就会两次释放同一块资源,自然报错

15.2unique_ptr

unique_ptr规定一个智能指针独占一块内存资源,当两个智能指针同时指向一块内存,编译报错。

实现原理

将拷贝构造和赋值函数声明为private或delete。不允许拷贝构造和赋值,但是支持移动构造函数,通过move把一个对象指针变成右值之后可以移动给另一个unique_ptr

15.3shared_ptr

shared_ptr可以实现多个智能指针指向相同对象,该对象和其相关资源会在引用为0时被销毁释放。

实现原理

内部有个引用计数,使用拷贝构造和赋值时,引用计数加1,当引用计数为0时,释放资源。

循环引用问题

  1. 循环引用会造成引用计数失效,从而导致内存泄漏问题。
  2. 使用weak_ptr来解决循环引用问题,weak_ptr不会使引用计数加1

线程安全性问题

在多线程的环境下,如果多个线程同时读一个shared_ptr对象是线程安全的,但是如果是多个线程对同一个shared_ptr对象进行读和写,则是线程不安全的,则需要加锁保护。

内存泄漏问题

当两个对象同时使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效从而导致内存泄漏。

15.4weak_ptr

 使用weak_ptr不能知道对象计数是否为0,因为它的构造和析构不会引起计数的增加和减少

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值