
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
对于一般人来说模拟实现的栈的底层就是一个数组,让数组尾部做栈顶。无论是数组栈还是链式栈,很多的东西都是和顺序表和链表是类似的
但是STL中的栈不是这样实现的,STL在设计初的考量就是相似的一些东西直接复用,像实现一个数组栈,直接如图封装一个vector实现,就不用再实现顺序表和链表的内部的一些逻辑了。

且C++设计STL的这些大佬的思路还更开阔一些,他们还加入了一种叫做容器适配器(Container,转换的意思)的东西


这样封装适配器的思路是非常牛的
“自己从头实现栈” 的写法(比如注释里的T* _a+_top+_capacity)—— 需要自己管理动态数组的扩容、释放;而现在这个stack是 “复用现有容器(比如vector/list)的功能,包装成栈的接口”,底层存储完全交给Container(比如vector),自己只封装 “栈需要的操作”(push/pop/top 等)。
这种适配结构的核心优点
vector/list作为底层容器 —— 这些容器本身已经实现了动态扩容、内存管理、尾插 / 尾删等功能,你只需要调用_con.push_back()/_con.pop_back(),相当于 “站在现有容器的肩膀上”,不用重复造轮子。vector< int >作为底层(yunze::stack<int, vector< int >>),但如果场景变了:yunze::stack<int, list<int>> st; // 底层变成链表,其他代码完全不用改
st.push(1); // 还是用同样的push接口
st.top(); // 还是用同样的top接口底层容器换了,但栈的调用代码完全不用改—— 因为stack封装了统一的接口,不管底层是vector还是list,对外都是 “栈的操作”。
push/pop/top—— 不用关心底层是 “数组尾插” 还是 “链表尾插”,也不用记vector的push_back或list的push_back细节,只需要记 “栈的标准操作” 即可,降低了学习和使用的成本。
vector/list是经过大量测试、优化的容器,比自己写的 “数组栈”(比如注释里的T* _a)更稳定:
yunze::stack<string, vector<string>> st;
st.push("hello");
cout << st.top() << endl; // 输出hello因为vector< string >本身支持string类型,你的stack不需要额外写任何代码,直接适配所有Container支持的类型。
看到这里可能看着该全新的写法还有一些疑问,下面再看一下具体的模板参数实例化和函数调用过程






库中的STL还支持使用缺省参数deque(一个容器),deque既不是一个数组栈,也不是一个链式栈,而是一个双端队列适配出来的栈,双端队列不要求先进先出,其在功能上是list和vector的融合体

队列是队尾入数据,队头出数据


也可以在queue的类中用erase代替pop_front();vector不支持直接头删,但是erase是支持头删的,但是这就让效率强行降低了,STL中的vector设计之初没有直接支持头删接口的原因就是希望少用(功能上还是支持的)
void pop()
{
_con.erase();
}deque(双端队列):是一种双开口的“连续”空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),也可以在中间任意位置插入删除,与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,其在功能上是list和vector的融合体,

接口和list和vector的接口都基本是类似的

仅管从功能上看deque好像可以替代vector和list,但是在实际情况下是不可以的,如果可以的话STL就没有vector和list两个容器了
deque更像是一个有着很好的设计初衷,为了解决vector和list的问题,但实际成品没有达到预期的一个容器,但是其也有很多可取之处 下图是vector和list的优缺点

CPU高速缓存访问命中率高间接的也就是说数据访问效率高,反之。该优缺点在归并排序和快速排序的性能测试尤其体现,这里的性能测试该篇文章有写C++ List 容器详解:迭代器失效、排序与高效操作

这里上面打错了,是100w个数据的数组。 排序的性能差异就是由于数据的访问效率低,排序的过程就要对数据结构中的数据进行大量的访问和交换,数据越多,这个差异就体现出来了
下面说一下CPU高速缓存访问命中率这个概念 一、计算机存储介质的层级特性 计算机的存储体系核心是 “速度越快→容量越小→成本越高” 的层级设计,从慢到快、从大到小分为:


CPU 执行指令的核心逻辑:为什么缓存是刚需
以代码for(auto& e : con) { e++; }为例,CPU 执行 “e++” 的本质是:
如果直接从内存读取 / 写入e,会因为内存速度远慢于 CPU 运算速度,导致 CPU 大部分时间处于 “等待数据” 的空闲状态(即 “CPU stall”)。因此 CPU 设计了缓存层级:

