1. stack
模拟实现
C++STL
中的栈是一种容器适配器,它是将vector/list
进行封装,push/pop
等接口直接调用vector/list
的接口即可,不需要像C语言那样,从头开始造轮子
namespace byh
{template<class T, class Container = deque<T>>class stack{public:void push(const T& val){_con.push_back(val);}void pop(){_con.pop_back();}const T& top(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}int main()
{byh::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;
}
在模板实例化时,我们给栈传什么容器,它就调用该容器的接口,默认容器是deque
deque
是一种双端队列,它结合了vector
和list
的优势
vector
:
- 优势:能够随机访问
- 劣势:头插/删,中间插/删要挪动数据,效率低;扩容有消耗
list
:
- 优势:任意位置插入/删除效率高
- 劣势:不支持随机访问
可以说,vector
和list
是两个互补的容器,既然这样,能不能把这两个容器的优势结合;因为有这样的需求,就有了deque
它的底层会有一个中控指针数组,每个元素都指向一个长度为N的数组
尾插时,如果当前的所有数组都满了,则开辟新的指针,指向新的数组,插入元素
头插时,在数组中,以从后往前的顺序依次插入元素
访问第i个元素时,如果第一个数组没满,i减去第一个数组元素个数,进行下面的运算:
- i / N -->找到该元素在第几个数组
- i % N -->找到是在该数组的第几个元素
如果第一个数组满了,直接进行上面的运算
如果中控指针数组满了,进行扩容,虽然最总还是要扩容,但消耗上大大降低
deque
:
- 优点:头插/删,尾插/删效率都不错
- 缺点:中间插/删会比较麻烦,不如
list
;随机访问的效率不如vector
stack
和queue
的默认容器使用deque
的原因就在于它的头插/删,尾插/删效率可以,并且不会使用随机访问或者中间插/删
2.queue
模拟实现
namespace byh
{template<class T, class Container = deque<T>>class queue{public:void push(const T& val){_con.push_back(val);}void pop(){_con.pop_front();}T& back(){return _con.back();}T& front(){return _con.front();}const T& back() const{return _con.back();}const T& front() const{return _con.front();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}int main()
{byh::queue<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;
}
3. priority_queue
queue
系列中,除了先进先出的普通queue
之外,还有一个优先级队列priority_queue
pop
时,不再是先进先出,而是按规定的优先级取出元素
#include<iostream>
#include<queue>int main()
{std::priority_queue<int> pq;pq.push(3);pq.push(2);pq.push(4);pq.push(5);pq.push(1);while (!pq.empty()){std::cout << pq.top() << " ";pq.pop();}std::cout << std::endl;return 0;
}
// 输出 5 4 3 2 1
仿函数实际上是一个类,只不过我们可以用函数的方式去使用它
它的内部是对()
运算符的重载
template<class T>struct Less{bool operator()(const T& x, const T& y){return x < y;}};void Test()
{byh::Less<int> less;cout << less(1, 2) << endl;// 有名对象cout << less.operator()(1, 2) << endl;cout << byh::Less<int>()(1, 2) << endl;// 匿名对象
}
我们可以在仿函数内部控制比较的逻辑,生活中大部分场景下都是用自定义类型描述一个对象,如何控制该对象的比较,进行排序,就需要我们规定好比较方式
struct Goods
{Goods(const char* str, double price, int evaluate):_name(str),_price(price),_evaluate(evaluate){}string _name;double _price;int _evaluate;
};struct ComparePriceLess
{bool operator()(const Goods& x, const Goods& y){return x._price < y._price;}
};//...int main()
{vector<Goods> v = { {"苹果",3.1,2},{"草莓",4.2,4},{"苹果",2.6,3} };sort(v.begin(), v.end(), ComparePriceLess());return 0;
}
3.1 模拟实现
template<class T>struct Less{bool operator()(const T& x, const T& y){return x < y;}};template<class T>struct Greater{bool operator()(const T& x, const T& y){return x > y;}};template<class T, class Container = vector<T>, class Compare = Less<T>>class priority_queue{public:void AdjustUp(int child){int parent = (child - 1) / 2;Compare com;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else break;}}void push(const T& val){_con.push_back(val);AdjustUp(size() - 1);}void AdjustDown(int parent){int child = parent * 2 + 1;Compare com;while (child < size()){if (child + 1 < size() && com(_con[child], _con[child + 1]))child++;if (com(_con[parent], _con[child])){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}else break;}}void pop(){swap(_con[0], _con[size() - 1]);_con.pop_back();AdjustDown(0);}T& top(){return _con[0];}const T& top() const{return _con[0];}size_t size() const{return _con.size();}bool empty() const{return _con.empty();}private:Container _con;};
4. Reverse_Iterator
反向迭代器与正向迭代器功能类似,它们都是遍历,不同的是,反向迭代器++
往前走,--
往后走
我们可以对正向迭代器的++
和--
修改,但这样做,每个容器的迭代器都要进行拷贝修改,不仅代码冗余,还麻烦
于是,我们用const
迭代器的思路,将Reverse_Iterator
写成一个模板,我们给它传哪个容器的迭代器,编译器就帮我们形成哪个容器的反向迭代器,从而减少我们的工作量
STL
中,rbegin()
和rend()
的位置与begin()
和end()
相反
访问时,是解引用当前位置的上一个位置
namespace byh
{template<class Iterator, class Ref, class Ptr>struct ReverseIterator{Iterator _it;ReverseIterator(Iterator it):_it(it){}Ref operator*(){Iterator temp = _it;return *(--temp);}Ptr operator->(){return &(operator*());}// ++itReverseIterator operator++(){_it--;return *this;}// --itReverseIterator operator--(){_it++;return *this;}bool operator!=(ReverseIterator rit){return _it != rit._it;}};
}
erator it):_it(it){}Ref operator*(){Iterator temp = _it;return *(--temp);}Ptr operator->(){return &(operator*());}// ++itReverseIterator operator++(){_it--;return *this;}// --itReverseIterator operator--(){_it++;return *this;}bool operator!=(ReverseIterator rit){return _it != rit._it;}};
}