✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C++
目录
[2.1 特化的概念](#2.1 特化的概念)
[2.2 函数模板特化](#2.2 函数模板特化)
[2.3 类模板特化](#2.3 类模板特化)
[2.3.1 全特化](#2.3.1 全特化)
[2.3.2 偏特化](#2.3.2 偏特化)
[3.1 分离编译](#3.1 分离编译)
[3.2 模板不能分离编译的原因](#3.2 模板不能分离编译的原因)
[3.3 解决方法](#3.3 解决方法)
一、非类型模板参数
模板参数分为两种:类型模板参数 、非类型模板参数。
- 类型模板参数:在参数列表中,跟在class或者typename后的类型名称。例如:
cpp
template<class T>
class A
{
T data;
};
- 非类型模板参数:在参数列表中, 声明一个常量,作为参数列表的一部分。在类中可以当作常量来使用。例如:
cpp
template<class T, size_t N>
class A
{
T data;
T arr[N];
};
注意:
浮点数、类对象、字符串不能作为非类型模板参数。
非类型模板参数的值必须在编译时就能够确定。
模板中,类型参数、非类型参数都可以给定缺省值 。
cpptemplate<class T = int, size_t N = 10> class A { T data; T arr[N]; }; int main() { A<> a; return 0; }
二、模板的特化
2.1 特化的概念
在模板类中,我们可以借助类型参数实现任意类型的大小比较:
cpp
template<class T>
class Great
{
public:
bool operator()(T n1, T n2)
{
return n1 > n2;
}
};
class Date
{
public:
Date(int year = 2000, int month = 8, int day = 10)
:year(year),
month(month),
day(day)
{}
bool operator>(const Date& date)
{
//实现了日期比较的逻辑...
if (year > date.year)
{
return true;
}
else if (year == date.year)
{
if (month > date.month)
{
return true;
}
else if (month == date.month)
return day > date.day;
}
return false;
}
private:
int year;
int month;
int day;
};
int main()
{
Great<Date> great;
Great<Date*> pgreat;
Date d1(2005, 10, 21);
Date d2(2005, 10, 11);
cout << great(d2, d1) << endl;//比较成功
Date* dd1 = &d1;
Date* dd2 = &d2;
cout << pgreat(dd2, dd1) << endl;//比较失败,比较的是地址
return 0;
}
但是当比较的类型变成指针的时候,我们会发现结果不符合我们的预期。原因是当类型为指针的时候,比较的逻辑是地址的大小,而不是指针指向的对象的大小关系。 如果想解决这种问题,就需要用到特化。特化就是:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特 化与类模板特化。
2.2 函数模板特化
要求:
- 必须有一个现有的函数模板。
- 关键字template后面接一对空的尖括号<>。
- 函数名字后跟一对尖括号<>,尖括号中指定需要特化的类型。
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
例如:
cpptemplate<class T> bool Great(T left, T right) { return left > right; } //对Great函数模板进行特化 template<> bool Great<Date*>(Date* left, Date* right) //left,right和模板函数的基础参数类型完全相同 { return *left > *right; }
这样一来,当模板参数为Date指针类型的时候,比较逻辑就会按照特化后的方式进行比较。
2.3 类模板特化
2.3.1 全特化
全特化就是将类模板中的所有参数都特化。
cpp
template<class T>
class Great
{
public:
bool operator()(T n1, T n2)
{
return n1 > n2;
}
};
template<class T>
class Great<T*>
{
public:
bool operator()(T* n1, T* n2)
{
return *n1 > *n2;
}
};
2.3.2 偏特化
偏特化就是任何针对模版参数进一步进行条件限制设计的特化版本。共有两种形式:
假设有以下类:
cpp
template<class T1, class T2>
class C
{
private:
T1 data1;
T2 data2;
};
-
对于模板参数进行部分特化:
cpptemplate<class T1> class C<T1,int>//将第二个参数特化为int { public: C() { cout << "C()(T1 int)" << endl; } private: T1 data1; int data2; };
-
对于模板参数进行进一步的限制,比如:
cpptemplate<class T1, class T2> class C<T1*, T2*> //指针特化 { public: C() { cout << "C()(T1* T2*)" << endl; } private: T1 data1; T2 data2; };
cpptemplate<class T1, class T2> class C<T1&, T2&> //引用特化 { public: C() { cout << "C()(T1& T2&)" << endl; } private: T1 data1; T2 data2; };
cpptemplate<class T1, class T2> class C<T1*, T2&> //指针 + 引用特化 { public: C() { cout << "C()(T1* T2&)" << endl; } private: T1 data1; T2 data2; };
cpp
int main()
{
C<char, int> c1; //调用<T1, int>特化
C<int*, int*> c2; //调用<T1*, T2*>特化
C<int&, int&> c3; //调用<T1&, T&>特化
C<int*, int&> c4; //调用<T1*, T2&>特化
return 0;
}
三、模板的分离编译
3.1 分离编译
在工程中,一般会将头文件(.h)、函数实现文件(.cpp)、测试文件(.cpp)分为三个文件来进行管理。头文件进行函数的声明,函数实现文件定义函数的具体实现过程,测试文件中有main函数,进行测试。而编译器将会对除头文件以外的文件 进行编译(之所以不编译头文件,是因为其他文件中包含了头文件),形成目标文件(.obj)。再将这些编译后的文件进行链接处理,形成可执行程序(.exe)。
3.2 模板不能分离编译的原因
每个文件在进行编译后,会附带一个符号表 ,方便链接器在其中寻找相应的函数名 和函数地址。如果模板分离在两个文件中进行声明和定义:
这种情况下,编译器会去之前包含的头文件中(Text.h)去寻找该函数的定义,但头文件中只有函数的声明,没有函数的定义,所以编译器无法实例化该函数。由于无法实例化该函数,所以编译器无法生成具体的函数调用指令,也就没有办法为这个函数分配地址。故该函数并没有在该文件的符号表中,编译器链接时在不同文件的符号表中查找Add函数的时候,就会找不到这个函数,生成链接错误。
3.3 解决方法
问题出现的原因是没有实例化相应的模板参数,那么只要在函数具体实现的位置实例化函数就可以解决问题:
但是这样非常不方便,每用一个类型就要实例化一次。建议直接在头文件中进行声明和定义,其他文件直接包头文件就好了。这样编译器就能够向上寻找到头文件中的函数定义,进行相应的实例化。