CPU 三级缓存的结构与访问规则 如四核 CPU 缓存架构图所示:
局部性原理:缓存设计的核心依据 CPU 缓存的批量加载逻辑,完全基于局部性原理(计算机程序的核心运行规律):
基于这一原理,CPU 加载 “当前数据 + 后续连续数据” 到缓存,能最大化 “缓存命中” 的概率 —— 这也是vector和list缓存命中率差异的核心根源。
五、缓存命中率的定义与核心影响 缓存命中率 = 缓存命中的访问次数 / 总数据访问次数 × 100%。
缓存污染 缓存容量是有限的,若加载了大量 “后续不会被访问” 的数据到缓存,会挤占 “有用数据” 的缓存空间,导致有用数据被挤出缓存→后续访问有用数据时触发 “未命中”,这种现象称为缓存污染。

vector<int> v = {1,2,3,4,5,...,20}为例(int 占 4 字节,20 个元素占 80 字节):0x100)时,触发 “缓存未命中”,CPU 从内存加载0x100~0x13F(64 字节,包含前 16 个元素:1~16)到 L1 缓存;v[1]=2~v[15]=16时,这些元素已在 L1 缓存中,100% 命中,直接读取;0x140~0x17F(64 字节,包含 17~20)到缓存,后续访问17~20仍为命中。整个遍历过程中,仅触发 2 次缓存未命中,其余 18 次均为命中,命中率≈90%;且 vector 无缓存污染(加载的缓存行全是有效元素)。
list<int> l = {1,2,3,4}为例:0x100):未命中,加载0x100~0x13F到缓存(但该缓存行中只有0x100是有效节点 1,其余 60 字节均为无用数据,缓存污染);0x200):该地址不在上一个缓存行中,触发未命中,加载0x200~0x23F到缓存(仅0x200是有效节点 2);且每次加载的缓存行中,有效数据仅占 4/64=6.25%,其余均为无用数据,严重污染缓存→挤占其他有用数据的缓存空间,进一步降低整体效率。

还有专门写该部分原理的博客程序员相关的CPU缓存知识
queue.h
#pragma once
#pragma once
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace yunze
{
template<class T, class Container = deque<T>>
class queue
{
public:
//队尾入数据
void push(const T& x)
{
//Container若不支持push_back就会报错,说明容器不适配
_con.push_back(x);
}
//队头出数据
void pop()
{
_con.pop_front();
}
//取队头数据
const T& front()
{
return _con.front();
}
//取队尾数据
const T& back()
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
//vector<T> _v;
Container _con;
};
}stack.h
#pragma once
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
namespace yunze
{
//template<class T>
//class stack //数组栈
//{
// // ...
//private:
// T* _a;
// size_t _top;
// size_t _capacity;
//};
template<class T, class Container = deque<T>>
class stack
{
public:
//构造析构拷贝不用写,没有其他需求,默认生成的够用
void push(const T& x)
{
//Container若不支持push_back就会报错,说明容器不适配
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
//top这里不用[],因为不一定是vector
const T& top()
{
//容器一般都有一个front和back接口,如vector和list
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
//vector<T> _v;
Container _con;
};
}test.cpp
#define _CRT_SECURE_NO_WARNINGS 666
#include"stack.h"
//int main()
//{
// //yunze::stack<int, vector<int>> st;//数组栈
// //yunze::stack<int, list<int>> st;//数组栈
// yunze::stack<int> st;
// st.push(1);
// st.push(2);
// st.push(3);
// st.push(4);
//
// while (!st.empty())
// {
// cout << st.top() << " ";
// st.pop();
// }
// cout << endl;
// return 0;
//}
#include"queue.h"
////默认也是用deque适配
//int main()
//{
// yunze::queue<int> q;
// //不能使用vector适配,vector不支持头删
// //yunze::queue<int, vector<int>> q;
//
// //队列只能使用list适配
// //yunze::queue<int, list<int>> q;
// q.push(1);
// q.push(2);
// q.push(3);
// q.push(4);
// while (!q.empty())
// {
// cout << q.front() << " ";
// q.pop();
// }
// cout << endl;
// return 0;
//}
#include<deque>
int main()
{
deque<int> dp;
dp.push_back(1);
dp.push_back(1);
dp.push_back(1);
dp.push_front(2);
dp.push_front(3);
dp.push_front(4);
dp[0] += 10;
for (auto e : dp)
{
cout << e << " ";
}
cout << endl;
return 0;
}