One of the first articles of C++ teaches you stack (understanding usage and simulation implementation)

insert image description here

what is stack

insert image description here

  1. stackIt is a container adapter specially used in contexts with last-in-first-out operations, and its deletion can only insert and extract elements from one end of the container.
  2. stackIt is implemented as a container adapter, which encapsulates a specific class as its underlying container and provides a set of specific
    member functions to access its elements, using a specific class as its underlying, element-specific container tail (ie top of the stack) is pushed and popped.
  3. stackThe underlying container of can be any standard container class template or some other specific container class, and these container classes should support the following
    operations:

empty : Null operation
back : Get tail element operation
push_back : Tail insert element operation
pop_back : Tail delete element operation

  1. The standard containers vector、deque、listall conform to these requirements and are used by default if no stackspecific underlying container is specified for deque.

insert image description here

The use of stack

1. stack constructor

insert image description here
These are the constructor declarations for the std::stack class in the C++ standard library. std::stack is an adapter container that can use different underlying containers to implement the function of a stack. These constructor declarations provide different ways to create and initialize std::stack objects, and you can choose the appropriate constructor according to your needs.

The various constructor options here include different initialization methods, move-initialization, using different allocators (allocators), combining different container types, etc. You can choose the appropriate constructor to create std::stack objects according to your needs.

The use of these constructors is similar to that of ordinary classes. You can use these constructors to instantiate std::stack objects and provide them with appropriate parameters, such as underlying containers, allocators, and so on.

You can use one of the following constructors:

initialize (1):

std::stack<int> myStack1; // 使用默认构造函数,默认底层容器是 std::deque

move-initialize (2):

std::stack<int> sourceStack;
sourceStack.push(1);
sourceStack.push(2);
sourceStack.push(3);

std::stack<int> myStack2(std::move(sourceStack)); // 使用移动构造函数,sourceStack中的元素移动到myStack2

allocator (3):

std::allocator<int> myAllocator;
std::stack<int, std::vector<int, std::allocator<int>>> myStack3(myAllocator); // 使用指定的分配器

init + allocator (4):

std::vector<int> vec4 = {
    
    1, 2, 3, 4, 5};
std::allocator<int> myAllocator4;
std::stack<int, std::vector<int, std::allocator<int>>> myStack4(vec4, myAllocator4); // 使用指定的容器和分配器

move-init + allocator (5):

std::vector<int> vec5 = {
    
    5, 4, 3, 2, 1};
std::allocator<int> myAllocator5;
std::stack<int, std::vector<int, std::allocator<int>>> myStack5(std::move(vec5), myAllocator5); // 使用移动构造函数,同时指定容器和分配器

copy + allocator (6):

std::stack<int> sourceStack6;
sourceStack6.push(1);
sourceStack6.push(2);
sourceStack6.push(3);

std::allocator<int> myAllocator6;
std::stack<int, std::vector<int, std::allocator<int>>> myStack6(sourceStack6, myAllocator6); // 复制构造函数,同时指定分配器

move + allocator (7):

std::stack<int> sourceStack7;
sourceStack7.push(7);
sourceStack7.push(8);
sourceStack7.push(9);

std::allocator<int> myAllocator7;
std::stack<int, std::vector<int, std::allocator<int>>> myStack7(std::move(sourceStack7), myAllocator7); // 使用移动构造函数,同时指定分配器

These examples initialize objects differently, std::stackincluding using different container types and allocators, based on different constructor variants. The exact usage scenarios will vary based on your coding needs.

2.empty()

bool empty() const; Is std::stackone of the member functions of the class in the C++ standard library. It is used to determine whether the stack is empty.

Return type : bool, indicating whether the stack is empty, if the stack is empty, return true, otherwise return false.
constThe keyword indicates that this function will not modify the member variables of the class.

You can call this function to check whether the stack is empty in actual use, so as to perform corresponding operations. Here is an example:

#include <iostream>
#include <stack>

int main() {
    
    
    std::stack<int> myStack;

    if (myStack.empty()) {
    
    
        std::cout << "Stack is empty." << std::endl;
    } else {
    
    
        std::cout << "Stack is not empty." << std::endl;
    }

    myStack.push(42);

    if (myStack.empty()) {
    
    
        std::cout << "Stack is empty." << std::endl;
    } else {
    
    
        std::cout << "Stack is not empty." << std::endl;
    }

    return 0;
}

