C++STL -- adapter (stack&queue&&priority queue&&reverse iterator (with detailed notes))

1. What is an adapter?

In C++, an adapter is a design pattern that is used to convert the interface of one class into the interface of another class to meet the needs of interaction between incompatible interfaces. The adapter pattern can solve the problem of interface mismatch between different classes, so that they can work together.

The adapter pattern realizes the transformation of the interface by introducing an intermediate layer. This intermediate layer is the adapter, which encapsulates the interface of the original class (adapted) and provides a new interface for the target class to use. The adapter mode makes the target class not directly dependent on the original class, but indirectly accesses the function of the original class through the adapter.

Adapter mode can make existing classes work with other classes, improving code reusability and flexibility. It is often used to extend or perform compatibility processing on the interface of an existing class library, so as to realize the adaptation of the interface without changing the original code.

Simply put, an adapter is a repackage of an existing class, or more directly, a repackage of the interface of an existing class to obtain the class we want and the interface it needs to meet our needs.

The stack, queue, priority queue, and reverse iterator in the C++STL standard library are adapted using the adapter pattern.

Two, stack

How to use the adapter to simulate and realize the stack container in C++STL? We know through the previous knowledge. The stack is a data structure with last-in-first-out (LIFO) characteristics. The bottom layer of the stack can be an array or a linked list, that is, an array stack and a linked stack.
Because the adapter is obtained by encapsulating the existing container, and our C++ STL library just has vector and list containers, so of course we can use vector or list to simulate a stack container. Here I will Implement an array stack with vector simulation.

namespace kb
{
    
    
	//适配器需要传一个已有的容器作为模板参数
	//容器的缺省参数是deque,deque是双端队列
	//整体结构是一个链表结构,但是每一个节点
	//存放的是一个数组的指针,并且指针是从中间
	//节点开始存的,所以头插头删,尾插尾删效率
	//都挺高,并且因为每个节点都是一个数组,也
	//不用频繁地开空间,既避免了vector头插头删
	//的效率低,也避免了list的频繁开空间,是一个
	//不错的作为stack和list默认容器的缺省值
	template <class T, class Container = deque<T>>
	class stack
	{
    
    
	public:
		//对于栈来说,push一般是调用数组的尾插实现的,虽然可以反过来设计,push调用
		//头插,但是数组的头插效率太低,并且vector本身并没有提供头插的接口,所以push
		// 用push_back,因为成员变量本身就是vector,所以直接调用vector的push_back
		//函数即可
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}

		//同样地,栈的pop就是从尾部删除一个数据即可,调用vector函数的pop_back接口
		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;
	};

	void test_stack(void)
	{
    
    
		//可以显式地传vector<int>容器实现栈
		stack<int,vector<int>> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);
		st.push(5);

		while (!st.empty())
		{
    
    
			cout << "top:" << st.top() << " size:" << st.size() << endl;
			st.pop();
		}
		cout << endl;
	}
}

Three, queue

So how to use the adapter to simulate and implement the queue container in C++STL? Through the previous knowledge we can know. Queue is a data structure with first-in-first-out (FIFO) characteristics. The bottom layer of queue generally uses deque double-ended queue by default. Therefore, the default parameters of the container we simulate and implement can use deque double-ended queue. An end queue is a linear data structure that allows efficient insertion and deletion operations on both ends. It does this by using a contiguous block of storage and a set of pointers indicating the location of the head and tail.

namespace kb
{
    
    
	template<class T, class Container = deque<T>>
	class queue
	{
    
    
	public:
		//队列的插入就是尾插,因为queue要符合先进先出的结构特性
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}
		//pop就是头插,先进先出
		void pop()
		{
    
    
			_con.pop_front();
		}
		//队头元素就是第一个元素
		T& front()
		{
    
    
			return _con.front();
		}
		const T& front() const
		{
    
    
			return _con.front();
		}
		//队为元素就是最后一个元素
		T& back()
		{
    
    
			return _con.back();
		}
		const T& back() const
		{
    
    
			return _con.back();
		}
		//队列元素个数
		size_t size() const
		{
    
    
			return _con.size();
		}
		//判空
		bool empty() const
		{
    
    
			return _con.empty();
		}
	private:
		Container _con;
	};

	void test_queue(void)
	{
    
    
		queue<int> q;
		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);
		q.push(5);

		while (!q.empty())
		{
    
    
			cout << "front:" << q.front() << " size:" << q.size() << endl;
			q.pop();
		}
		cout << endl;
	}
}

4. Priority queue

4.1 What is a priority queue?

Priority Queue (Priority Queue) is a special queue data structure, which can ensure that the dequeue order of elements is based on priority, that is, the element with the highest priority is dequeued first.

