stack 与 queue 与 priority_queue 与 仿函数 与 模板进阶

目录

stack

queue

deque

priority_queue

使用

模拟实现

仿函数

仿函数的用法

仿函数的意义

模板进阶

非类型模板参数

模板特化

类模板特化的用法

类模板特化的意义

函数模板特化的用法

模板的分离编译

模板分离编译报错的原因

​解决方法

模板总结


栈、队列和堆的用法在我的<<数据结构>>专栏都已经分享过了,详见以下博客:

别样的数据结构---栈-CSDN博客

别样的数据结构---队列-CSDN博客

独树一帜的完全二叉树---堆-CSDN博客

下面内容的重点还是模拟实现,不过栈和队列以及优先级队列的模拟实现与之前容器的实现有很大区别,我们知道,栈和队列的底层结构可以是顺序表,也可以是链表,只是新增了先进先出还是先进后出的限制,优先级队列的底层本质是堆,存储结构也是顺序表,因此,本篇博客中栈和队列的实现借助其他容器就可以实现!

这也是我们要讲解的栈和队列容器适配器,也就是用其他容器适配栈或者队列,至于哪些容器可以适配,哪些容器不能适配,完全取决于适配的容器有没有提供栈和队列要用到的接口~

stack

namespace dck
{
    //通过模板参数,控制底层容器是谁,不管底层容器是谁,都可以适配出后进先出的栈
	template <class T, class Container>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		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()
{
	dck::stack<int, vector<int>> st;
	//dck::stack<int, list<int>> st;
	//dck::stack<int, deque<int>> st; //deque是双端队列,也是STL容器
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);
	while (!st.empty())
	{
		cout << st.top() << " "; // 5 4 3 2 1
		st.pop();
	}
	return 0;
}

queue

using namespace std;
namespace dck
{
	template <class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_front();
		}

		const T& front()
		{
			return _con.front();
		}

		const T& back()
		{
			return _con.back();
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}
//vector不能适配queue
//因为vector不支持头删
int main()
{
	//dck::queue<int, list<int>> q;
	//dck::queue<int, deque<int>> q;
	dck::queue<int, vector<int>> q;
	q.push(3);
	q.push(1);
	q.push(2);
	q.push(7);
	q.push(5);
	while (!q.empty())
	{
		cout << q.front() << " "; //3 1 2 7 5
		q.pop();
	}
	return 0;
}

deque

双端队列也是STL容器之一,库中使用stack与queue都没有传递第二个模板参数,是因为库中的stack与queue默认的容器适配器就是deque

双端队列和队列没有什么关系哦,双端队列并没有先进先出的要求,库中提供的双端队列的接口几乎是vector和 list的综合, 但实际deque并不常用,因为deque在具体的某一方面的效率不如vector或listl, 那为啥还要有deque容器的存在呢??

我们知道vector的优点是尾插尾删效率高,支持下标的随机访问,缺点是中间或者头部的插入删除需要大量挪动数据,而list的优点是随机插入的效率很高,按需申请释放空间,缺点是不支持下标的随机访问,而deque在尾插尾删或者下标随机访问方面必然不如vetor, 在中间或头部的插入删除方面不如list,  但deque综合了两者的优点, 所以deque不是单向冠军,但综合实力很不错~

下面是deque的结构,我们简单说明一下:

中控数组本质是一个指针数组,每个数组元素保存的是一段buff数组的地址,所以插入元素很简单,只需要在buff中插入即可,如果buff满了,那就重新开辟一段buff数组,再保存buff数组的地址到中控数组元素中即可; 当中控数组满了,才需要扩容,而且扩容拷贝数据的代码很低,因为中控数组的元素都是指针,大小都是4/8个字节

如果想在中间插入元素,那就有两种选择:

一是整体挪动所有buff数组的数据,优点是保证每个buff数组是一样大的,[ ]下标的随机访问效率依旧很高,只需要让下标i减去第一个没有满的buff数组的元素个数,再 /= buff数组的大小,就可以定位到第几个buffer, 然后 % buff数组大小,就可以确定在buff数组的第几个位置;缺点是整体挪动数据效率下降!

