标准模板库(Standard Template Library,简称STL)简单说,就是一些常用数据结构和算法的模板的集合。
广义上讲,STL分为3类:Algorithm(算法)、Container(容器)和Iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。
容器:数据结构 list vector deques
算法:操作容器中的数据的模板函数,sort() find()函数;
迭代器:访问容器内对象的方法。指针,iterator
仿函数:重载了操作符的struct,
适配器:接口类,专门用来修改现有类的接口,提供一中新的接口
空间配制器:对象的创建与销毁;内存的获取与释放。
请说说 STL 中常见的容器,并介绍一下实现原理
1.顺序容器
vector: 动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。
list:双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
deque :双向队列。元素在内存连续存放。在两端增删元素具有较佳的性能
2.关联容器
元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap
set/multiset *set 即集合。set中不允许相同元素,multiset中允许存在相同元素。
map/multimap.map与set的不同在于map中存放的元素有且仅有两个成员变,一个名为first,另一个名为second, map根据first值对元素从小到大排序,并可快速地根据first来检索元素。
3.容器适配器
封装了一些基本的容器,使之具备了新的函数功能,比如把deque封装一下变为一个具有stack功能的数据结构。这新得到的数据结构就叫适配器。包含stack,queue
栈是项的有限序列,后进先出。序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶的项)
队列,先进先出。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。
3.map内部实现了一个红黑树
hashtable采用了函数映射的思想记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找
eque内部实现的是一个双向队列
list内部实现的是一个双向链表。元素在内存不连续存放
vector
采用一维数组实现,元素在内存连续存放,不同操作的时间复杂度为:
插入: O(N)
查看: O(1)
删除: O(N)
deque
采用双向队列实现,元素在内存连续存放,不同操作的时间复杂度为:
插入: O(N)
查看: O(1)
删除: O(N)
list
采用双向链表实现,元素存放在堆中,不同操作的时间复杂度为:
插入: O(1)
查看: O(N)
删除: O(1)
map、set、multimap、multiset
上述四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为:
插入: O(logN)
查看: O(logN)
删除: O(logN)
unordered_map、unordered_set、unordered_multimap、 unordered_multiset
上述四种容器采用哈希表实现,不同操作的时间复杂度为: 插入: O(1),最坏情况O(N)
查看: O(1),最坏情况O(N)
删除: O(1),最坏情况O(N)
4.迭代器用过吗?什么时候会失效?
对序列容器vector,deque来说,使用erase后,后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。
对于关联容器map,set来说,使用了erase后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素,不会影响下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此上面两种方法都可以使用。
**
5.迭代器的作用:对顺序容器和关联容器中的元素进行操作
用于指向顺序容器和关联容器中的元素
通过迭代器可以读取它指向的元素
通过非const迭代器还可以修改其指向的元素**
6.迭代器不是指针,是类模板,表现的像指针(更像智能指针)。
迭代器返回的是对象引用而不是对象的值
Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。
STL 中 resize 和 reserve 的区别
(1)capacity:该值在容器初始化时赋值,指的是容器能够容纳的最大的元素的个数。还不能通过下标等访问,因为此时容器中还没有创建任何对象。
(2)size:指的是此时容器中实际的元素个数。可以通过下标访问0-(size-1)范围内的对象。
C++11新特性
1.列表初始化,任何类型对象的初始化
2.成员变量默认初始化。class B { public: int m = 1234; //成员变量有一个初始值 int n}
3.auto关键字 编译器可以自动判断的类型
vector< vector > v;
vector< vector >::iterator i = v.begin();
auto i = v.begin();
4.decltype 求表达式的类型
auto varname = value; decltype(exp) varname = value;
decltype 根据 exp 表达式推导出变量的类型,跟"="右边的 value 没有关系。
5.智能指针 shared_ptr
多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。
6.初始化空类型指针 nullptr int * a1 = nullptr; 特殊类型的字面值,可以被转换成其他的指针类型。
7.右值引用和move语义
必须绑定到右值的引用,主要是对一些临时变量来使用
左值引用不能绑定到要求转换的表达式。
实际开发中我们可能需要对右值进行修改(实现移动语义时就需要)
move语义:避免非必要拷贝和临时对象 。将某个左值强制转化为右值
8.正则表达式 :正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串
9.Lambda匿名函数:
auto f = [](int a){ return a + 1; };
[=]值传递的方式(读取所有可读数据,但不能改动)
[&]参考的方式改动
[] 不捕获任何变量。
[=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
[bar] 按值捕获 bar 变量,同时不捕获其他变量。
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
10.如果在程序中使用new从堆(自由存储区)分配内存,等到不需要时,应使用delete将其释
C++引用了智能指针auto_ptr,以帮助自动完成这个过程。
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化
11. C++ 右值引用与转移语义
一般来说,不能取地址的表达式,就是右值引用,能取地址的,就是左值。
int i =24;
int &j =i;左值引用
int &&k = i*j;右值引用
int &&k = i+1;
12.智能指针及其实现,shared_ptr 线程安全性,原理
auto_ptr p3(new string(“I reigned loney as a cloud.”));
auto_ptr p4;
p4=p3; //此时不会报错
unique_ptr pu1(new string (“hello world”));
unique_ptr pu2;
shared_ptr实现共享式拥有概念。 资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数
55.线程安全性
多个线程同时读同一个shared_ptr对象是线程安全的,但是如果是多个线程对同一个shared_ptr对象进行读和写,则需要加锁。
多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。
当两个对象同时使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄露。