沧州市做网站,如何免费自学网站建设,做一个微网站平台,wordpress模板和主题✨个人主页#xff1a; 熬夜学编程的小林
#x1f497;系列专栏#xff1a; 【C语言详解】 【数据结构详解】【C详解】
目录 1. 非类型模板参数
2. 模板的特化
2.1 概念
2.2 函数模板特化
2.3 类模板特化
2.3.1 全特化
2.3.2 偏特化
2.3.3 类模板特化应用示例
3. …
✨个人主页 熬夜学编程的小林
系列专栏 【C语言详解】 【数据结构详解】【C详解】
目录 1. 非类型模板参数
2. 模板的特化
2.1 概念
2.2 函数模板特化
2.3 类模板特化
2.3.1 全特化
2.3.2 偏特化
2.3.3 类模板特化应用示例
3. 模板分离编译
3.1 什么是分离编译
3.2 模板的分离编译
3.3 解决方法
4. 模板总结 1. 非类型模板参数
模板参数分 类 类型形参与非类型形参。
类 类型形参即出现在模板参数列表中跟在class或者typename之类的参数类型名称。非类型形参就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。
由STL库中静态顺序表array举例我们声明类大小时需要固定元素个数在C语言中我们通常使用宏来定义常量如下
#define N 10 namespace lin
{templateclass Tclass array{public:T operator[](size_t index) { return _array[index]; }const T operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 _size; }private:T _array[N];size_t _size;};
}
上面的代码中有一个缺陷如果我们想实例化元素个数为10和元素个数为100的静态顺序表我们的办法就是实例化两个元素个数为100的静态顺序表但是此时会浪费很大的空间。由于上述原因我们就可以引出非类型模板参数代码实现如下
namespace lin
{// 只支持整型做非类型模板参数浮点数类对象自定义类型不能// 类型模板参数 class 对象// 非类型模板参数 类型 常量templateclass T , size_t N 10// 使用缺省参数class array{public:T operator[](size_t index) { return _array[index]; }const T operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 _size; }private:T _array[N];size_t _size;};
} 使用上述的代码声明类时可以根据自己的需求实例化不同大小的静态顺序表类。使用如下
int main()
{// 实例化大小不同的类lin::arrayint a1; // 默认 N 10lin::arrayint, 1000 a2; // N 1000return 0;
}
注意 1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。 2. 非类型的模板参数必须在编译期就能确认结果。 2. 模板的特化 2.1 概念 通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理比如实现了一个专门用来进行小于比较的函数模板。
日期类
class Date
{
public:friend ostream operator(ostream _cout, const Date d);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);}
private:int _year;int _month;int _day;
};ostream operator(ostream _cout, const Date d)
{_cout d._year - d._month - d._day;return _cout;
}
代码演示
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}
int main()
{cout Less(1, 2) endl;// 可以比较结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl; // 可以比较结果正确Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
} 可以看到Less绝对多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。上述示例中p1指向的d1显然小于p2指向的d2对象但是Less内部并没有比较p1和p2指向的对象内容而比较的是p1和p2指针的地址这就无法达到预期而错误。 此时就需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。 2.2 函数模板特化 函数模板的特化步骤 1. 必须要先有一个基础的函数模板。 2. 关键字template后面接一对空的尖括号。 3. 函数名后跟一对尖括号尖括号中指定需要特化的类型。 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误。 // 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}// 对Less函数模板进行特化
template
bool LessDate*(Date* left, Date* right)
{return *left *right;
}
int main()
{cout Less(1, 2) endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl;Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 调用特化之后的版本而不走模板生成了return 0;
}
注意一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给出。
bool Less(Date* left, Date* right)
{return *left *right;
}
该种实现简单明了代码的可读性高容易书写因为对于一些参数类型复杂的函数模板特化时特别给出因此函数模板不建议特化。 2.3 类模板特化 2.3.1 全特化
全特化即是将模板参数列表中所有的参数都确定化。
templateclass T1, class T2
class Data
{
public:Data() { cout DataT1, T2 endl; }
private:T1 _d1;T2 _d2;
};
template
class Dataint, char//两个模板参数均特别处理
{
public:Data() { cout Dataint, char endl; }
};
void Test()
{Dataint, int d1;//调用模板Dataint, char d2;//调用全特化
}
2.3.2 偏特化
偏特化(半特化)任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类
templateclass T1, class T2
class Data
{
public:Data() { cout DataT1, T2 endl; }
private:T1 _d1;T2 _d2;
};
偏特化有以下两种表现方式
1.部分特化将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
template class T1
class DataT1, int
{
public:Data() { cout DataT1, int endl; }
};
2.参数更进一步的限制。 偏特化并不仅仅是指特化部分参数而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
// 两个参数偏特化为指针类型
templateclass T1,class T2
class DataT1*, T2*
{
public:Data() { cout DataT1*, T2* endl; }
};// 一个参数特化为指针类型一个参数特化为引用类型
templateclass T1,class T2
class DataT1, T2*
{
public:Data() { cout DataT1, T2* endl; }
};int main()
{Dataint*, int* d4;// 调用特化的指针版本Dataint, int* d5;// 调用一个为指针一个为引用类型return 0;
} 2.3.3 类模板特化应用示例 有如下专门用来按照小于比较的类模板Less
#includevector
#include algorithm
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vectorDate v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);// 可以直接排序结果是日期升序sort(v1.begin(), v1.end(), LessDate());vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 可以直接排序结果错误日期还不是升序而v2中放的地址是升序// 此处需要在排序过程中让sort比较v2中存放地址指向的日期对象// 但是走Less模板sort在排序时实际比较的是v2中指针的地址因此无法达到预期sort(v2.begin(), v2.end(), LessDate*());vectorDate*::iterator it v2.begin();while (it ! v2.end()){cout *(*it) ;//打印日期it;}return 0;
} 通过观察上述程序的结果发现对于日期对象可以直接排序并且结果是正确的。但是如果待排序元素是指针结果就不一定正确。因为sort最终按照Less模板中方式比较所以只会比较指针而不是比较指针指向空间中内容此时可以使用类版本特化来处理上述问题
// 对Less类模板按照指针方式特化
templateclass T
struct LessT*
{bool operator()(const T* x, const T* y) const{return *x *y;}
}; 特化之后在运行上述代码就可以得到正确的结果。
3. 模板分离编译 3.1 什么是分离编译 一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
3.2 模板的分离编译 假如有以下场景模板的声明与定义分离开在头文件中进行声明源文件中完成定义
Array.h
// 声明
namespace lin
{templateclass T, size_t N 10class array{public:size_t size()const;private:T _array[N];size_t _size 0;// 缺省值初始化};void func();
}
Array.cpp
// 定义
namespace lin
{templateclass T,size_t Nsize_t arrayT, N::size()const{return _size;}void func(){cout void func() endl;}
}
Test.cpp int main()
{lin::arrayint a1;// 构造函数cout a1.size() endl;lin::func();
} 在上述代码中当调用size函数时会报链接错误而调用func函数则不会报错为什么呢 // size与func都只有声明编译时检查一下函数名和参数匹配没问题则暂且通过 // 定义在.cpp文件链接的时候再去其他文件中找函数地址 // 调用的地方知道实例化T成什么类型但是只有声明没有定义 // 定义的地方不知道实例化T成什么类型所以有定义无法实例化也就是无法生成函数的地址到符号表 3.3 解决方法
1. 将声明和定义放到一个文件 xxx.hpp 里面或者xxx.h其实也是可以的。推荐使用这种。 在.h文件中声明定义 // .h预处理展开后实例化模板时既有声明又有定义直接就实例化 // 编译时有函数的定义直接就有地址不需要链接去找 如下
// 声明定义在同一个.h文件
namespace lin
{templateclass T, size_t N 10class array{public:size_t size()const;private:T _array[N];size_t _size 0;// 缺省值初始化};templateclass T, size_t Nsize_t arrayT, N::size()const{return _size;}void func();
}
2. 模板定义的位置显式实例化。这种方法不实用不推荐使用。
namespace lin
{templateclass T, size_t N 10class array{public:size_t size()const;private:T _array[N];size_t _size 0;// 缺省值初始化};// 显示实例化templateclass arrayint;templateclass arraydouble;
}
显示实例化的缺陷是一个类型就需要实例化一次比较麻烦。
4. 模板总结 【优点】 1. 模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生。 2. 增强了代码的灵活性。 【缺陷】 1. 模板会导致代码膨胀问题也会导致编译时间变长。 2. 出现模板编译错误时错误信息非常凌乱不易定位错误 。