继续写笔记,之前的笔记找时间补上
模板进阶
非类型模板参数
在进行泛型编程的时候,我们会使用模板来把一个函数或类适用于大部分类型。
例如:
template<calss T>
class A{
};
T是其中的类型形参,但模板除了类型形参之外,还有非类型形参。
template<class T, size_t N = 10>
class A{
};
是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
例如,我们要生成一个固定大小的栈(栈的类由我们自己来写)
#define N 100
template<class T>
class stack{
private:
int _a[N];
};
如果不使用非类型模板参数,就只能先定义一个大小N,再去生成这个类,如果要改变固定大小,就只能改变N,但是,如果我们要同时生成两个不一样大小的静态栈,这种方法就不适用。
用非类型模板参数就很好解决。
template<class T, size_t N = 10>
class stack{
private:
int _a[N];
};
int main(){
//使用默认大小10
stack<int> s2;
//指定大小为20
stack<int, 20> s1;
return 0;
}
同时我们要注意
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
第二点的意思就是说,我们在实例化对象的时候,所用的参数不能是变量。
int a = 10;
stack<int, a> s;
这样就是不行的。
模板的特化
函数模板特化
在使用模板的时候,对于一些函数模板,有的类型可能适用,但有的类型可能就不适用。
例如:
template<class T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}
如果是string类型,这个函数就不适用。
这时候就要进行模板特化,即:在原模板类的基础上,针对特殊类型所进行特殊化的实现。
不用特化的形式是这样,
template<class T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}
bool IsEqual(const char* left, const char* right)
{
return strcmp(left, right) == 0;
}
因为在进行函数匹配的时候,是优先找到最适合的那个。
所以如果参数是string类型,就会先匹配第二个。
如果是模板特化,应该这样
template<class T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}
template<>
bool IsEqual<const char* const>(const char* const &left, const char* const &right)
{
if (strcmp(left, right) > 0)
return true;
return false;
}
要注意的是
- 必须要先有一个基础的函数模板,即有
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
类模板特化
全特化
指将类模板中的参数全部特化
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;
};
偏特化
指对模板参数的条件限制的特化。
例类模板
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
通常有两种偏特化,一种就是对参数的部分进行特化。
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
即特化一个参数。
另一种特化是对于参数类型的更近一步的特化
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
模板的分离编译
什么是分离编译?就是一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
以往在设计普通的函数或类的时候,可以把函数名,类及其成员函数放在一个.h文件中,而函数和成员函数的实现可以在一个.cpp文件中。但在模板类或者模板函数中,却不能这样。
所以类模板和函数模板的声明和实现不能分离。
总结,对于模板来说它既有优点也有缺点。
优点:1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
缺点:1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误