In C++, std::priority_queue is a container adapter provided by the standard library to implement a priority queue. It is implemented based on the heap data structure, and the commonly used heap implementation method is the binary heap (Binary Heap).

A binary heap is a complete binary tree in which the value of each node is greater (or less than) the value of its children (max-heap or min-heap). In std::priority_queue, the maximum heap is used by default, that is, the element with the highest priority is at the head of the queue.

It should be noted that by default, std::priority_queue makes larger elements have higher priority. If you need to change the comparison method, you can customize the Functor or use a lambda expression as the template parameter of std::priority_queue.

Because the underlying data structure of priority_queue is a heap, and the operation of the heap requires a large amount of random access data, upward adjustment, downward adjustment, etc., so the default container is vector.

Since it is a priority queue, it must be compared, and the data type in the container is uncertain. If the comparison is hard-coded and directly compared according to the value, it is not suitable for some types, such as passing The incoming parameter is an address, so it is obviously not possible to directly compare the size, but to dereference to obtain the corresponding effective value comparison, so this time the functor comes on the stage, and the functor can customize the relevant comparison rules for different types. Comparison, so what is the sacred function of the functor, and how to do the above operations?

4.2 Functors

In C++, a functor is an object that behaves like a function, can be called like a function, and can also be copied, assigned, and passed like a normal object.

Functors can implement the function call operator ( operator() ), which allows them to be called like functions, accepting arguments and returning a result. This method of calling the operator by overloading the function makes the object have the characteristics of being callable like a function.

Here is an example of a functor:

//带模板参数的仿函数类
template <class T>
struct Greater
{
    
    
    bool operator()(const T& x1, const T& x2)
    {
    
    
        return x1 < x2;
    }
};

int main()
{
    
    
    //用仿函数类定义出一个仿函数类对象,该对象能够像函数一样调用
    Greater<int> Gt;
    cout << Gt(3, 5) << endl;
    return 0;
}

However, for some special data, it is not possible to directly compare greater than less than, such as pointers. The content we want to compare is not the pointer itself, but the content pointed to by the pointer. At this time, we need to modify the operator() of the functor The comparison rules of , and compare according to the comparison method we want.
For example:

template <class T>
struct Greater
{
    
    
    bool operator()(const T& x1, const T& x2)
    {
    
    
        //x1和x2是地址,需要解引用再比较大小
        return *x1 < *x2;
    }
};

int main() 
{
    
    
    Greater<int*> Gt;
    int* b = new int(5);
    int* a = new int(3);
    cout << Gt(a, b) << endl;
    return 0;
}

For custom types like date classes, we can also control the comparison rules in the functor operator() to achieve the comparison results we want. In this way, using the functor in the priority_queue queue can also adapt to the comparison of various data types. You only need to modify the comparison rules to implement the functor. In the subsequent code, you only need to use the functor object to complete the comparison.

4.3 Priority queue code

#pragma once

namespace kb
{
    
    
	//仿函数类模板
	template<class T>
	struct Less
	{
    
    
		//重载operator(),实例化出来的仿函数对象可以调用operator()
		//完成对数据的比较逻辑,前提是元素要支持大于小于比较,如果不支持,
		// 则需要我们自己重载元素比较的运算符,如果直接的大于小于比较不
		//符合我们的需要,就需要修改比较规则,例如指针的直接比较不符合
		// 我们的需求,就先解引用,再比较,总之,仿函数operator()的
		//比较规则可以按照自己的需求定义,很灵活
		bool operator()(const T& x1, const T& x2)
		{
    
    
			return x1 > x2;
		}
	};

	//仿函数类模板
	template<class T>
	struct Greater
	{
    
    
		bool operator()(const T& x1, const T& x2)
		{
    
    
			return x1 < x2;
		}
	};

	//对于不符合需求的比较,指针比较需要先解引用,再比较
	//struct LessDate
	//{
    
    
	//	bool operator()(const Date* x1, const Date* x2)
	//	{
    
    
	//		return *x1 > *x2;
	//	}
	//};

	//传递的第三个参数就是仿函数的类模板,C++库里面优先级队列的默认仿函数是Less,
	//但是大的元素的优先级更高,恰恰和仿函数的less含义相反,所以这里是需要记忆的
	template <class T,class Container=vector<T>,class Compare=Less<T>>
	class priority_queue
	{
    
    
	private:
		//向上调整(堆的知识)
		void AdjustUp(int child)
		{
    
    
			//利用模板参数传过来的仿函数类模板实例化出仿函数对象
			//可以像函数一样使用这个对象完成对两个数据的比较
			Compare cmp;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
    
    
				//cmp像函数一样使用,本质是仿函数对象(可调用对象)
				if (cmp(_con[child], _con[parent]) > 0)
				{
    
    
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
    
    
					break;
				}
			}
		}

