一.知识点梳理
1.标准模板库(STL)简介
- 库
系列程序组件的集合,它们可以在不同的程序中重复使用,对库设计的要求就是通用性 - 模板
实现通用性的基础,可以在使用模板时才对类型做选择 - 标准模板库(STL)
包含了容器类、迭代器和算法三个部分
容器(Container):某类对象的集合
迭代器(Iterator):在对象集合上进行遍历
算法(Algorithm):处理集合内的元素
- 三者之间的关系
2. 容器
2.1 容器的分类
- 顺序容器
元素根据它的位置排列和访问,每个元素都有固定位置,元素的位置取决于插入时机和地点,和元素值无关。
vector、deque、list
顺序容器 | 说明 |
---|---|
vector(矢量) | 从容器尾部快速插入与删除,可随机直接访问任何元素 |
deque(双端队列) | 从容器头部或尾部快速插入与删除,直接访问任何元素 |
list(链表) | 从任何地方快速插入与删除,双向链表 |
- 关联容器
每个元素都没有固定位置,元素位置取决于容器自己特定的排序规则,与键值有关,与插入顺序无关;根据键来访问元素。
set、map;
关联容器 | 说明 |
---|---|
set(集合) | 快速查找,不允许重复值 |
multiset(多重集合) | 快速查找,允许重复值 |
map(映射) | 一对一映射,基于关键字快速查找,不允许重复值 |
multimap(多重映射) | 一对多映射,基于关键字快速查找,允许重复值 |
- 其它容器
Hash_set、hash_map、bitset、stack、queue等
其它容器 | 说明 |
---|---|
stack(栈) | 后进先出(LIFO) |
queue(队列) | 先进先出(FIFO) |
priority_queue(优先级队列) | 最高优先级元素总是第一个出列 |
- 容器的头文件
- 知识要点
1.所有容器都提供有效的动态内存管理,用户在添加元素时,不必担心元素存储在哪里,删除元素时,也不必担心元素的释放
2.要用作容器的元素类型,必须满足以下两个条件:
(1)元素类型必须支持赋值运算
(2)元素类型的对象必须可以复制
引用不支持赋值,所以没有元素时引用类型的容器
IO库类型不支持复制或赋值,不能创建存放IO类型对象的容器
3.容器如果是存储类类型的元素,最好这个类定义默认的构造函数
2.1.1 顺序容器——vector
- 定义和特点
vector是一个类模板,定义对象时要显示的指定类型实参
vector<int> vTest;
特点:
1、支持对元素的随机快速访问,可以有效的在容器尾部添加和删除元素。但在其它位置插入或者删除则要付出昂贵的代价
2.基本上所有的STL算法都能对vector操作
3.vector实际上就是动态数组,是大小可变的向量,在需要时可以改变其大小
- vector类对象的构造方法
vector <type> vTest1; //创建空的vTest1
vector <type> vTest2(n); //vTest2包含值为已经初始化元素的n个副本
vector <type> vTest3(n, value); //vTest3包含n个值为value的元素
vector <type> vTest4(vTest2); //采用复制构造函数,vTest4是vTest2的副本
vector <type> vTest5(v4.begin(), v4.end());
- vector类的常用操作
1.获取元素的操作
函数操作 | 作用 |
---|---|
front() | 返回第一个元素 |
back() | 返回最后一个元素 |
at(i) | 返回第i个元素 |
[i] | 返回第i个元素 |
2.迭代器的操作
函数操作 | 作用 |
---|---|
begin() | 返回指向第一个元素的迭代器 |
end() | 返回超出末端的迭代器 |
rbegin() | 返回逆序迭代器,指向最后一个元素 |
rend() | 返回逆序迭代器,指向第一个元素的前面的位置 |
3.容器容量的操作
函数操作 | 作用 |
---|---|
size() | 返回vector元素数量的大小 |
capacity() | 返回容器需要分配更多存储空间之前所能存储的元素总数 |
max_size() | 返回vector所能容纳元素的最大数量 |
reserve(size_t size) | 设定预留size个元素的存储空间 |
empty() | 判断vector是否为空 |
4.对vector的元素进行修改的操作
函数操作 | 作用 |
---|---|
pop_back() | 移除最后一个元素 |
push_back() | 在vector最后添加一个元素x |
clear() | 清空所有元素 |
erase(begin, end) | 删除迭代器begin和end所辖范围内的元素 |
erase(i) | 删除迭代器i所指向的元素 |
insert(i, x) | 把x插入vector中由迭代器i所指明的位置 |
insert(i, begin, end) | 把迭代器begin和end所管辖范围内的元素插入到vector中由迭代器所指明的位置 |
insert(i, n, x) | 把x的n个副本插入vector中由迭代器i所指明的位置 |
assign(begin, end) | 用迭代器begin和end所管辖范围内的元素替换vector元素 |
assign(num, val) | 用val的num个副本替换vector元素 |
2.1.2 顺序容器——deque
双端队列类模板deque类似vector,区别是支持高效的在头部插入和删除元素
- 操作
函数操作 | 作用 |
---|---|
pop_back() | 移除最后一个元素 |
pop_front() | 移除第一个元素 |
push_back(x) | 在deque最后添加元素x |
push_front(x) | 在deque最前面添加元素x |
2.1.3 顺序容器——list
- 双向链表容器
不支持随机访问,访问某个元素要求遍历所涉及的其它元素;
任意位置插入和删除元素所花费的时间是固定的,与位置无关,效率高
2.1.3 顺序容器的选择规则
- 如果要求随机访问元素,则使用vector和deque
- 如果必须在容器的中间位置插入或删除元素,则使用list
- 如果不是在中间位置,而是在首部或者尾部,则使用deque
- 如果即需要随机访问,又要在中间位置插入或删除,则选择哪种容器要取决于下面两种操作付出的相对代价:
(1)随机访问list元素的代价。
(2)在vector或deque中插入或删除元素时复制元素的代价。
实际程序中更多的使用是访问还是插入删除,来决定相对代价小的容器。
2.2 关联容器
2.2.1 关联容器与顺序容器的区别
关联容器通过键(key)访问元素;
顺序容器通过元素在容器中位置存储和访问元素。
2.2.2 关联容器map
-
基本概念
1.map的本质是元素的值与某个特定的键相关联,而并非通过元素在容器中的位置来获取;
2.map类型的对象所包含的元素必须具有不同的键;
3.map支持很多顺序容器也提供的操作,同时还提供管理或使用键的特殊操作。 -
与map相关的类型pair
包含的头文件:#include< utility>pair <string, string> pTest; pTest.first // 返回pTest中第一个类型的数据值 pTest.second //返回pTest中第二个类型的数据值 pair <int, string> pTest2 = make_pair(1, "hello"); //表示创建一个pair对象调用pair<int, string>类的复制构造函数来给pTest2进行初始化
-
map类对象的构造
map <key, value> m1; //m1当前为空,其键和值的类型为key和value map <key, value> m2(m1); //复制构造,m2与m1必须有相同的键类型和值类型 map <key, value> m3(m1.begin(), m1.end()); //用迭代器进行构造
-
map类模板中定义的类型
map<k, v>::key_type表示map中用作索引的键的类型
map<k, v>::mapped_type表示map中键关联的值的类型 -
map的下标操作
vector的下标操作只能访问已经存在的元素,不能添加元素
map用下标访问不存在的元素将在容器中添加一个新的元素,它的键即为该下标值
如果该键存在,则下标操作仅仅表示返回值为键值对值的引用 -
map和insert的操作
map <int, char> m1; m1[1] = 'a'; m1.insert(mkle_pair(1, 'b')); m1.insert(mkle_pair(2, 'b'));
下标操作中,用键来获取该键关联的值,如果键已在容器中,则重新给键关联的值赋值
insert操作与下标操作不同的是,如果键已在容器中,则insert将不做任何操作
3.迭代器
3.1 迭代器的定义
迭代器是一种检查容器内元素并遍历元素的数据类型
迭代器操作提供了比下标操作更通用化的方法访问元素
迭代器指向容器中的一个特定位置
- 标准库为每一种容器定义了一种迭代器类型
vector<int>::iterator iter;
3.2 begin和end操作
每一种容器都定义了一对名为begin和end的操作,用来返回迭代器;
如果容器中有元素,则begin返回第一个元素,end返回的迭代器指向容器末端元素的下一个,即超出末端迭代器:
3.3 迭代器的基本操作
迭代器的基本操作,类似普通的指针
4.string类
string是标准库类型,是标准库中定义的一个类
支持长度可变的字符串,C++标准库负责管理与存储字符相关的内存。
定义string类型的目的就是满足对字符串的一般应用。
string类支持大部分的顺序容器操作。
4.1 string类对象的构造
函数操作 | 作用 |
---|---|
string s1 | 默认构造函数,初始化为空串 |
string s2(s1) | 复制构造函数,s2初始化为s1的副本 |
string s3(“value”) | 将s3初始化为一个字符串常量的副本 |
string s4(“value”, size_t n) | 将s4初始化为一个字符串常量的一部分的副本。 |
string s5(n, ‘c’) | 将s5初始化为字符’c’的n个副本 |
string s6(s1,2,3) | 将s6初始化为s1一部分的副本 |
string s7(s3.begin(),s3.end()) | 用一对迭代器指向的元素之间的范围给s7初始化 |
4.2 string类常用操作
函数操作 | 作用 |
---|---|
s.empty() | 判空 |
s.size() | 返回s中字符的个数返回值类型为string::size_type |
s[n] | 下标操作,返回s中位置为n的字符 |
s.at(n) | 和下标操作类似,但会进行边界检查 |
s1+ s2 | 字符串拼接 |
s1 = s2 | 赋值操作 |
== != < > | 关系运算符 保持原有含义 |
s.c_str() | 返回const char* C风格的字符串 |
- 注意事项
支持大部分的顺序容器的操作
支持迭代器
不支持以栈方式操作容器:不能使用front、back、pop_back
5.泛型编程
5.1 泛型算法
标准库容器定义的功能函数并不多,而是选择提供一组算法,这些算法与容器的类型大多不相关,是泛型算法;
STL中提供大约有70多种标准算法,算法是函数模板;
1.算法可以处理容器,也可以处理C语言的数组;
2.算法是通过遍历由两个迭代器标记的一段元素来实现其功能;
3.算法本身并不执行容器提供的操作,只是单独依赖迭代器和迭代器操作实现;
4.算法要求容器内的元素必须能做比较运算
包含在头文件#include < algorithm>
5.2 算法分类
- 只读算法,对容器内的元素不改变:copy, equal, mismatch, search, count_if, for_each
- 改变容器元素的算法:copy, remove, fill, replace, swap
- 对容器内元素重新排序的算法:sort, unique
二.疑难点总结
1. map和set有什区别,分别是怎么实现的?
- 先说二者是怎么实现的
map和set都是c++容器当中的关联容器,其底层也都采用了红黑树来实现,并且map和set的一些操作接口与红黑树一样,就好像所有的操作都是转掉红黑树的操作; - 二者的不同:
1.map中元素是以形式为键值对形式存储的,键在里面起到的是索引的作用,而值在与一个确定的索引进行绑定,可以通过一个索引找到一个确定的值;然而set的元素只包含键(key),是键的一个简单的集合;
2.set的迭代器是const只读的不能进行修改的,而map中的value是可以被修改,但是key也不能被修改的;不能被修改的原因是其底层的排序是根据键来保证其有序性,一旦允许修改key的值,那么需要删除掉旧的key然后在插入新的key,导致整个底层结构发生变化,iterator指针也不知道是指向改变之前还是改变之后,容易出错,所以STL中将set的迭代器设置为只读,map的迭代器可以修改value的值,但是key也是只读;
3.map支持下标操作,set不支持;
2. 简单介绍一下STL中的allactor
- 基本定义:allactor:STL分配器
我们平时所使用的的new和delete,都是分为两个阶段;new先调用::operator进行配置内存:然后在调用对象的构造函数进行初始化;delete是先调用析构函数,然后在::operator delete进行释放内存;
然而allactor是将这两个阶段分开来进行操作,就比如我可以先申请一块空间,但是我现在还不用:通俗点及时先抢占一块处女地,不进行开垦;
3. STL迭代器是如何删除元素的?
对于序列容器vector和deque来说,使用了erase删除元素之后,后面的迭代器会失效,导致无法正常使用,但是会返回回来一个有效的迭代器,这个时候我们需要设置一个迭代器用来接收它返回回来有效的迭代器;还有一点就是调用了erase之后,后面的每个元素会向前移动一位;
但是对于关联容器map和set来说,其底层结构是红黑树,所以调用了erase之后,不会影响下一个迭代器的有效性,只会致使当前的迭代器失效;list由于使用了不连续存储的方式,所以也不会影响下一个元素的迭代器的有效性;
4. STL由什么组成?
容器、迭代器、算法、分配器、仿函数等;
分配器负责给容器分配空间,而算法通过迭代器负责从容器中获取想要处理的数据,仿函数可以协助算法完成一些操作,套接器用来套接适配仿函数;
5. STL中map和unordered_map的不同和相同?
map和unordered_map在底层都是通过红黑树来实现的,能够自动对键进行排序,并且元素都是按照key-value,也就是一对一对存储的;
不同之处是map的键是唯一的,而UNordered_mpa的键是可以重复存在的;
6. vector和list的区别,以及应用什么不同?
- vector概念:
vector是一个连续存储的容器,有操作系统动态在堆中分配内存空间,其底层的实现原理是数组; - 特点:
两倍容量增长,当vector进行插入元素操作的时候,如果空间足够,那么直接插入进去并调整迭代器;如果空间不够,则按照当前元素的空间大小,重新申请一个两倍大的空间,然后将原有的数据存储到新的空间,并添加数据,并且将原有的空间给析构释放掉,之前的迭代器失效; - 性能
访问:时间复杂度:O(1)
插入:
1.在末端插入且空间足够,直接插入,速度很快;空间不够,需要动态申请空间和释放,以及对之前的数据进行拷贝;
2.在中间插入数据且空间足够时,进行拷贝;空间不够时, 需要动态申请和释放,以及拷贝;
删除同插入的原理类似;
- 应用
对容器的元素经常随机访问,且不频繁的删除和增加元素; - list概念
list是一个动态链表,操作系统在堆上进行分配内存空间,每插入一个元素会申请一块空间,每删除一个元素会释放一次空间,其底层原路是双向链表 - 性能
访问:随机访问的能力很差,只能从头到尾遍历
插入和删除:非常方便 - 应用
经常频繁的插入和修改数据 - 二者的区别汇总
1.vector底层是数组实现,而list底层是双向链表
2.vector支持随机访问,list不支持;
3.vector插入和删除元素上开销比较大,而list较小;
4.vector是一次将内存分配好,不够在进行动态分配,而链表是每操作一次申请或释放一次;
5.vector的随机访问性能好,但是插入删除的性能较差,但是list与其相反;
7. STL中迭代器的作用,有了指针为什么还有用迭代器?
-
迭代器
(Iterator)模式也被叫做游标模式,提供一种方法可以进行顺序的访问容器里面的内容,其特点是可以在不知道容器内部具体结构和组成的情况下,通过迭代器进行访问数据元素;
-
与指针的区别:
迭代器不是指针,是类模板,它通过重载了一些指针的操作符,比如*,++等,模拟了指针的功能,将指针进行了封装,是一个可以遍历容器内全部或部分元素的对象,是指指针的一种升级;为什么会产生迭代器,是因为可以把不同类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到访问元素的目的;