stl的stack怎么用,stl基本容器
Yyds干货库存
写在这前面应该是我们STL的最后一篇博客了。今天,有三种数据结构,即堆栈、队列和优先级队列。从某种意义上说,它们不叫容器,而是容器适配器。我们先互相了解一下。
容器适配器。当我们看他们的模板参数时,你会明白第二个参数是一个容器。所谓容器适配器,就是我们自己不实现,而是用另一种数据结构来实现。
在这里,我想给你解释一下什么是适配器。在我们的日常生活中,我们知道mainland China的民用电压是220v,但当你给手机充电时,它不是200v。这是因为你的手机充电器改变了电压。这个充电器是一个适配器。也就是说,一个东西可以用,但可能功能多一点,我们只需要拿出一小部分。
计算机也是如此。我们可以用一种封装另一种。我们直接重用他们原来的方法,就不自己写了。
这里我们不会演示如何使用堆栈。一切都很简单。让我们看看它们是如何实现的。
这里,我们直接用一个数据结构来模拟实现栈。至于下面的德缺,暂且不提。
模板类T,类容器=std:deque T
类堆栈
{
公共:
无效推送(常数x) {
_ con . push _ back(x);
}
void pop() {
_ con . pop _ back();
}
const T top() {
return _ con . back();
}
bool empty() {
return _ con . empty();
}
size_t size() {
return _ con . size();
}
私人:
Container _ con
};在这里测试一下吧。我们使用标准库来测试它。
#包括堆栈
int main()
{
stack int,vector int s;
s . push;
s . push(2);
s . push(3);
s . push(4);
而(!s.empty()
{
cout s . top()endl;
s . pop();
}
返回0;
}
是的,你没有看错。如果你不想用deque做容器,我们也可以选择其他的。但是你选择的数据结构必须有对应的函数,这也是STL中数据结构的函数名大多相同的优势。
这和stack是一样的,stack是使用的另一种数据结构。排队没什么好谈的。
模板类T,类容器=std:deque T
类别队列
{
公共:
无效推送(常数x) {
_ con . push _ back(x);
}
void pop() {
_ con . pop front();
}
const T top() {
return _ con . front();
}
bool empty() {
return _ con . empty();
}
size_t size() {
return _ con . size();
}
T back() {
return _ con . back();
}
const back()const {
return _ con . back();
}
T front() {
return _ con . front();
}
const T front()const
return _ con . front();
}
私人:
Container _ con
};Priority_queue使用这个优先级队列,它本质上是一个底层的堆。我们今天的重点就是这个内容。
我们可以先看看他们的接口。都比较简单。
先简单用一下。这个默认好像是大优先,就是建一个大堆。
#包括iostream
#包括队列
使用命名空间std
int main()
{
priority _ queue int p;
p . push(1);
p . push(2);
p . push(0);
p . push(-1);
p . push(1100);
而(!p.empty())
{
cout p . top()“”;
p . pop();
}
返回0;
}
我们想知道我们是否能建一个小堆。标准库中有一个模仿函数更大,可以帮助我们建立一个小堆。至于模仿功能,我们只需要先知道它的用法就可以了。以后再分享。
int main()
{
priority_queue int,vector int,greater int p;
p . push(1);
p . push(2);
p . push(0);
p . push(-1);
p . push(1100);
而(!p.empty())
{
cout p . top()“”;
p . pop();
}
返回0;
}
Priority_queue模拟现在我们还可以模拟priority_queue是如何实现的。它的本质是一个数组,也就是一个堆。这里不讨论如何向上或向下调整。我们已经在数据结构里和大家分享过了,这里就不赘述了。我们主要看模仿功能的使用。
优先级队列也是一个容器适配器。
模板类T
无结构
{
布尔运算符()(常数t1,常数t2)常数
{
返回t1 t2
}
};
模板类T
结构更大
{
布尔运算符()(常数x,常数y)常数
{
返回x y;
}
};
模板类T,类容器=std:vector T,类比较=bit:less T
类别优先级_队列
{
公共:
优先级队列()
{
}
私人:
Container _ con
};推到堆里
无效推送(常数x)
{
_ con . push _ back(x);
adjustUp(_ con . size()-1);
}从堆中弹出()
无效弹出()
{
断言(!_ con . empty());
std:swap(_con[0],_ con[size()-1]);
_ con . pop _ back();
adjust dwon(0);
}top()查看堆叠的元素
const T top()
{
return _ con[0];
}调整我们的数据进出堆需要调整,如果我们写下堆中元素的优先级,代码的灵活性就不行了。比如我们要一个小堆,你给我一个现成的堆。这是一个问题。我们无法控制它。早期可能用函数指针实现,但是函数指针的可读性太差。我们逐渐把它改成了模仿功能。
仿函数我们先来了解一下仿函数。我们来看看这样的结构。
结构我的结构
{
布尔运算符()(常量整数x,常量整数y)
{
返回x y;
}
};我们重载了()操作符,这使得我们的调用看起来像是函数调用,所以我们称之为模仿函数。
int main()
{
MyStruct comfunc
cout comfunc(1,2)endl;
返回0;
}
void adjust won(size _ t parent)
{
比较comFunc
size _ t child=2 * parent 1;
while(子尺寸())
{
if(child 1 _ con . size()com func(_ con[child],_con[child 1])
{
孩子;
}
if(com func(_ con[父级],_ con[子级]))
{
STD:swap(_ con[父级],_ con[子级]);
父母=孩子;
child=2 * child 1;
}
其他
{
打破;
}
}
}
void adjustUp(int child)
{
//先建一个大堆
比较comFunc
size _ t parent=(child-1)/2;
while(子0)
{
if(com func(_ con[父级],_ con[子级]))
{
STD:swap(_ con[父级],_ con[子级]);
孩子=父母;
parent=(child-1)/2;
}
其他
{
打破;
}
}
}仿函数最大的好处就是代替了函数指针。对于自定义类型,我们也可以单独编写它们的模仿函数,但是要求自定义类型重载等号运算符。但是我们也知道C支持C语言。我们之前学习qsort的时候用到了函数指针,那么这里也可以传入函数指针吗?可以,但是需要一些方法。
我们先假设可以像最初的qsort一样使用它。
bool comFare(int x,int y)
{
返回x y;
}
int main()
{
bit:priority_queue int,vector int,bool(*)(int,int)p;
p . push(1);
p . push(3);
p . push(2);
返回0;
}
现在我们要困惑了。我们的第三个模板参数是函数指针类型,可以看作是内置类型。编译器为自动调用它的构造函数初始化nullptr。所以编译器会报错。这里我们开始尴尬了。我们应该怎么做才能传入我们想要的比较函数?我们在这里不能使用模板参数,但是构造函数可以。
我们把模板参数可以实例化的对象看作是类中的一个成员变量,所以在构造函数的时候初始化它。我们需要修改函数。
这样,我们可以像这样使用优先级队列。
bool comFare(int x,int y)
{
返回x y;
}
int main()
{
bit:priority_queue int,vector int,bool(*)(int,int)p(com fare);
//bit:priority _ queue int p(com fare);
p . push(1);
p . push(3);
p . push(2);
p . push(0);
p . push(-1);
而(!p.empty())
{
cout p . top()“”;
p . pop();
}
返回0;
}
我们来试试之前的用法。能用吗?可以找到,后面需要解释原理。
int main()
{
//bit:priority_queue int,vector int,bool(*)(int,int)p(com fare);
bit:priority _ queue int p;
p . push(1);
p . push(3);
p . push(2);
p . push(0);
p . push(-1);
而(!p.empty())
{
cout p . top()“”;
p . pop();
}
返回0;
}
我先解释一下传入的函数指针的类型。我们知道,实例化对象将调用默认的构造函数,如下所示。而且我们实例化对象的时候传入参数,第三个模板参数的类型就变成了函数指针的类型。这里的类型互相匹配。
这样我们就可以理解为,如果不传入函数指针类型,那么第三个模板参数默认是它的默认值,也就是仿函数类型west。编译器自动调用构造函数,并实例化模仿函数类型的对象,这就是为什么它可以被使用。我们知道我们可以使用函数指针,但如果有一个简单的函数指针,为什么还要用它呢?
这里的Sort,我们还想看看仿函数的应用,最好的体现就是这个sort函数,这是STL算法库中提供的一个函数。当我们看迭代器类型时,我们让你看到它。这里不做详细介绍,只说模仿功能相关的知识。
int main()
{
向量int v;
五. push _ back(1);
五. push _ back(2);
五. push _ back(3);
五、push _ back(13);
v . push _ back(-1);
sort(v.begin()、v . end());
for (int val : v)
{
cout val“”;
}
返回0;
}
我们注意到默认是升序。我们能把它们按降序排列吗?是的,需要使用模仿功能。
int main()
{
向量int v;
五. push _ back(1);
五. push _ back(2);
五. push _ back(3);
五、push _ back(13);
v . push _ back(-1);
sort(v.begin(),v.end(),greater int());
for (int val : v)
{
cout val“”;
}
返回0;
}
看上面排序中的参数,我们发现传入的是仿函数的匿名对象,和前面的有点不一样。我们可以反过来理解模仿函数的具体用途,即作为判断是上升还是下降的条件。
为什么在这里传入对象,我们需要解释为什么传入对象而不是简单的传入类型。第一点是,我们可能必须自己定义变量,这意味着如果我们传入类型,我们需要在类型中重载一些运算符。而且,我们的比较方法是固定的。
结构货物
{
string _ name
双_价;
size _ t _ saleNum
//.
布尔运算符(常量商品g)常量
{
return _ price g. _ price
}
布尔运算符(常量商品g)常量
{
return _ price g. _ price
}
};
int main()
{
商品gds[4]={{ 苹果,2.1,1000},{ 香蕉,3.0,200},{ 橘子,2.2,300},{ 菠萝,1.5,50 } };
sort(gds,gds 4,greater Goods());
返回0;
}
但是这里的比较规则已经确定了,只能按价格来比较,那么如果要按销量来比较呢?这是一个问题。所以这里我们传入对象,我们可以将每个比较规则封装到一个类中,并实例化它。一般来说,小于就是小于,对应升序。
结构货物
{
string _ name
双_价;
size _ t _ saleNum
//.
};
无结构价格
{
布尔运算符()(常数商品g1,常数商品g2)常数
{
返回g1。_price g2。_价格;
}
};
构建更高的价格
{
布尔运算符()(常数商品g1,常数商品g2)常数
{
返回g1。_price g2。_价格;
}
};
struct LessSaleNum
{
布尔运算符()(常数商品g1,常数商品g2)常数
{
返回g1。_saleNum g2。_ saleNum
}
};
巨大结构
{
布尔运算符()(常数商品g1,常数商品g2)常数
{
返回g1。_saleNum g2。_ saleNum
}
};
int main()
{
商品gds[4]={{ 苹果,2.1,1000},{ 香蕉,3.0,200},{ 橘子,2.2,300},{ 菠萝,1.5,50 } };
cout ======================== endl;
for(int I=0;I 4;我)
{
cout gds[i]。_name price : gds[i]。_price saleNum: gds[i]._ saleNum endl
}
cout ======================== endl;
sort(gds,gds 4,LessSaleNum());
cout ======================== endl;
for(int I=0;I 4;我)
{
cout gds[i]。_name price : gds[i]。_price saleNum: gds[i]._ saleNum endl
}
cout ======================== endl;
sort(gds,gds 4,GreaterSaleNum());
cout ======================== endl;
for(int I=0;I 4;我)
{
cout gds[i]。_name price : gds[i]。_price saleNum: gds[i]._ saleNum endl
}
cout ======================== endl;
返回0;
}
理解德缺这里我们需要了解一下德缺。这也是数据结构,但是他没什么好讲的。今天我们只看他的底层结构。
我们知道,如果物理内存是连续的,那么CPU访问的速度是非常快的。对于列表,这是一个节点。我们可以这样想吗?假设我们把一个很小的碎片空间看成一个向量,几个很小的空间连成一个列表。这样会在一定程度上提高效率。
这里我们还需要一个数组来记录每个碎片空间的地址。
首先我说这个想法很好,但是实际效率很低。一般我们不这么做。
反向迭代器我们前面的列表只实现了正向迭代器。这里我们看到了适配器。这里我们可以看看反向迭代器,它是由正向迭代器改编而来的。
我们先来看看源代码。
我们知道方向迭代器的方向是不同的或者——其他都是一样的,所以C站在顶层模式看它们。我们将在这里实现反向迭代器。首先,我们将实现一个相当令人沮丧的版本,然后我们将与您讨论上述版本是如何实现的。
反向迭代器我们重点说一下解引用,这涉及到我们之前的list的设计原则。
模板类迭代器,类引用,类指针
结构反向迭代器
{
Iterator _ it
typedef Reverse _ Iterator Iterator,Ref,Ptr Self
反向迭代器(Iterator it)
:_它(它)
{}
引用运算符*()
{
迭代器tmp=_ it
return *(tmp);
}
Ptr操作员-()
{
return(运算符*);
}
Self运算符()
{
-_ it;
返回* this
}
自运算符-()
{
_ it
返回* this
}
布尔运算符!=(常数自我s)
{
return _it!=s. _ it
}
};正如我们前面谈到的,list的迭代器是以这种方式用end和begin设计的。
同样,这里也需要对称设计,所以对称的结果应该是这样的。当我们取消引用时,我们可以取消引用前一个节点的值。
reverse_iterator rbegin()
{
返回迭代器(end());
}
reverse _ iterator render()
{
返回迭代器(begin());
}
分析完反向迭代器,我们还是要再讲一下反向迭代器。虽然上面我们已经实现了,但是这里还有一个小问题。我们传入了三个模板参数,但是我看到标准库中只有一个。我觉得和它一样。让我们看看标准迭代器是如何实现的。
前向迭代器中需要存在一些东西,这样我们才能完成迭代器提取,
模板类迭代器
结构反向迭代器
{
Iterator _ it
typedef Reverse_iterator迭代器自身;
反向迭代器(Iterator it)
:_它(它)
{}
迭代器:引用运算符*()
{
迭代器tmp=_ it
return *(tmp);
}
迭代器:指针运算符-()
{
return(运算符*);
}
Self运算符()
{
-_ it;
返回* this
}
自运算符-()
{
_ it
返回* this
}
布尔运算符!=(常数自我s)
{
return _it!=s. _ it
}
};
要知道正向迭代器本质上是一个模板,我们在模板中寻找特定的参数,这是编译器不允许的。这里我们需要一个关键字。
我们之前见过关键字typename,就不说原始函数了。在这里,直接说明你为什么这么做,为什么能解决问题。首先,我们知道正向迭代器本来就是一个模板类。我们只能通过在模板类中查找类型来获得虚拟类型。关键字typename可以告诉编译器这是模板类中的类型。你需要等待一段时间,等到这个类型被实例化后,才能得到这个类型。
typename迭代器:引用运算符*()
{
迭代器tmp=_ it
return *(tmp);
}
typename迭代器:指针运算符-()
{
return(运算符*);
}
我们也可以在逆向类中重命名它,这样更简单。
typedef typename迭代器:reference Ref
typedef typename Iterator:pointer Ptr;
反向迭代器(Iterator it)
:_它(它)
{}
引用运算符*()
{
迭代器tmp=_ it
return *(tmp);
}
Ptr操作员-()
{
return(运算符*);
}
我们在这里讲这个关键词,主要是为了让大家更好的理解。至于迭代器,这里先说,后面再讲迭代器提取原理,更痛苦。
让我们先看看下面的代码,
#包括iostream
#包含列表
使用命名空间std
模板类T
作废清单_打印(清单T l)
{
list T:const _ iterator cit=l . c begin();
而(cit!=l.cend())
{
cout * cit“”;
cit
}
}
int main()
{
list int l;
l . push _ back(1);
l . push _ back(1);
l . push _ back(1);
l . push _ back(1);
l . push _ back(1);
l . push _ back(1);
list _ print(l);
列表字符串lstr
lstr . push _ back( QQ QQ QQ );
lstr . push _ back( QQ QQ QQ );
lstr . push _ back( QQ QQ QQ );
lstr . push _ back( QQ QQ QQ );
lstr . push _ back( QQ QQ QQ );
list _ print(lstr);
返回0;
}
这是因为列表是一个模板,我们去模板,它们的类型是编译器不允许的,所以我们需要在这里添加一个typename。
模板类T
作废清单_打印(清单T l)
{
typename list T:const _ iterator cit=l . c begin();
而(cit!=l.cend())
{
cout * cit“”;
cit
}
}
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。