二是扩容当前要插入的buff数组,优点是不用挪动大量数据,效率比较高,缺点是每个buff数组不一样大,所以[ ] 下标随机访问时候效率低下,需要依次用下标 i 减掉每个buff数组的大小!

priority_queue

priority_queue底层就是学习数据结构时讲到的堆,可以看到,priority_queue也用到了容器适配器---vector,  使用priority_queue不需要单独包头文件,因为priority_queue是放在queue头文件中的!

使用

#include <iostream>
using namespace std;
#include <queue>

void test_priority_queue1()
{
	priority_queue<int> q; //默认是大堆(默认是第三个参数是less)
	//priority_queue<int, vector<int>, less<int>> q; //less是大堆
	q.push(3);
	q.push(1);
	q.push(5);
	q.push(2);
	q.push(4);
	while (!q.empty())
	{
		cout << q.top() << " "; //5 4 3 2 1
		q.pop();
	}
	cout << endl;
}

void test_priority_queue2()
{
	priority_queue<int, vector<int>, greater<int>> q; //greater是小堆
	q.push(3);
	q.push(1);
	q.push(5);
	q.push(2);
	q.push(4);
	while (!q.empty())
	{
		cout << q.top() << " "; //1 2 3 4 5
		q.pop();
	}
	cout << endl;
}


void test_priority_queue3()
{
	vector<int> v;
	v.push_back(3);
	v.push_back(1);
	v.push_back(4);
	v.push_back(2);
	priority_queue<int> q(v.begin(), v.end()); //支持用迭代器区间构造
	while (!q.empty())
	{
		cout << q.top() << " "; //4 3 2 1
		q.pop();
	}
	cout << endl;
}

int main()
{
	//test_priority_queue1();
	//test_priority_queue2();
	test_priority_queue3();
	return 0;
}

模拟实现

#include <iostream>
#include <vector>
using namespace std;

namespace dck
{
	template <class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		//无参构造函数
		priority_queue()
		{}

		//构造函数(迭代器区间)
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//向下调整建堆
			for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}

		//向上调整
		void adjust_up(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//插入
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size()-1); 
		}

		//向下调整
		void adjust_down(int parent)
		{
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					child = child + 1;
				}
				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = (child * 2) + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top()
		{
			return _con[0];
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}
void test_priority_queue1()
{
	dck::priority_queue<int> q;
	q.push(2);
	q.push(3);
	q.push(1);
	q.push(4);
	while (!q.empty())
	{
		cout << q.top() << " "; //4 3 2 1
		q.pop();
	}
	cout << endl;
}

仿函数

仿函数本质是一个对象,只不过因为对应的类重载了函数调用运算符---(), 所以对象可以像函数一样来使用,所以叫做仿函数

仿函数的用法

template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

void test_functor()
{
	Less<int> less;

	cout << less(1, 2) << endl; //1
	cout << less(1.5, 0.6) << endl; //0
}

int main()
{
	test_functor(); 
	return 0;
}

仿函数的意义

以上述priority_queue模拟实现为例,  我们写的代码实现的是大堆,如果想要变小堆,就需要用到函数指针了,而函数指针比较复杂,所以C++采用了仿函数实现!

无非就是给priority_queue新增了一个模板参数来控制传递的类,进而可以在priority_queue类内部来决定比较时是>还是<, 从而控制了大小堆

#include <iostream>
#include <vector>
using namespace std;

template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

template <class T>
class Greater
{
public:
	bool operator()(T x, T y)
	{
		return x > y;
	}
};

namespace dck
{
	template <class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	public:
		//无参构造函数
		priority_queue()
		{}

