实验环境
操作系统:win10
gcc:8.1.0
开发软件:qt5.14.2
模板概念
在C++的相关代码开发中,我们常常会遇到的是关于代码通用性的问题;模板就是C++支持参数化程序设计的工具,通过它可以实现参数化多态性。这里可以看一下两个例子。
int sum(int x,int y)
{
return x+y;
}
double sum(double x,double y)
{
return x+y;
}
事实上两个函数除了数据类型以外进行的功能是一样的,但如果像这样分开写其实大大的降低了代码整体的通用性,对于设计和使用都是不方便的选择。这就是模板需要起作用的地方,模板的定义如下
template<模板参数表>
类型名 函数名(参数表)
{
函数
}
为了方便理解,以上述的例子为出发点进行衍生。
#include<iostream>
using namespace std;
template<typename test>
test sum(test a,test b)
{
return a+b;
}
int main()
{
int test1=1,test2=2;
double test3=3,test4=4;
cout<<sum(test1,test2)<<endl;
cout<<sum(test3,test4)<<endl;
return 0;
}
可以看到,这样的方式成功完成了对一般模板函数的成功运用。
特化模板函数
但此时需要考虑一个特殊状况,使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数,如下所示。
template<class test>
bool Isequal(test &t1, test &t2)
{
return t1 == t2;
}
上述的函数可以判断传入的两个参数是否相等,但显而易见的,当数据类型为字符型时,上述函数无法生效,故在此使用特化的模板函数。
template<>
bool Isequal<char*>(char*& t1, char*& t2)
{
return strcmp(t1, t2) == 0;
}
上面展示的就是特化的模板函数,需要注意几点
- 使用模板特化时,必须要先有基础的模板函数(就是上面第一个模板函数)
- template 后直接跟<> 里面不用写类型
- 函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型
- 特化的函数的函数名,参数列表要和原基础的模板函数相同
同样 我们简单的运行来验证结果。
从上图可以看到,运行的结果符合预期,特化的函数成功运作了。
模板类
既然存在模板函数的概念,同样也存在模板类的概念。使用类模板的过程可以使得程序员为类定义一种模式,使得类中的部分数据成员,部分成员函数的参数,返回值和局部变量能取不同的类型。这里为了方便说明,简单的建立一个**队列(queue)**的类来进行实践。
首先让我们对简单的模板类进行说明。
template <typename T>
class Complex{
public:
//构造函数
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
//运算符重载
Complex<T> operator+(Complex &c)
{
Complex<T> tmp(this->a+c.a, this->b+c.b);
return tmp;
}
private:
T a;
T b;
}
int main()
{
//对象的定义,必须声明模板类型,因为要分配内容
Complex<int> a(10,20);
Complex<int> b(20,30);
Complex<int> c = a + b;
return 0;
}
其中简单的展示了类模板的使用方式,重要的部分都有进行注释。
队列类
话不多说,先把实现的代码放在下面
#include <iostream>
using namespace std;
template<class T>
struct Node {
T data;
Node *next;
};
template<class T>
class MyQueue {
public:
MyQueue(); // 构造函数
~MyQueue(); // 析构函数
void Push(const T& x); // 压入队列
T Front(); // 获取队头元素
T Back(); // 获取队尾元素
void Pop(); // 删除队头元素
bool Empty() const; // 判断队列是否为空
int Size() const; // 返回队列的大小
private:
Node<T> *head; // 头指针
Node<T> *tail; // 尾指针
};
// 构造函数
template<class T>
MyQueue<T>::MyQueue() {
head = tail = NULL;
}
// 析构函数
template<class T>
MyQueue<T>::~MyQueue() {
Node<T> *current;
while(head != NULL) {
current = head;
head = head->next;
delete current;
current = NULL;
}
}
// 压入队列
template<class T>
void MyQueue<T>::Push(const T &x) {
Node<T> *current;
current = new Node<T>();
if(current == NULL) {
std::cerr << "申请空间失败!\n";
exit(1);
}
current->data = x;
current->next = NULL;
if(head == NULL)
head = current;
else
tail->next = current;
tail = current;
}
// 获取队头元素
template<class T>
T MyQueue<T>::Front() {
Node<T> *current;
if(head == NULL) {
std::cout << "队列为空!\n";
exit(1);
}
current = head;
return current->data;
}
// 获取队尾元素
template<class T>
T MyQueue<T>::Back() {
Node<T> *current;
if(head == NULL) {
std::cout << "队列为空!\n";
exit(1);
}
current = tail;
return tail->data;
}
// 删除队头元素
template<class T>
void MyQueue<T>::Pop() {
if(head == NULL) {
std::cout << "队列为空!\n";
exit(1);
}
head = head->next;
}
// 判断队列是否为空
template<class T>
bool MyQueue<T>::Empty() const {
if(head == NULL)
return true;
return false;
}
// 返回队列的大小
template<class T>
int MyQueue<T>::Size() const {
int n;
Node<T> *current;
n = 0;
current = head;
while(current != NULL) {
n++;
current = current->next;
}
return n;
}
可以看到,上面的类在使用了模板类的前提下实现了队列的简单的构造函数,析构函数,向队列中加入元素,删除队列元素,返回队列长度,获取队头和队尾的长度等的基本操作。
先让我们看一下实现的效果
与模板类的实现在上述的队列代码中都有体现,以下任然需要补充一些在使用类模板时会遇到的问题。
- 模板类本身未指定所使用的数据类型,不能单独编译模板类的实现。 只用在使用模板类的阶段,指定了模板中的数据类型,编译器才能正常编译。因此,在实际开发中,必须把实现全部写在头文件里面,把声明和实现分开的做法不可取。
- 模版不支持在局部函数中声明定义或使用
- 自动类型推导,必须推导出一致的数据类型T,才可以使用模板必须要确定出T的数据类型,才可以使用。
- 模版类的定义和实现不能分开写在不同文件中,否则会导致编译错误