目录
一、前言
二、非类型模板参数
三、模板的特化
3.1 类模板特化
3.11 全特化
3.12 偏特化
3.2 函数模板特化
3.3 注意
四、模板的分离编译
一、前言
前面的文章梳理了模板初阶的一些用法,在后面梳理了STL的一些容器的用法后,下面将用到含有STL的模板部分知识进行梳理。
那么首先我们先看下面的代码:
template<class Container>
void Print(const Container& con)
{Container::const_iterator it=con.begin();while(it != con.end()){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);Print(v);
}
这里我们首先实例化了一个vector<int>的对象v,然后插入两个数据,最后写了一个打印函数来打印有迭代器的容器的数据,但这里我们运行后出现了上图的错误,这个错误出现的原因是第四行我们使用模板导致的,因为在我们使用模板进行定义时,在没有对它进行实例化前编译器无法识别,在没有实例化时,Container::const_iterator这一个定义,编译器无法识别,因为这一句话既可以当作类型,也可以当作变量/对象,虽然作为变量时加上后面的it是不对的,但编译器无法在没有其他任何操作前正确识别,所以报错了。那么我们想要解决这个问题只要提前告诉编译器这是类型就可以了,这里用到了定义模板参数时的另一个与class同等效果的typename,typename在模板中与class唯一的不同就在于将typename提前可以告诉编译器这句话是代表类型,这样编译器就会在识别时确定这是类型,就不会报错了。
template<class Container>
void Print(const Container& con)
{typename Container::const_iterator it=con.begin();while(it != con.end()){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);Print(v);
}
类似上面这样的只要传过来的容器含有模板参数(vector<T>这样的也是)都要在前面加typename
注意:无论定义模板参数时是class还是typename,遇到上面这种模板参数来定义时都要在定义前加typename
二、非类型模板参数
首先我们知道模板的使用格式是
template<class (模板参数), ...>
那么非类型模板参数就是在模板参数的定义中用常量来代表模板参数。
下面我们写了一个静态栈的代码:
#define N 10
template<class T>
class Stack
{
private:T _a[N];int _top;
};
int main()
{Stack<int> st1;Stack<int> st2;
}
如果此时我们需要st1的大小为10,st2的大小为100,如何做?此时我们发现一个静态栈已经无法实现了,那么此时只要我们用到非类型模板参数加一个非类型模板参数即可:
template<class T,size_t N>
class Stack
{
private:T _a[N];int _top;
};
int main()
{Stack<int,10> st1;Stack<int,100> st2;
}
上述代码所示:我们在模板参数中加了一个非类型模板参数,这样我们在实例化时只要填我们需要的常量时就可以了。
注意:1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。2. 非类型的模板参数必须在编译期就能确认结果。
三、模板的特化
在实际应用中会遇到一些普通模板无法实现的情况,需要特殊处理,那么此时就可以使用模板的特化来特殊处理。(这里以梳理用法为主)
模板的特化分为函数模板特化和类模板特化
3.1 类模板特化
3.11 全特化
全特化就是将所有模板参数特殊化,格式如下:
template<>
class A<类型,类型...>
{};
全特化将<>中的所有模板参数去掉,将我们需要特化的类型放到类名后面的<>中
我们看下面的代码:
//没有特化
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1, T2>" << endl;}
private:T1 d1;T2 d2;
};
//全特化
template<>
class Data<int, char>
{
public:Data(){cout << "Data<int,char>" << endl;}
private:int d1;char d2;
};
int main()
{Data<int ,int> d1;Data<int, char> d2;
}
这串代码中上面是没有特化的,下面是全特化,全特化这里我们需要int和char型,所以将类型写到Data后即可,那么此时我们只有在调用时以Data<int, char> d2 ;这样的格式才可以使用全特化,<int,int>在这里只能调用没有特化类
3.12 偏特化
偏特化就是特化部分模板参数或进一步限制模板参数。
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1, T2>" << endl;}
private:T1 d1;T2 d2;
};
//偏特化1:特化部分模板参数
template<class T1>
class Data<T1, int>
{
public:Data(){cout << "Data<T1, int>" << endl;}
private:T1 d1;int d2;
};
//偏特化2:进一步限制模板参数
template<class T1, class T2>
class Data<T1*,T2*>
{
public:Data(){cout << "Data<T1*, T2*>" << endl;}
private:T1* d1;T2* d2;
};
偏特化有两种,偏特化1:特化部分模板参数和偏特化2:进一步限制模板参数,它们代表着两种偏特化形式:
偏特化1:将部分模板参数特化,上述代码中我们可以看到保留了一个模板参数T1,特化了一个模板参数成int。
偏特化2:这里保留了所有的模板参数,但将所有的模板参数进一步进行限制.。例如上述代码中将所有的模板参数限制为指针,也就是说只要参数都是指针类型的都会去调用这个模板类。
3.2 函数模板特化
函数模板只有全特化
#include<stdbool.h>
//没有特化
template<class T>
bool Less(T a, T b)
{return a < b;
}
//全特化
template<>
bool Less<Date*>(Date* a, Date* b)
{return *a < *b;
}int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;
}
上述代码中先写了一个Less没有特化的比较,当遇到像Date* p1=&d1这样的变量时再使用普适模板就无法比较了,所以需要写一个Less模板的特化将Date*单写出来
bool Less(Date* a, Date* b)
{return *a < *b;
}
上述代码中没有使用模板但依然可以正常比较,大部分的函数模板都可以被不适用模板的函数代替,所以此时再使用模板就会降低运行效率,因此函数模板的特化在此时没有必要使用。
3.3 注意
无论是类模板特化还是函数模板特化,在使用特化时必须要有普通模板的存在,不然会报错。
四、模板的分离编译
我们有时会为了方便将函数的声明和定义分离,将声明放到头文件中,定义放到源文件中,但如果使用了模板后那么这个函数就不可以与普通函数一样放到两个文件中。
我们看下面代码:
//Stack.h
namespace lbs
{template<class T, class container = vector<int>>class stack{public:void push(const T& val);void pop();T& top(){return _con.back();}size_t size(){return _con.size();}private:container _con;};
//-------------Stack.cpp---------------------------
namespace lbs
{template<class T, class container>void stack<T, container>::push(const T& val){_con.push_back(val);}template<class T, class container>void stack<T, container>::pop(){_con.pop_back();}//template class stack<int>;
}
//-----------------test.cpp-------------------------
int main()
{lbs::stack<int> st1;st1.push(1);st1.pop();
}
上述代码中,我们在Stack.h文件中我们写了一个stack类,其中push和pop进行了声明与定义分离,size和top没有进行分离,此时我们运行后在定义的push和pop处会报错。
这里出现错误的原因就是在链接过程中编译器无法正确识别类模板的实例化,因为这里出现了模板参数,而模板参数在改函数没有被调用时不会实例化,所以链接阶段没有进行实例化,那么此时在Stack,cpp文件中的定义就无法正常进行。
想要正确分离有两种方法:
1、较为麻烦(不推荐)
在Stack.cpp文件中加上template class stack<类型>;这一句话
在定义所在文件中加上这一句话就代表提前告诉编译器要对此类型进行实例化,但麻烦的地方在于每此用新的类型时都要加上这一句话并填上对应的类型
2、较为简单(推荐)
namespace lbs
{template<class T, class container = vector<int>>class stack{public:void push(const T& val);void pop();T& top(){return _con.back();}size_t size(){return _con.size();}private:container _con;};template<class T, class container>void stack<T, container>::push(const T& val){_con.push_back(val);}template<class T, class container>void stack<T, container>::pop(){_con.pop_back();}
}
将定义放到声明的下方,此时声明与定义分离