暂定结构:基础知识总结-问题分析-参考文献
如有错误,欢迎各位批评指正
代码块在文章中看着有点冗长,但我用的语雀,代码块可以收缩,软件里面看着是比较舒服的
STL,全名叫标准模板库
广义上分为三部分,容器,算法和迭代器,容器和算法通过迭代器进行无缝连接
详细分为六部分,容器-Container,算法-Algorithm,迭代器-Iterator,仿函数-Function object,适配器-Adaptor,空间配置器-Allocator
顺序容器:vector,deque,list
元素顺序增加,插入位置与元素值无关
关联式容器:set/multiset,map/multimap
元素时排序的,根据元素值决定插入的位置
容器适配器:stack,queue,priority_queue
封装了一些基本的容器,具备一些新的功能
动态数组,元素在内存中连续存放,随机访问元素和在尾部增删元素都是常数时间
vector的迭代器
vector的动态空间分配
vector创建时会在内存中分配一块线性连续空间,以两个迭代器指示空间中已使用的范围,另设一迭代器指示整块空间的末尾(可以用capacity()来查看空间大小)。当空间满了后,空间配置器会分配一块更大的空间,然后将原有的元素复制到新空间中,最后释放旧空间,这样所需要的时间旧很多了,同时建立在原空间上的迭代器也会失效。这警示我们用vector时,要预留足够的空间(reserve())
#include<vector>
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
双向队列,元素在内存中连续存放,随机访问元素和两端增删元素都是常数时间
deque的动态空间管理
deque中的元素存储在一段一段定量的连续空间块中,首端空间块向一个方向扩展,尾端空间块向另一个方向扩展,当空间快满时会链接一个新的空间快
deque是由这些空间块组成,每个块可以看作一个定量分配的数组,所以在读取数据时计算出偏移量及可在常数时间内读取
- deque允许在常量时间内对头部元素进行删除和插入
- deque没有容量的概念,deque是以分段的连续空间组合而成,随时可以添加一段空间链接起来,而vector旧空间不足会重新分配一块更大的空间,然后复制元素,再释放旧空间
deque和vector的相似点
- 在尾部插入元素具有良好的性能(常数时间),但在中间插入元素相对较慢
- 元素在内存中都是连续存放的
使用deque的场景
- 需要在两端插入和删除元素
- 不需要引用中间元素时候-因为deque的不同空间块之间是以指针数组的形式连接,所以性能上比不上vector
- 需要释放不再使用的元素-当空间块为空时会进行释放
#include<deque>
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。原数据消失
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
copy(deq.begin(),deq.end(),ostream_iterator<int>(cout," "));//将deq中的元素复制到标准输出流cout,并以空格间隔
deq[index]
deq.at(index)
front();//返回第一个数据。
back();//返回最后一个数据
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(it);//删除it迭代器处的数据,返回下一个数据的位置。
insert(it,elem);//在it迭代器处插入一个elem元素的拷贝,返回新数据的位置。
insert(it,n,elem);//在it迭代器处插入n个elem数据,无返回值。
insert(it,beg,end);//在it迭代器处插入[beg,end)区间的数据,无返回值。
双向链表,元素在内存中是非连续的,元素的逻辑顺序由指针实现
list是由一系列结点构成,每个结点包括存储数据的数据域,存储下一个结点的指针域
list的特点
- 随用随加,不用即释放,不会造成内存上的浪费
- 在任意位置增删元素都是常数时间
- 结点的空间消耗大,读取数据的时间效率低
list的迭代器
list的迭代器是Bidirectional Iterators,具有前移,后移,取值的功能
list的迭代器在增删元素时不会像vector一样丢失,删除元素时只是当前位置迭代器丢失,其他位置迭代器不受影响
#include<list>
list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num,
resize(num, elem);//重新指定容器的长度为num,
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。
front();//返回第一个结点元素值。
back();//返回最后一个结点元素值。
如果想获取结点应该用begin()
reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序
set时一个内部自动排序且不含重复元素的容器,multiset可以有重复元素,set和multiset的底层实现时红黑树,所以容器中的元素时一个个树结点,通过父指针和子结点指针连接在一起
红黑树
红黑树是一种二叉搜索树,左子树结点值小于根节点,右子树值大于根节点,每个树节点包括数据值,左右子结点指针,父节点指针和一个表示颜色的变量,红黑树通过一些规则保持一种平衡
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色的。
- 每个叶子节点(NIL节点,空节点)都是黑色的。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点。
迭代器
set的元素既是键值又是实值,只能通过迭代器访问,因为set需保持有序,不可以通过迭代器更改元素的值,增删元素也不会对其他迭代器造成影响。如果想要改变元素值,只能先删除,然后再添加
set容器的使用场景
- 有序序列
- 快速查找元素
- 需要删除重复元素-插入重复元素会被忽略
#include<set>
set<T,[MyCompare]> st;//set默认构造函数:默认从小到大,Mycompare类里面写重载(),能更高的支持面向对象编程
mulitset<T> mst; //multiset默认构造函数:
set(const set &st);//拷贝构造函数
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
size();//返回容器中元素的数目
empty();//判断容器是否为空
insert(elem);//在容器中插入元素。返回两个值第一个是迭代器第二个是是否插入成功bool值,可以用pair接受
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);//查找键key的元素个数
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
map与set类似,也是是一个根据键值自动排序且不含重复键值的容器,底层实现也是红黑树,但map的元素值是pair,pair的first是键值,second是实值。类似于set的find函数查找的也是键值
迭代器
map 也是通过迭代器对元素进行访问,也不可以通过迭代器修改键值,但是可以修改实值,增删操作也不会对其他迭代器造成影响
map 的使用场景
- 统计元素出现次数
- 充当字典,快速查找元素
- 消去重复元素
- 需要数对作为元素值
#include<map>
map<T1, T2,[MyCompare]> mapTT;//map默认构造函数:
map(const map &mp);//拷贝构造函数
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
size();//返回容器中元素的数目
empty();//判断容器是否为空
mapStu.insert(pair<int, string>(3, "小张"));//通过pair的方式插入对象,返回pair<iterator,bool>
mapStu[3] = "小刘"; //通过数组的方式插入值
clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
stack是一种先进后出的STL容器,不提供迭代器,只能再在顶获取元素,增删操作也是在栈顶完成
底层实现
stack的默认底层实现是deque,也可以用list和vector实现
#include<stack>
stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
stack(const stack &stk); //拷贝构造函数
push(elem); //向栈顶添加元素
pop(); //从栈顶移除第一个元素
top(); //返回栈顶元素
empty(); //判断堆栈是否为空
size(); //返回栈的大小
queue是一种先进先出的STL容器,不提供迭代器
底层实现
queue的默认底层实现是deque,也可以用list实现
优先级队列priority_queue,queue的一种,包括在queue.h里面
优先级队列会根据给定的优先级函数对插入元素进行排序,在访问时也是先访问优先级最高的元素
优先级队列的底层实现是堆(完全二叉树),存储元素的容器默认是vector,也可以是deque
#include<queue>
priority_queue<int, vector<int>, less<int>> priQueMaxFirst;
//第一个是元素的数据类型,第二个是存储数据的容器,第三个是优先级函数
//less是大项堆,大数据在前,小于前面的元素时下沉.greater时小项堆
//也可以用自定义的优先级函数
priority_queue<int, vector<int>, cmp> priQueMaxFirst;
struct cmp //class也可以
{
bool operator()( Data &a, Data &b){
return a.getId() < b.getId();
}
};//这是个大项堆,可以看作a是新增元素,然后逐个跟最前边元素比较
q.top();
//优先级队列获取优先级最高的元素操作时top不是front,也没有back,其他跟queue相同
#include<queue>
queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数
push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素
empty(); //判断堆栈是否为空
size(); //返回栈的大小
本质是容器类中的模板类,类中对一些操作符进行重载,其对象可以对容器中的元素进行访问等一系列操作
迭代器的类型
- 前向迭代器(Forward Iterator):支持解引用*,递增++,赋值=操作
- 双向迭代器(Bidirectional Iterator):支持前向迭代器的操作和递减–操作
- 随机访问迭代器(Random Access Iterator):支持双向迭代器的操作和+,-,+=,-=,>,<操作
各容器的迭代器类型
容器名 迭代器类型
array 随机访问
vector 随机访问
deque 随机访问
stack、queue、priority_queue 不支持
list 双向
set/multiset 双向
map/multimap 双向
迭代器的定义和获取
容器类名::iterator it;
vector<int>::iterator it;
auto it;
//auto自动识别类型
容器对象.begin();
容器对象.end();
容器对象.rbegin();
容器对象.rend();
迭代器的辅助函数
advance(it,n);//移动it
distance(it1,it2);
iter_swap(it1,it2);
各种常⽤的算法,如sort、find、copy、for_each。从实现的⻆度来看,STL算法是⼀种function tempalte.
常见算法及用法
//算法主要是由头文件<algorithm><functional> <numeric>组成。
//<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…
//<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
//<functional> 定义了一些模板类,用以声明函数对象。
//sort
sort(vec.begin(),vec.end()); //默认为从小到大
class MyCompare
{
public:
bool operator()(int num1, int num2)
{
return num1 > num2;
}
};
sort(v.begin(), v.end(),MyCompare());//利用仿函数,排序为从大到小
//find
class GreaterThenFive
{
public:
bool operator()(int num)
{
return num > 5;
}
};
vector<int>::iterator it = find(v.begin(), v.end(), GreaterThenFive());
//value也可以,如果没有找到返回v.end()
//for_each 遍历 返回函数对象
void print01(int val){
cout << val << " ";
}
struct print001{
void operator()(int val){
cout << val << " ";
}
};
void test01(){
vector<int> v;
for (int i = 0; i < 10;i++){
v.push_back(i);
}
//遍历算法
for_each(v.begin(), v.end(), print01);
cout << endl;
for_each(v.begin(), v.end(), print001());
cout << endl;
}
//for_each返回值
struct print02{
print02(){
mCount = 0;
}
void operator()(int val){
cout << val << " ";
mCount++;
}
int mCount;
};
void test02(){
vector<int> v;
for (int i = 0; i < 10; i++){
v.push_back(i);
}
print02 p = for_each(v.begin(), v.end(), print02());
cout << endl;
cout << p.mCount << endl;
}
//copy
int src[] = {1, 2, 3, 4, 5};
int dst[5];
copy(std::begin(src), std::end(src), std::begin(dst));
仿函数是一个类对象,类中重载了函数调用运算符,使得它能像函数一样调用
作用
- 可以作为一些算法的参数,如sort,for_each,用来定制算法的行为
- 可以用来保持内部状态,如优先队列,map,set
stl适配器有三种,容器适配器,迭代器适配器,函数适配器
有stack,queue,priority_queue,基本一些基本容器,如deque,vector实现,行使一些不同的功能(先进先出,新进后出)
包括反向迭代器(reverse_iterator),插入迭代器(insert_iterator),流迭代器(istream_iterator,ostream_iterator),都是在基本迭代器的基础上具有特定功能的迭代器
常用来对容器进行反向遍历,++变–,–变++
#include <iterator>
vector<int> myvector{ 1,2,3,4,5,6,7,8 };
reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.rbegin());//指向 8
cout << *my_reiter << endl;// 8
cout << *(my_reiter + 3) << endl;// 5
cout << *(++my_reiter) << endl;// 7
向指定容器插入元素
- back_insert_iterator 在指定容器的尾部插入新元素,但前提必须是提供有 push_back() 成员方法的容器(包括 vector、deque 和 list)
- front_insert_iterator 在指定容器的头部插入新元素,但前提必须是提供有 push_front() 成员方法的容器(包括 list、deque 和 forward_list)
- insert_iterator 在容器的指定位置之前插入新元素,前提是该容器必须提供有 insert() 成员方法
#include <iterator>
list<int> foo(2, 5);
list<int>::iterator it = ++foo.begin();
insert_iterator<list<int>> insert_it = inserter(foo, it);
*insert_it = 1;
*insert_it = 2;
*insert_it = 3;
*insert_it = 4;
//5 1 2 3 4 5
list<int> foo(2, 5);
back_insert_iterator<list<int>> back_it = back_inserter(foo);
*back_it = 1;
*back_it = 2;
*back_it = 3;
*back_it = 4;
//5 5 1 2 3 4
list<int> foo(2, 5);
front_insert_iterator<list<int>> front_it = front_inserter(foo);
*front_it = 1;
*front_it = 2;
*front_it = 3;
*front_it = 4;
//4 3 2 1 5 5
- istream_iterator 从输入流中读取数据
- ostream_iterator 将元素输出到输出流中
#include <iterator>
vector<int> numbers = {1, 2, 3, 4, 5};
ostream_iterator<int> out_it(cout, " "); // 向标准输出写入整数,使用空格分隔
copy(numbers.begin(), numbers.end(), out_it); // 将vector中的数据写入输出流
double value1, value2;
std::cout << "Please insert values: "; //11 11 2
std::istream_iterator<double> eos; //输入流的结束位置
std::istream_iterator<double> iit (std::cin); //读取输入流的每个数据,绑定std::cin
if (iit!=eos) value1=*iit;
++iit;
if (iit!=eos) value2=*iit;
std::cout << value1 << "*" << value2 << "=" << (value1*value2) << '\n';
//11 * 11 = 121
函数适配器可以修改现有函数的参数,返回值和调用方式从而生成另一种函数
迭代器不只存在于容器中,自定义的类中,也可以定义一个迭代器类,类中将++,–,*等类似于指针的操作符方法重载
#include <iostream>
// 自定义容器类
class MyContainer {
private:
int data[5] = {1, 2, 3, 4, 5};
public:
// 自定义迭代器类
class Iterator {
private:
int* ptr;
public:
Iterator(int* p) : ptr(p) {}
// 重载*操作符,获取指向的元素值
//返回的时数据的引用,表示用户可以通过*it修改值
//const表示这个重载函数内部不会对对象的成员变量进行修改
int& operator*() const {
return *ptr;
}
// 重载++操作符,将指针移动到下一个位置
Iterator& operator++() { //返回引用是为了支持连续的递增操作,++++
++ptr;
return *this; //this指向调用该成员函数的对象的指针
}
// 重载!=操作符,判断两个迭代器是否相等
bool operator!=(const Iterator& other) const {
return ptr != other.ptr;
}
};
// 获取容器起始位置的迭代器
Iterator begin() {
return Iterator(data);
}
// 获取容器结束位置的迭代器
Iterator end() {
return Iterator(data + 5);
}
};
int main() {
MyContainer container;
// 使用自定义容器的迭代器遍历容器中的元素并输出
for (auto it = container.begin(); it != container.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
迭代器失效时指迭代器完全无效或者指向的元素不同
迭代器的作用
有指针为什么还要用迭代器?(迭代器和指针的区别)
迭代器与指针不同,迭代器是模板类,封装了指针,重载了指针的++,–,->等操作符,可以在不暴露容器内部结构的情况下对元素进行遍历,是一种"高级"指针,可以根据不同的数据类型实现不同的++,–等操作
主要考察的是迭代器失效问题
回答与迭代器失效相同,只关注删除
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务