5.emplace
template <class... Args> void emplace(Args&&... args);
是 C++ 标准库中 std::stack
类的成员函数之一。它用于在栈顶处构造一个新的元素,使用传递的参数来进行构造。
Args
:可变模板参数,用于传递构造元素所需的参数列表。
args
:完美转发(perfect forwarding)的参数包,用于构造元素。
这个函数利用了完美转发,可以将传入的参数包 args
在构造新元素时进行适当的转发,从而可以使用不同的构造函数来创建元素。
以下是一个示例:
#include <iostream> #include <stack> class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "Constructor with int: " << value << std::endl; } }; int main() { std::stack<MyClass> myStack; myStack.emplace(42); // 使用传入的参数构造 MyClass 对象 myStack.emplace(15); std::cout << "Stack size: " << myStack.size() << std::endl; while (!myStack.empty()) { std::cout << myStack.top().value << " "; // 输出栈顶元素的 value myStack.pop(); // 弹出栈顶元素 } return 0; }
在上述示例中,我们首先定义了一个名为 MyClass
的类,该类具有一个带有整数参数的构造函数。然后,我们使用 emplace
函数将不同的整数值作为参数传递给构造函数,以构造 MyClass
对象。最终,我们输出栈中元素的 value
成员,并弹出栈顶元素,以清空栈。
6.pop()
void pop();
是 C++ 标准库中 std::stack
类的成员函数之一。它用于将栈顶元素弹出(删除)。
这个函数没有返回值,它只是从栈中移除栈顶元素。在调用 pop()
函数之前,需要确保栈不为空,否则会导致未定义行为。
以下是一个示例:
#include <iostream> #include <stack> int main() { std::stack<int> myStack; myStack.push(42); myStack.push(15); myStack.push(7); std::cout << "Stack size before popping: " << myStack.size() << std::endl; myStack.pop(); // 弹出栈顶元素 std::cout << "Stack size after popping: " << myStack.size() << std::endl; return 0; }
在上述示例中,我们首先将三个元素压入栈中,然后输出栈的大小。接着,我们调用 pop()
函数弹出一个栈顶元素,并再次输出栈的大小。输出应该是弹出元素后的栈大小。
7.swap
void swap(stack& x) noexcept(/*see below*/);
是 C++ 标准库中 std::stack
类的成员函数之一。它用于交换两个栈的内容,使得两个栈中的元素互换。
x
:另一个栈对象的引用,与当前栈对象交换内容。
这个函数没有返回值,它会交换当前栈对象和传入的栈对象 x
的内容。需要注意的是,交换操作并不会改变栈的容器类型或分配器,只是交换两个栈中的元素。
关于 noexcept
:noexcept
是异常规范说明符,用于表示函数不会抛出异常。在这个函数的声明中,noexcept
后面可能会有一些条件,表示函数是否在某些情况下可能会抛出异常。如果没有特定的条件,可以简单地写成 noexcept
,表示该函数不会抛出异常。
以下是一个示例:
#include <iostream> #include <stack> int main() { std::stack<int> stack1; std::stack<int> stack2; stack1.push(1); stack1.push(2); stack1.push(3); stack2.push(4); stack2.push(5); std::cout << "Before swapping:" << std::endl; std::cout << "Stack 1: "; while (!stack1.empty()) { std::cout << stack1.top() << " "; stack1.pop(); } std::cout << std::endl; std::cout << "Stack 2: "; while (!stack2.empty()) { std::cout << stack2.top() << " "; stack2.pop(); } std::cout << std::endl; stack1.push(6); stack1.push(7); stack1.swap(stack2); // 交换栈的内容 std::cout << "After swapping:" << std::endl; std::cout << "Stack 1: "; while (!stack1.empty()) { std::cout << stack1.top() << " "; stack1.pop(); } std::cout << std::endl; std::cout << "Stack 2: "; while (!stack2.empty()) { std::cout << stack2.top() << " "; stack2.pop(); } std::cout << std::endl; return 0; }
在上述示例中,我们首先创建了两个栈对象 stack1
和 stack2
,然后向它们分别添加一些元素。然后,我们使用 swap
函数交换了这两个栈的内容。在交换后,输出应该显示交换前后栈中元素的变化。
std::stack
的 swap
函数可以在不同的容器类型之间进行交换,但是有一些限制需要注意。
std::stack
的 swap
函数内部会调用其底层容器的 swap
函数来实现元素的交换。因此,如果底层容器类型不同,它们的 swap
函数也必须是兼容的。通常情况下,C++ 标准库中的容器都会提供支持不同容器类型间交换的 swap
函数,但在使用自定义容器或第三方容器库时需要注意。
总之,在标准情况下,如果你使用的是标准库中的容器(如 std::vector、std::deque、std::list
等),那么在不同容器类型之间调用 std::stack
的 swap
函数是安全的。但是,如果使用自定义的容器类型,需要确保这些容器的 swap 函数支持交换到其他容器类型。
模拟实现stack的准备
1.什么是容器适配器?
虽然stack
和queue
中也可以存放元素,但在STL
中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack
和队列只是对其他容器的接口进行了包装,STL
中stack
和queue
默认使用deque
容器适配器(Container Adapters)是 C++ 标准库提供的一种数据结构,它们基于现有的容器类型,提供了特定的接口和功能,以便更方便地实现某些特定的数据结构和算法。容器适配器本质上是对底层容器的封装,提供了不同的数据访问方式,使它们适用于特定的用途。
标准库中提供了三种常用的容器适配器:
stack
:栈适配器,基于底层容器提供了栈数据结构的操作,如压入(push)、弹出(pop)、查看栈顶元素等。默认底层容器是std::deque
,但也可以使用其他支持back()
和push_back()
操作的容器。
queue
:队列适配器,基于底层容器提供了队列数据结构的操作,如入队(push)、出队(pop)、查看队首元素等。默认底层容器是std::deque
,但也可以使用其他支持back()
和push_back()
操作的容器。
priority_queue
:优先队列适配器,基于底层容器提供了优先队列数据结构的操作,支持在插入元素时根据优先级进行排序。默认底层容器是std::vector
,但也可以使用其他支持随机访问和插入操作的容器。
2. deque的简单介绍
deque
(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
,与vector
比较,头插效率高,不需要搬移元素;与list
比较,空间利用率比较高。
deque
并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque
类似于一个动态的二维数组,其底层结构如下图所示:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque
的迭代器身上,因此deque
的迭代器设计就比较复杂,如下图所示:
那deque是如何借助其迭代器维护其假想连续的结构呢?
3. deque的缺陷
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector
高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。但是,deque
有一个致命缺陷:不适合遍历,因为在遍历时,deque
的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector
和list
,deque
的应用并不多,而目前能看到的一个应用就是,STL
用其作为stack
和queue
的底层数据结构。
4. 为什么STL中stack和queue默认使用deque?
STL(标准模板库)中的 stack
和 queue
默认使用 std::deque
作为底层容器的原因是出于性能和功能的考虑。
std::deque
(双端队列)是一个双向开口的动态数组,支持在队首和队尾进行高效的插入和删除操作。它的内部实现使得在队首和队尾的操作都能达到接近常数时间复杂度,这使得 std::deque
在作为底层容器时能够提供较好的性能。
对于 stack
和 queue
,它们通常需要在栈顶或队尾进行元素的插入和删除操作,而 std::deque
提供了这种高效的能力。此外,std::deque
也支持在常数时间内对栈顶或队首元素进行访问,这在实现栈和队列的常见操作时非常有用。
虽然 stack
和 queue
默认使用 std::deque
,但你也可以选择通过指定其他容器类型作为底层容器来实现不同的性能和行为。这是通过模板参数来实现的。例如,你可以使用 std::vector
作为底层容器来实现 stack
或 queue
,但需要注意 std::vector
在头部插入元素时可能导致较大的时间复杂度。
总之,std::deque
作为 stack
和 queue
的默认底层容器,具备良好的性能和功能,结合了deque的优点,而完美的避开了其缺陷,能够满足大多数情况下的需求。如果你有特定的需求,可以选择其他底层容器来进行定制。
模拟实现stack
#pragma once #include <deque> namespace xzq { template<class T, class Container = deque<T>> class stack { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_back(); } T& top() { return _con.back(); } const T& top() const { return _con.back(); } bool empty() const { return _con.empty(); } size_t size() const { return _con.size(); } private: Container _con; }; }
以下是这个模拟栈的代码实现过程的分析:
头文件包含:代码首先包含了头文件 <deque>
,这是为了使用底层容器 std::deque
。
命名空间定义:代码将 stack
类放置在了命名空间 xzq
下,这是为了避免命名冲突和提供代码的组织结构。
stack 类模板定义:stack
类是一个模板类,有两个模板参数:T
表示栈中存储的元素类型,Container
表示底层容器的类型,默认为 std::deque<T>
。
公共成员函数:
push(const T& x)
:将传入的元素值 x 添加到底层容器的末尾,实现了入栈操作。
pop()
:从底层容器的末尾删除一个元素,实现了出栈操作。
T& top()
和const T& top() const
:分别返回底层容器的末尾元素的引用(允许修改)和常量引用(只读),实现了查看栈顶元素操作。
bool empty() const
:返回底层容器是否为空。
size_t size() const
:返回底层容器中元素的数量。私有成员变量
_con
:这是一个模板类的私有成员变量,用于存储实际的栈元素。其类型是根据模板参数Container
确定的,在实例化时会被替换为具体的容器类型。
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!