(写给未来遗忘的自己)
stack
在 C++ 中,stack
是标准模板库(STL)的一部分,是一种容器适配器。容器适配器为现有的容器提供了一种特定的接口,而不改变底层容器的实现方式。stack
是一个典型的后进先出(LIFO,Last In First Out)数据结构。
基本特性
- 容器适配器:
stack
是一个容器适配器,默认情况下使用deque
作为底层容器,但可以使用任何符合要求的容器,如vector
或list
。 - 接口:
stack
提供了一组有限的接口,以限制对底层容器的访问方式。这些接口包括push
、pop
、top
、empty
和size
。
stack
的常用方法
push(const T& value)
:向栈顶添加一个元素。pop()
:移除栈顶的元素。top()
:返回栈顶的元素。empty()
:检查栈是否为空。size()
:返回栈中元素的数量。
#include <iostream>
#include <stack>
#include <vector>
#include <list>
int main() {
std::stack<int> s; // 使用默认的 deque 作为底层容器
std::stack<int, std::vector<int>> s2; // 使用 vector 作为底层容器
std::stack<int, std::list<int>> s3; // 使用 list 作为底层容器
s.push(1);
s.push(2);
s.push(3);
while (!s.empty()) {
std::cout << s.top() << " "; // 输出栈顶元素
s.pop(); // 移除栈顶元素
}
/**************************************************************************************/
// top:返回栈顶元素
std::cout << "Top element: " << s.top() << std::endl; // 输出 3
// pop:移除栈顶元素
s.pop();
std::cout << "Top element after pop: " << s.top() << std::endl; // 输出 2
// size:返回栈中元素的数量
std::cout << "Stack size: " << s.size() << std::endl; // 输出 2
// empty:检查栈是否为空
std::cout << "Is stack empty? " << (s.empty() ? "Yes" : "No") << std::endl; // 输出 No
// 继续 pop 直到栈为空
s.pop();
s.pop();
std::cout << "Is stack empty after popping all elements? " << (s.empty() ? "Yes" : "No") << std::endl; // 输出 Yes
return 0;
}
stack
的实现
stack
是通过对底层容器的接口进行包装和限制来实现的。以下是一个简化的实现示例:
#include <deque>
template <typename T, typename Container = std::deque<T>>
class Stack {
public:
void push(const T& value) {
container.push_back(value);
}
void pop() {
container.pop_back();
}
T& top() {
return container.back();
}
const T& top() const {
return container.back();
}
bool empty() const {
return container.empty();
}
size_t size() const {
return container.size();
}
private:
Container container;
};
附:自定义模板的解释
template <typename T, typename Container = std::deque<T>>
/*
1.template <typename T, typename Container = std::deque<T>>:
template <typename T, typename Container = std::deque<T>>
声明了一个模板,其中 T 是类型参数,Container 是一个模板参数,
其默认类型是 std::deque<T>。这意味着在没有指定 Container 类型时,
默认使用 std::deque<T> 作为 Container 类型。
2.typename T:
T 是一个模板类型参数,表示可以传递任何类型。
3.typename Container = std::deque<T>:
Container 是另一个模板类型参数,它有一个默认值 std::deque<T>。
std::deque<T> 是一个标准库的双端队列(deque),它是 Container 的默认类型。
这样一来,如果用户在实例化模板时不指定 Container 类型,模板将使用
std::deque<T> 作为默认的容器类型。
*/
stack
不提供迭代器
stack
不提供迭代器,因为它的设计是为了隐藏底层容器的细节,并且只提供与栈操作相关的方法。迭代器允许对容器的任意访问,这与栈的 LIFO 特性不符。
如果需要遍历 stack
中的元素,应该使用底层容器。(一般不建议使用)
#include <stack>
#include <vector>
#include <iostream>
int main() {
std::stack<int, std::vector<int>> s;
s.push(1);
s.push(2);
s.push(3);
std::vector<int>& underlyingContainer = const_cast<std::vector<int>&>(s._Get_container());
for (auto it = underlyingContainer.begin(); it != underlyingContainer.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
queue
std::queue
也是 C++ 标准模板库(STL)中的一个容器适配器,用于实现先进先出(FIFO,First In First Out)数据结构。与 std::stack
类似,std::queue
也依赖于底层容器来存储元素,但它对外只暴露队列操作的接口。
queue常用方法
push(const T& value)
:向队列尾部添加元素。pop()
:移除队列头部的元素。front()
:返回队列头部的元素。back()
:返回队列尾部的元素。empty()
:检查队列是否为空。size()
:返回队列中元素的数量。
#include <iostream>
#include <queue>
#include <deque>
#include <list>
int main() {
// 使用默认的 std::deque 作为底层容器
std::queue<int> q;
// push:向队列尾部添加元素
q.push(1);
q.push(2);
q.push(3);
// front:返回队列头部的元素
std::cout << "Front element: " << q.front() << std::endl; // 输出 1
// back:返回队列尾部的元素
std::cout << "Back element: " << q.back() << std::endl; // 输出 3
// pop:移除队列头部的元素
q.pop();
std::cout << "Front element after pop: " << q.front() << std::endl; // 输出 2
// size:返回队列中元素的数量
std::cout << "Queue size: " << q.size() << std::endl; // 输出 2
// empty:检查队列是否为空
std::cout << "Is queue empty? " << (q.empty() ? "Yes" : "No") << std::endl; // 输出 No
return 0;
}
自定义底层容器
你可以使用 std::list
或 std::vector
作为 queue
的底层容器,只要它们支持必要的操作
#include <iostream>
#include <queue>
#include <list>
int main() {
std::queue<int, std::list<int>> q;
q.push(1);
q.push(2);
q.push(3);
std::cout << "Front element: " << q.front() << std::endl; // 输出 1
std::cout << "Back element: " << q.back() << std::endl; // 输出 3
q.pop();
std::cout << "Front element after pop: " << q.front() << std::endl; // 输出 2
std::cout << "Queue size: " << q.size() << std::endl; // 输出 2
std::cout << "Is queue empty? " << (q.empty() ? "Yes" : "No") << std::endl; // 输出 No
return 0;
}
queue
的实现
#include <iostream>
#include <queue>
#include <deque>
#include <list>
// 自定义 queue 类,公开底层容器的访问
template <typename T, typename Container = std::deque<T>>
class MyQueue {
public:
using value_type = T;
using container_type = Container;
using size_type = typename Container::size_type;
explicit MyQueue(const Container& cont = Container()) : c(cont) {}
explicit MyQueue(Container&& cont = Container()) : c(std::move(cont)) {}
MyQueue(const MyQueue& other) : c(other.c) {}
MyQueue(MyQueue&& other) noexcept : c(std::move(other.c)) {}
MyQueue& operator=(const MyQueue& other) {
c = other.c;
return *this;
}
MyQueue& operator=(MyQueue&& other) noexcept {
c = std::move(other.c);
return *this;
}
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
value_type& front() { return c.front(); }
const value_type& front() const { return c.front(); }
value_type& back() { return c.back(); }
const value_type& back() const { return c.back(); }
void push(const value_type& value) { c.push_back(value); }
void push(value_type&& value) { c.push_back(std::move(value)); }
void pop() { c.pop_front(); }
// 公开底层容器的访问
Container& get_container() { return c; }
const Container& get_container() const { return c; }
private:
Container c;
};
int main() {
// 使用自定义的 queue 类,底层容器为 std::deque
MyQueue<int> q;
q.push(1);
q.push(2);
q.push(3);
// 获取底层容器的引用
std::deque<int>& underlyingContainer = q.get_container();
// 遍历底层容器
for (const int& elem : underlyingContainer) {
std::cout << elem << " ";
}
return 0;
}
不同底层实现的区别
std::queue
和 std::stack
是容器适配器,它们可以使用不同的底层容器实现来存储元素。选择不同的底层容器会影响到这些适配器的性能和特性。主要的底层容器包括 std::deque
、std::vector
和 std::list
。
1. std::deque
std::deque
(双端队列)是 std::queue
和 std::stack
的默认底层容器,因为它提供了在头部和尾部的高效插入和删除操作。
-
优点:
- 支持在头部和尾部进行常数时间的插入和删除操作。
- 动态调整大小,不需要额外的内存拷贝。
- 支持随机访问元素。
-
缺点:
- 内部实现比较复杂,可能会占用更多的内存。
- 相比
std::vector
在元素访问和遍历上稍慢。
#include <iostream>
#include <queue>
#include <deque>
int main() {
std::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
std::cout << "Front element: " << q.front() << std::endl; // 输出 1
std::cout << "Back element: " << q.back() << std::endl; // 输出 3
q.pop();
std::cout << "Front element after pop: " << q.front() << std::endl; // 输出 2
return 0;
}
2. std::vector
std::vector
是动态数组,支持快速的随机访问。
-
优点:
- 提供常数时间的随机访问。
- 适合在尾部进行插入和删除操作。
- 内存布局连续,有利于缓存局部性。
-
缺点:
- 在头部插入和删除元素时效率较低,因为需要移动所有元素。
- 当重新分配内存时,可能需要将整个数组复制到新位置。
#include <iostream>
#include <stack>
#include <vector>
int main() {
std::stack<int, std::vector<int>> s;
s.push(1);
s.push(2);
s.push(3);
std::cout << "Top element: " << s.top() << std::endl; // 输出 3
s.pop();
std::cout << "Top element after pop: " << s.top() << std::endl; // 输出 2
return 0;
}
3. std::list
std::list
是一个双向链表。
-
优点:
- 支持在任何位置进行常数时间的插入和删除操作。
- 不需要移动其他元素。
-
缺点:
- 不支持随机访问,需要线性时间进行元素访问。
- 内存开销较大,因为每个元素都需要存储指向前后元素的指针。
- 相比
std::vector
和std::deque
,遍历速度较慢。
#include <iostream>
#include <queue>
#include <list>
int main() {
std::queue<int, std::list<int>> q;
q.push(1);
q.push(2);
q.push(3);
std::cout << "Front element: " << q.front() << std::endl; // 输出 1
std::cout << "Back element: " << q.back() << std::endl; // 输出 3
q.pop();
std::cout << "Front element after pop: " << q.front() << std::endl; // 输出 2
return 0;
}