		//构造函数(迭代器区间)
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//向下调整建堆
			for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}

		//向上调整
		void adjust_up(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//插入
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size()-1); 
		}

		//向下调整
		void adjust_down(int parent)
		{
			Compare com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child = child + 1;
				}
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = (child * 2) + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top()
		{
			return _con[0];
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

 有了上面仿函数的实现,我们就可以像库一样通过控制给priority_queue传递的参数来控制大小堆

void test_priority_queue1()
{
	dck::priority_queue<int> q; //默认是大堆
	q.push(2);
	q.push(3);
	q.push(1);
	q.push(4);
	while (!q.empty())
	{
		cout << q.top() << " "; //4 3 2 1
		q.pop();
	}
	cout << endl;
}

void test_priority_queue2()
{
	dck::priority_queue<int, vector<int>, Greater<int>> q; //小堆
	q.push(2);
	q.push(3);
	q.push(1);
	q.push(4);
	while (!q.empty())
	{
		cout << q.top() << " "; //1 2 3 4 
		q.pop();
	}
	cout << endl;
}

int main()
{
	//test_priority_queue1(); 
	test_priority_queue2();
	return 0;
}

除了上面的传统比较方法,我们也可以自定义仿函数的比较方法,假设有如下场景:

#include <iostream>
#include <vector>
using namespace std;

template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

template <class T>
class Greater
{
public:
	bool operator()(T x, T y)
	{
		return x > y;
	}
};

namespace dck
{
	template <class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	public:
		//无参构造函数
		priority_queue()
		{}

		//构造函数(迭代器区间)
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//向下调整建堆
			for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}

		//向上调整
		void adjust_up(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//插入
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size()-1); 
		}

		//向下调整
		void adjust_down(int parent)
		{
			Compare com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child = child + 1;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = (child * 2) + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top()
		{
			return _con[0];
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};


void test_priority_queue3()
{
	dck::priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;  //2018-10-30

	dck::priority_queue<Date*> q2;
	q2.push(new Date(2018, 10, 29));
	q2.push(new Date(2018, 10, 28));
	q2.push(new Date(2018, 10, 30));
	cout << *(q2.top()) << endl; //每次打印结果都在变化

}

int main()
{
	test_priority_queue3();
	return 0;
}

由于优先级队列中我们传参传的是指针,new出来的空间地址是随机的,所以比较结果一直在变化,虽然传递的是就是指针,但是如果我们就是想按照Date的大小来比较呢??所以我们就可以自己写一个仿函数,定义新的比较规则,  这样就可以按照我们想要的方式去比较了~

#include <iostream>
#include <vector>
using namespace std;

template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

template <class T>
class Greater
{
public:
	bool operator()(T x, T y)
	{
		return x > y;
	}
};

namespace dck
{
	template <class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	public:
		//无参构造函数
		priority_queue()
		{}

		//构造函数(迭代器区间)
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//向下调整建堆
			for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}

		//向上调整
		void adjust_up(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//插入
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size()-1); 
		}

		//向下调整
		void adjust_down(int parent)
		{
			Compare com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child = child + 1;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = (child * 2) + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top()
		{
			return _con[0];
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

	private:
		Container _con;
	};
}

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

class PDateCompare
{
public:
	bool operator()(Date* x, Date* y)
	{
		return *x < *y;
	}
};

void test_priority_queue3()
{
	dck::priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;  //2018-10-30

	dck::priority_queue<Date*, vector<Date*>, PDateCompare> q2;
	q2.push(new Date(2018, 10, 29));
	q2.push(new Date(2018, 10, 28));
	q2.push(new Date(2018, 10, 30));
	cout << *(q2.top()) << endl; //2018-10-30
}

int main()
{
	test_priority_queue3();
	return 0;
}

模板进阶

非类型模板参数

我们定义了一个栈类型,栈的成员变量---数组的大小是固定的,现在定义了两个栈对象,一个栈想开辟10大小的空间,另一个栈想开辟100大小的空间,此时我们就可以用到非类型的模板参数

#include <iostream>
using namespace std;

template <class T, size_t N> //非模板参数必须是整形家族的(整形,字符型),不能是浮点型等其他类型
class Stack
{
private:
	T _a[N];
};

int main()
{
	Stack<int, 10> st1;  
	Stack<int, 100> st2;

	int n;
	cin >> n;
	//Stack<int, n> st3; //err, 非模板参数必须传常量
	return 0;
}