In the above example, we first create an empty stack myStack, and then use empty()the function to check whether the stack is empty. Output if the stack is empty "Stack is empty.", otherwise output "Stack is not empty.". After that, we push an element onto the stack and check the state of the stack again. The output should be "Stack is not empty.".

3.size()

size_type size() const;Is std::stackone of the member functions of the class in the C++ standard library. It is used to return the number (size) of elements in the current stack.

Return type : size_type, usually an unsigned integer type, representing the number of elements in the stack.
constThe keyword indicates that this function will not modify the member variables of the class.

You can get the number of elements in the stack by calling this function in actual use. Here is an example:

#include <iostream>
#include <stack>

int main() {
    
    
    std::stack<int> myStack;

    std::cout << "Initial size: " << myStack.size() << std::endl;

    myStack.push(42);
    myStack.push(15);
    myStack.push(7);

    std::cout << "Size after pushing elements: " << myStack.size() << std::endl;

    myStack.pop();

    std::cout << "Size after popping an element: " << myStack.size() << std::endl;

    return 0;
}

In the above example, we first create an empty stack myStack, and then use size()the function to get the size of the stack. After that, we push three elements onto the stack and use size()the function again to get the size of the stack. Next, we pop an element and use size()the function again to get the size of the stack. The output should be the size of the stack after different operations.

3.top()

reference top();and const_reference top() const;is one of the member functions of the class in the C++ standard library std::stack. They are used to get a reference to the top element of the stack.

reference top();: Returns a reference to the top element of the stack. If you need to modify the top element of the stack, you can use this version.
const_reference top() const;: Returns a constant reference to the top element of the stack. Use this version if you don't need to modify the top element of the stack, but just need to read it.
In these two versions, constthe keyword means that the member variables of the class will not be modified inside the function, ensuring that top()no accidental modification will occur during the use of the function.

Here is an example:

#include <iostream>
#include <stack>

int main() {
    
    
    std::stack<int> myStack;

    myStack.push(42);
    myStack.push(15);

    // 使用 top() 获取栈顶元素
    int topElement = myStack.top();
    std::cout << "Top element: " << topElement << std::endl;

    // 修改栈顶元素
    myStack.top() = 99;
    std::cout << "New top element: " << myStack.top() << std::endl;

    return 0;
}

In the above example, we first push two elements onto the stack, then use top()the function to get the top element of the stack, and output it. Next, we use top()the function to modify the value of the top element of the stack, and then use top()the function again to get the new top element of the stack and output it. Note that in order to modify the top element of the stack, we used top()the first version of the function.

4.push

void push(const value_type& val);and void push(value_type&& val);is one of the member functions of the class in the C++ standard library std::stack. They are used to push a new element onto the stack.

void push(const value_type& val);: Pushes a const reference to the passed in value onto const value_type&the stack. This version is used to pass the value of a const reference and does not modify the value passed in.
void push(value_type&& val);: Pushes an rvalue reference to the passed in value onto value_type&&the stack. This version is used to pass the value of an rvalue reference, usually with move semantics, which may modify the value passed in.

Both versions of the push function allow you to add new elements to the top of the stack. You can use the first version if you need to preserve the immutability of the passed in values; you can use the second version if you want to take advantage of move semantics to avoid unnecessary copies.

Here is an example:

#include <iostream>
#include <stack>

int main() {
    
    
    std::stack<int> myStack;

    myStack.push(42); // 使用右值,将 42 压入栈中
    myStack.push(15);

    int newElement = 99;
    myStack.push(newElement); // 使用常量引用,将 newElement 压入栈中

    std::cout << "Stack size: " << myStack.size() << std::endl;
    while (!myStack.empty()) {
    
    
        std::cout << myStack.top() << " "; // 输出栈顶元素
        myStack.pop(); // 弹出栈顶元素
    }

    return 0;
}

In the above example, we use different versions of pushthe function to push elements onto the stack, including using rvalues 42​​and const references newElement. Then, we loop out the elements in the stack and pop the top element to clear the stack. The final output should be the value of the elements in the stack.

5.place