		//向下调整(堆的知识)
		void AdjustDown(int parent)
		{
    
    
			Compare cmp;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
    
    
				if (child + 1 < _con.size() && cmp(_con[child + 1], _con[child]) > 0)
				{
    
    
					child++;
				}
				if (cmp(_con[child], _con[parent]) > 0)
				{
    
    
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
    
    
					break;
				}
			}
		}
	public:

		//需要注意的是,提供了这个用迭代器区间初始化的构造函数就必须要提供一个
		//无参的构造函数作为默认构造函数,因为自己写了构造函数编译器就不会自动
		//生成默认构造函数了
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
    
    
			//为了提高效率,可以先把数据全部尾插到容器中,容器可以看成是
			//vector,支持[]访问元素,然后再对数组的元素进行建堆
			while (first != last)
			{
    
    
				_con.push_back(*first);
				++first;
			}
			//对数据的元素进行建堆,从最后一个非叶子节点开始往上做向下调整
			for (int i = (size() - 1 - 1) / 2; i >= 0; i--)
			{
    
    
				AdjustDown(i);
			}
		}

		//必须提供无参的构造函数
		priority_queue()
		{
    
    }

		void push(const T& x)
		{
    
    
			//对于堆来说,插入数据就是先尾插到数组的尾部,
			_con.push_back(x);
			//再堆这个新插入的数据做一遍向上调整即可
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
    
    
			//pop的目的是删除top的数,即删除第一个数
			//先把第一个数和最后一个数交换
			swap(_con[0], _con[_con.size() - 1]);
			//删除最后一个数,本质就是删除了堆顶top的数
			_con.pop_back();
			//再对交换到堆顶的元素做一遍向下调整即可
			AdjustDown(0);
		}

		const T& top()
		{
    
    
			//堆顶元素
			return _con[0];
		}

		//判空
		bool empty() const
		{
    
    
			return _con.empty();
		}

		//计算队列的数据个数
		size_t size() const
		{
    
    
			return _con.size();
		}
	private:

		//优先级队列默认的容器是vector
		Container _con;
	};

	void test_PriorityQueue1(void)
	{
    
    
		std::vector<int> v = {
    
     1,2,3,4,5 };
		priority_queue<int,vector<int>,Greater<int>> pq(v.begin(), v.end());
		/*pq.push(1);
		pq.push(2);
		pq.push(3);
		pq.push(4);
		pq.push(5);*/

		while (!pq.empty())
		{
    
    
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;

	}

}

Five, reverse iterator

I believe that we are very familiar with the iterator by now, it provides a unified way for us to access the elements of the container without knowing the underlying implementation of the container. Of course, the reverse iterator, as the name suggests, is an iterator that goes backwards. The ++ of the forward iterator is the – of the reverse iterator, and the – of the forward iterator is the ++ of the reverse iterator. Those that can support forward iterators generally need to support reverse iterators, including vector, list, etc., so reverse iterators are also a kind of adapter, that is, pass forward iterators of other containers as template parameters, through Encapsulate to get the corresponding reverse iterator.

insert image description here

namespace kb
{
    
    
	//为什么要传Ref和Ptr过来,因为*的返回值是类型的引用,
	// ->的返回值是指针,跟正向迭代器的返回值是一样的
	template <class Iterator,class Ref,class Ptr>
	struct ReverseIterator
	{
    
    
	private:
		//反向迭代器的成员变量就是一个容器的正向迭代器,本质就是对正向迭代器进行封装
		Iterator _it;
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
	public:

		//构造函数
		ReverseIterator(const Iterator& it)
			:_it(it)
		{
    
    }

		Ref operator*()
		{
    
    
			//曾经把 Iterator tmp(_it) 写成 Self tmp(_it) 造成死循环
			//因为解引用是不能改变指针的指向的,而根据反向迭代器的设计规则
			//需要先--再解引用,所以需要创建一个临时的迭代器,使其先--,再
			//返回值解引用之后的值
			Iterator tmp(_it);
			return *(--tmp);
		}

		Ptr operator->()
		{
    
    
			//operator->就复用operator*()即可
			return &(operator*());
		}

		Self& operator++()
		{
    
    
			//反向迭代器的++等于正向迭代器的--
			--_it;
			return *this;
		}

		Self& operator--()
		{
    
    
			//反向迭代器的--等于正向迭代器的++
			++_it;
			return *this;
		}

		bool operator!=(const Self& it)
		{
    
    
			return _it != it._it;
		}

		bool operator==(const Self& it)
		{
    
    
			return _it == it._it;
		}

	};
}

The above is what I want to share with you today, have you learned it? If you feel that you have gained something, then please like and follow. We will continue to update the relevant content of C++ in the future. See you in the next issue! ! ! ! ! ! ! !

Guess you like

Origin blog.csdn.net/weixin_70056514/article/details/131837762