非类型的模板参数还是有存在的意义的,比如C++11新出的容器array以及位图bitset都用到了非类型的模板参数

#include <array>
int main()
{
	int arr1[10];
	array<int, 10> arr2;

	cout << sizeof(arr1) << endl; //10
	cout << sizeof(arr2) << endl; //10

	//arr1[20] = 1; //编译器对静态数组的越界是一种抽查,所以即使越界,程序也可能正常运行
	arr2[20] = 1; //转化成去调用运算符重载函数, 越界直接assert断言报错, 检查更为严格
	return 0;
}

模板特化

模板特化指的是指对某些类型进行特殊处理, 类模板实例化时也就采用最匹配原则!

注意, 特化模板不能单独存在,必须先有原模板,才能有特化模板

类模板特化的用法

全特化

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//下面这个类叫做上面类的全特化

template<>
class Data<int, double>
{
public:
	Data() { cout << "Data<int, double>" << endl; }
private:
	int _d1;
	double _d2;
};

int main()
{
	Data<int, int> d1; //Data<T1, T2>
	Data<int, double> d2; //Data<int, double>
	return 0;
}

偏特化类型一:

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//下面这个类叫做上面类的偏特化

template<class T>
class Data<T, double>
{
public:
	Data() { cout << "Data<T, double>" << endl; }
private:
	T _d1;
	double _d2;
};

int main()
{
	Data<int, int> d1; //Data<T1, T2>
	Data<double, double> d2; //Data<T, double>
	return 0;
}

偏特化类型二:

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//下面这个类叫做上面类的偏特化

template<class T1, class T2>
class Data<T1*, T2*> //只要是两个指针类型,就实例化我这个模板
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
	T1* _d1;
	T2* _d2;
};

int main()
{
	Data<int, int> d1; //Data<T1, T2>
	Data<double*, int*> d2; //Data<T1*, T2*>
	return 0;
}
类模板特化的意义

上文在讲解priority_queue时,如果传递的参数是Date*,但是还是想按照Date内容进行比较,这时除了自己写一个PDateCompare仿函数之外,还可以将Less模板进行全特化


template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

//模板全特化
template <>
class Less<Date*>
{
public:
	bool operator()(Date* x, Date* y)
	{
		return *x < *y;
	}
};

同样的,我们也可以利用偏特化类型二解决上述问题

template <class T>
class Less
{
public:
	bool operator()(T x, T y)
	{
		return x < y;
	}
};

//模板偏特化

template <class T>
class Less<T*>
{
public:
	bool operator()(T* x, T* y)
	{
		return *x < *y;
	}
};
函数模板特化的用法
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

//函数模板
template <class T>
bool Less(T left, T right)
{
	return left < right;
}

//函数模板特化
template <>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	Date* d1 = new Date(2023, 10, 8);
	Date* d2 = new Date(2023, 10, 9);
	cout << Less(d1, d2) << endl; //1
	return 0;
}

类模板推荐使用特化,但函数模板不推荐采用特化,容易出问题, 比如下面这段代码

//函数模板
template <class T>
bool Less(const T& left, const T& right) //为了提高传参效率,采用引用
{
	return left < right;
}

//函数模板特化
template <>
//下面这种写法是错误的,因为期望const修饰的是引用,但是直接把T替换成Date*之后,const修饰的是指针
//bool Less<Date*>(const Date* &left, const Date* &right) //× 
bool Less<Date*>(Date* const &left, Date* const &right) //√
{
	return *left < *right;
}

所以函数模板尽量不要用特化,可以写一个具体的函数,来和函数模板实例化生成的具体函数构成重载即可

//函数模板
template <class T>
bool Less(const T& left, const T& right) 
{
	return left < right;
}

//具体函数
bool Less(Date* left, Date* right) 
{
	return *left < *right;
}

模板的分离编译

模板分离编译报错的原因
解决方法

1.显式实例化

函数模板显式实例化

a.cpp

template <class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

//显示实例化成int
template
int Add<int>(const int& left, const int& right);