template <class... Args> void emplace(Args&&... args);Is std::stackone of the member functions of the class in the C++ standard library. It is used to construct a new element at the top of the stack, using the passed parameters to construct.

Args: Variable template parameter, used to pass the list of parameters required to construct the element.
args: A parameter pack for perfect forwarding, used to construct elements.
This function takes advantage of perfect forwarding, which can properly forward the incoming parameter pack argswhen constructing new elements, so that different constructors can be used to create elements.

Here is an example:

#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;
}

In the above example, we first defined a MyClassclass called that has a constructor that takes an integer parameter. We then use emplacethe function to construct the object by passing different integer values ​​as arguments to the constructor MyClass. Finally, we output valuethe members of the elements in the stack and pop the top element to clear the stack.

6.pop()

void pop();Is std::stackone of the member functions of the class in the C++ standard library. It is used to pop (delete) the top element of the stack.

This function has no return value, it just removes the top element from the stack. Before calling pop()the function, you need to ensure that the stack is not empty, otherwise it will cause undefined behavior.

Here is an example:

#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;
}

In the above example, we first push three elements onto the stack, and then output the size of the stack. Next, we call pop()the function to pop a top element of the stack, and output the size of the stack again. The output should be the size of the stack after popping elements.

7.swap

void swap(stack& x) noexcept(/*see below*/);Is std::stackone of the member functions of the class in the C++ standard library. It is used to exchange the contents of two stacks, so that the elements in the two stacks are interchanged.

x: A reference to another stack object to exchange content with the current stack object.
This function has no return value, it will exchange the contents of the current stack object and the passed stack object x. It should be noted that the exchange operation does not change the container type or allocator of the stack, but only exchanges the elements in the two stacks.

About noexcept: noexceptis an exception specification specifier used to indicate that a function will not throw an exception. In the declaration of this function, noexceptthere may be some conditions behind, indicating whether the function may throw an exception under certain circumstances. If there is no specific condition, it can be simply written as noexcept, indicating that the function will not throw an exception.

Here is an example:

#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;
}

In the above example, we first created two stack objects stack1and stack2, and then added some elements to each of them. Then, we swapswapped the contents of the two stacks using the function. After the swap, the output should show how the elements in the stack changed before and after the swap.

std::stackThe swapfunction can swap between different container types, but there are some limitations to be aware of.

std::stackThe function of the swapunderlying container will call swapthe function of its underlying container to realize the exchange of elements. Therefore, if the underlying container types differ, their swapfunctions must also be compatible. Usually, the containers in the C++ standard library provide swapfunctions that support the exchange between different container types, but you need to pay attention when using custom containers or third-party container libraries.

In summary, in the standard case, if you are using a container in the standard library (such as std::vector、std::deque、std::listetc.), it is safe to call functions std::stackof different container types. swapbut,If you use custom container types, you need to ensure that the swap function of these containers supports swapping to other container types.

Simulate the preparation of the stack

1. What is a container adapter?

Although stackHezhong queuecan also store elements, STLit is not divided into the ranks of containers in Hezhong, but it is called a container adapter . This is because stackHe Queue only wraps the interface of other containers, STLand Zhonghe uses it by default stack.queuedeque

Container Adapters are a data structure provided by the C++ standard library. Based on existing container types, they provide specific interfaces and functions to more easily implement certain specific data structures and algorithms .Container adapters are essentially an encapsulation of the underlying container, providing different data access methods and making them suitable for specific purposes.

Three commonly used container adapters are provided in the standard library:

stack: The stack adapter provides operations on the stack data structure based on the underlying container, such as push (push), pop (pop), and view the top element of the stack. The default underlying container is std::deque, but other containers that support back()and push_back()operations can also be used.
queue: The queue adapter provides operations on the queue data structure based on the underlying container, such as enqueue (push), dequeue (pop), and view the first element of the queue. The default underlying container is std::deque, but other containers that support back()and push_back()operations can also be used.
priority_queue: The priority queue adapter provides the operation of the priority queue data structure based on the underlying container, and supports sorting according to the priority when inserting elements. The default underlying container is std::vector, but other containers that support random access and insertion operations can also be used.

insert image description here
insert image description here
insert image description here

2. A brief introduction to deque