//显示实例化成double
template
double Add<double>(const double& left, const double& right);

类模板显式实例化

a.cpp

template<class T>
void Stack<T>::Push(const T& x)
{
	cout << "void Stack<T>::Push(const T& x)" << endl;
}

template<class T>
void Stack<T>::Pop()
{
	cout << "void Stack<T>::Pop()" << endl;
}

//显式实例化
template
class Stack<int>;

2.模板声明和定义写在同一个文件中

比如我们把模板的声明和定义都写在a.h中,就没有问题了~

a.h / a.hpp

.hpp的一般指的是我这个文件既有声明,也有定义,大概率是含有模板,不方便分离编译~

#include <iostream>
using namespace std;

//函数模板声明
template<class T>
T Add(const T& left, const T& right);

//函数模板定义
template <class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

//类模板声明
template<class T>
class Stack
{
public:
	void Push(const T& x);
	void Pop();
private:
	T _a;
	int _top;
	int _capacity;
};

//类模板定义
template<class T>
void Stack<T>::Push(const T& x)
{
	cout << "void Stack<T>::Push(const T& x)" << endl;
}

template<class T>
void Stack<T>::Pop()
{
	cout << "void Stack<T>::Pop()" << endl;
}

模板总结

【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/518575.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Git安装教程(图文安装)

Git Bash是git(版本管理器)中提供的一个命令行工具&#xff0c;外观类似于Windows系统内置的cmd命令行工具。 可以将Git Bash看作是一个终端模拟器&#xff0c;它提供了类似于Linux和Unix系统下Bash Shell环境的功能。通过Git Bash&#xff0c;用户可以在Windows系统中运行基于…

【数据处理包Pandas】DataFrame对象的合并

目录 前言一、回顾Numpy数组的合并二、concat方法合并DataFrame对象三、append方法的使用四、merge方法合并DataFrame对象&#xff08;一&#xff09;比较merge与concat&#xff08;二&#xff09;参数on、left_on和right_on的用法&#xff08;三&#xff09;合并时四种不同的连…

c# wpf template ItemsPanel 简单试验

1.概要 2.代码 <Window x:Class"WpfApp2.Window9"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/…

软件测试(Junit5 单元测试框架)(五)

1. Junit单元测试框架 Junit 是 Java 的一个单元测试框架, 使用Selenium写自动化测试用例, 使用Junit 管理写好的测试用例. 2. 注解&#xff1a; Test 表示当前的这个方法是一个测试用例. 示例: 添加依赖 <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-…

[译] 教你如何用 Flutter 的 GestureDetector 构建自定义滑块

这个控件非常简单&#xff0c;我们接收完成的百分比值&#xff0c;以及正面和背面部分的颜色。主 Container 将背面颜色作为背景&#xff0c;我们将绘制正面部分去覆盖它。它的子节点是 Row&#xff0c;虽然它只包含一个子节点&#xff0c;但我保留了它&#xff0c;方便你添加另…

impala使用round函数保留小数失效

问题描述如标题所示 1.理论情况: round()函数,是用来做四舍五入的,比如:select round(2.126,2) 结果为:2.132.异常情况: 但是有时候会出现一些意料之外的情况,比如:select round(1/3,3) 结果为:0.33300000000000002正确的应该是:0.333截图效果示例如下: 3.解决办…

51之LCD1602与模块化编程

LCD1602&#xff0c;即我们开发板上附赠的那个液晶显示屏&#xff0c;我们通常可以使用这个液晶显示屏用来做调试工具&#xff0c;我们使用一下江科大提供的关于这个LCD1602的代码&#xff0c;用来为我们提供了类似C语言标准库里面的printf函数的用法&#xff0c;只是这个更加复…

非关系型数据库-----------探索 Redis高可用 、持久化、性能管理

目录 一、Redis 高可用 1.1什么是高可用 1.2Redis的高可用技术 二、 Redis 持久化 2.1持久化的功能 2.2Redis 提供两种方式进行持久化 三、Redis 持久化之----------RDB 3.1触发条件 3.1.1手动触发 3.1.2自动触发 3.1.3其他自动触发机制 3.2执行流程 3.3启动时加载…

AssetBundle在移动设备上丢失

1&#xff09;AssetBundle在移动设备上丢失 2&#xff09;Unity云渲染插件RenderStreaming&#xff0c;如何实现多用户分别有独立的操作 3&#xff09;如何在圆柱体类型的地图中编程玩家的输入 4&#xff09;Mixamo动画的根运动问题 这是第380篇UWA技术知识分享的推送&#xff…

如何处理ubuntu22.04LTS安装过程中出现“Daemons using outdated libraries”提示

Ubuntu 22.04 LTS 中使用命令行升级软件或安装任何新软件时&#xff0c;您可能收到“Daemons using outdated libraries”&#xff0c;“Which services should be restarted?”的提示&#xff0c;提示下面列出备选的重启服务&#xff0c;如下。 使用以下命令&#xff0c;能够…

盒子模型和伪元素

一.盒子模型的理解 我们平常在布局的时候,少不了盒子模型,今天讲解一下对盒子模型的理解。 理解:我们可以把盒子模型比作一个装着快递的包裹:里面的东西可以比作是内容,盒子里面的填充物可以比作是padding 外层的包装纸线条,可以比作是border&#xff0c;这个快递离另外个快递…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(9)复发

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 第一课 ——第三课素材文件 https://www.alipan.c…

20230405让WIN11暂停更新365天(暂停更新35天)

20230405让WIN11暂停更新365天&#xff08;暂停更新35天&#xff09; 2024/4/5 20:34 缘起&#xff0c;备用的笔记本电脑只要一开机&#xff0c;就会被比尔盖茨/微软提醒去更新/升级&#xff01; 不胜其烦&#xff01; 虽然可以在设置里设置暂停更新35天。但是也是不胜其扰&…

蓝桥杯杯赛之深度优先搜索优化《1.分成互质组》 《 2.小猫爬山》【dfs】【深度搜索剪枝优化】【搜索顺序】

文章目录 思想例题1. 分成互质组题目链接题目描述【解法一】【解法二】 2. 小猫爬山题目链接题目描述输入样例&#xff1a;输出样例&#xff1a;【思路】【WA代码】【AC代码】 思想 本质为两种搜索顺序&#xff1a; 枚举当前元素可以放入哪一组枚举每一组可以放入哪些元素 操…

解决windows下Qt Creator显示界面过大的问题

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;QT❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 问题描述 解决方法 1、右击此电脑--->属性 2、点击高级系统设置--->点击环境变量 3、 找到系…

YOLOv3

YOLOv3 论文简介论文内容1. 采用darknet53FPN结构2. 边框预测保持与YOLOv2保持一致3. 沿用YOLOv2 kmeans生成先验anchors4.类别预测改为多分类格式 论文简介 论文&#xff1a;《YOLOv3: An Incremental Improvement》 作者&#xff1a;Joseph Redmon, Ali Farhadi 论文下载地址…

使用 Kafka 保证消息不丢失的策略及原理解析

✨✨祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 一、引言 二. 持久化存储 2.1持久化存储原理&#xff1a; 2.2使用示例&#xff1a; …

7.java openCV4.x 入门-Mat之转换、重塑与计算

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天建议把本文当作笔记来看&#xff0c;据说专栏目录里面有相应视频&#x1f92b; &#x1f9ed;文…

2024HW --->反序列化漏洞!

对于反序列化&#xff0c;这个漏洞也是常用的&#xff0c;不过涉及到的方面非常非常广&#xff0c;比其他漏洞也难很多 于是本篇文章就分成PHP和JAVA的反序列化来讲讲 1.反序列化 想要理解反序列化&#xff0c;首先就要理解序列化 序列化&#xff1a;把对象转换为字节序列的过…

Redis -- 缓存雪崩问题

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 可能原因 : 同一时间大量的key到期 ; 解决方案&#xff1a; 给不同的Key的TTL添加随机值 利用Redis集群提高服务的可用性 给缓存业务添加降…