deque(Double-ended queue): It is a data structure with double-opening "continuous" space. The meaning of double-opening is: insertion and deletion operations can be performed at both ends of the head and tail, and the time complexity is, compared with, head O(1)insertion vectorefficiency High, there is no need to move elements; compared with list, the space utilization rate is relatively high.
insert image description here
dequeIt is not a real continuous space, but spliced ​​by a series of continuous small spaces. It is actually dequesimilar to a dynamic two-dimensional array. Its underlying structure is shown in the figure below: the bottom
insert image description here
layer of the double-ended queue is an illusion of continuous space. In fact, it is piecewise continuous. In order to maintain the illusion of "overall continuity" and random access, it falls on the dequeiterator. Therefore, dequethe design of the iterator is more complicated, as shown in the following figure:
insert image description here
how does deque use its iterator What about maintaining its supposedly continuous structure?
insert image description here

3. The defect of deque

Compared with vector, the advantage of deque is that when the head is inserted and deleted, there is no need to move elements, and the efficiency is particularly high, and when expanding, there is no need to move a large number of elements, so its efficiency must be vectorhigh.

Compared with list , its bottom layer is a continuous space, the space utilization rate is relatively high, and no additional fields need to be stored. However, dequethere is a fatal flaw: it is not suitable for traversal, because when traversing, dequethe iterator needs to frequently check whether it has moved to the boundary of a small space, resulting in low efficiency. In sequential scenarios, frequent traversal may be required. Therefore, in practice, when a linear structure is required, the sum is given priority in most cases , vectorand there are not many applications. One application that can be seen so far is to use it as the underlying data structure of the sum.listdequeSTLstackqueue

4. Why do stack and queue in STL use deque by default?

The reason for using stackand by default in the STL (Standard Template Library) as the underlying container is for performance and functionality.queuestd::deque

std::deque(Double-ended queue) is a dynamic array with two-way openings that supports efficient insertion and deletion operations at the head and tail of the queue. Its internal implementation enables operations at the head and tail of the queue to achieve near-constant time complexity, which enables it std::dequeto provide better performance as the underlying container.

For stackand queue, they usually need to insert and delete elements at the top of the stack or at the end of the queue, and std::dequeprovide this efficient capability. In addition, std::dequeit also supports access to the top of the stack or the first element of the queue in constant time, which is very useful when implementing common operations on stacks and queues.

While stackand queueare used by default std::deque, you can choose to achieve different performance and behavior by specifying other container types as the underlying container. This is achieved through template parameters. For example, you can use or std::vectoras the underlying container , but you need to be aware that inserting elements at the head may cause a large time complexity.stackqueuestd::vector

In short, std::dequeas the default underlying container of stackand queue, it has good performance and functions, combines the advantages of deque, perfectly avoids its defects, and can meet the needs in most cases. If you have specific needs, you can choose other underlying containers for customization.

Simulation implementation 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;
	};
}

The following is an analysis of the code implementation process of this simulation stack:

Header file inclusion : The code includes the header file first <deque>, in order to use the underlying container std::deque.

Namespace definition : The code stackplaces classes under a namespace xzqto avoid naming conflicts and provide an organizational structure for the code.

Stack class template definition : stackclass is a template class with two template parameters: Tindicating the type of elements stored in the stack, Containerindicating the type of the underlying container, the default is std::deque<T>.

Public member functions :

push(const T& x): Add the incoming element value x to the end of the underlying container, realizing the push operation.
pop(): Delete an element from the end of the underlying container, realizing the stack operation.
T& top()and const T& top() const: Return the reference (modification allowed) and constant reference (read-only) of the end element of the underlying container respectively, realizing the operation of viewing the top element of the stack.
bool empty() const: Returns whether the underlying container is empty.
size_t size() const: Returns the number of elements in the underlying container.
Private member variable _con: This is a private member variable of the template class, which is used to store the actual stack elements. Container Its type is determined according to the template parameters and will be replaced with a specific container type when instantiated.

epilogue

Interested friends can pay attention to the author, if you think the content is good, please give a one-click triple link, you crab crab! ! !
It is not easy to make, please point out if there are any inaccuracies
Thank you for your visit, UU watching is the motivation for me to persevere.
With the catalyst of time, let us all become better people from each other! ! !

Guess you like

Origin blog.csdn.net/kingxzq/article/details/132253070